diff --git a/Makefile b/Makefile index 8dc3d49..0e55009 100644 --- a/Makefile +++ b/Makefile @@ -45,26 +45,29 @@ prep-python: .venv/bin/pip install --upgrade pip .venv/bin/pip install requests parameterized +exec-python-e2e-test: + TEST_ARCH=${TEST_ARCH} TEST_PORT=${TEST_PORT} .venv/bin/python3 test/integration/local_lambda/test_end_to_end.py + integ-tests: make prep-python docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - TEST_ARCH=x86_64 TEST_PORT=8002 .venv/bin/python3 test/integration/local_lambda/test_end_to_end.py - TEST_ARCH=arm64 TEST_PORT=9002 .venv/bin/python3 test/integration/local_lambda/test_end_to_end.py - TEST_ARCH="" TEST_PORT=9052 .venv/bin/python3 test/integration/local_lambda/test_end_to_end.py + make TEST_ARCH=x86_64 TEST_PORT=8002 exec-python-e2e-test + make TEST_ARCH=arm64 TEST_PORT=9002 exec-python-e2e-test + make TEST_ARCH="" TEST_PORT=9052 exec-python-e2e-test integ-tests-with-docker-x86-64: make ARCH=x86_64 compile-with-docker make prep-python - TEST_ARCH=x86_64 TEST_PORT=8002 .venv/bin/python3 test/integration/local_lambda/test_end_to_end.py + make TEST_ARCH=x86_64 TEST_PORT=8002 exec-python-e2e-test integ-tests-with-docker-arm64: make ARCH=arm64 compile-with-docker make prep-python docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - TEST_ARCH="arm64" TEST_PORT=9002 .venv/bin/python3 test/integration/local_lambda/test_end_to_end.py + make TEST_ARCH=arm64 TEST_PORT=9002 exec-python-e2e-test integ-tests-with-docker-old: make ARCH=old compile-with-docker make prep-python - TEST_ARCH="" TEST_PORT=9052 .venv/bin/python3 test/integration/local_lambda/test_end_to_end.py + make TEST_ARCH="" TEST_PORT=9052 exec-python-e2e-test \ No newline at end of file diff --git a/test/integration/local_lambda/test_end_to_end.py b/test/integration/local_lambda/test_end_to_end.py index 4046a31..47a9b2e 100644 --- a/test/integration/local_lambda/test_end_to_end.py +++ b/test/integration/local_lambda/test_end_to_end.py @@ -10,7 +10,7 @@ from contextlib import contextmanager from parameterized import parameterized -SLEEP_TIME = 2 +SLEEP_TIME = 1.5 DEFAULT_1P_ENTRYPOINT = "/lambda-entrypoint.sh" ARCHS = ["x86_64", "arm64", ""] @@ -23,11 +23,11 @@ class TestEndToEnd(TestCase): def setUpClass(cls): testdata_path = Path(__file__).resolve().parents[1].joinpath("testdata") dockerfile_path = testdata_path.joinpath("Dockerfile-allinone") - cls.image_name = "aws-lambda-local:testing" cls.path_to_binary = Path().resolve().joinpath("bin") # build image - image_name = cls.image_name if cls.ARCH == "" else f"{cls.image_name}-{cls.ARCH}" + image_name_base = "aws-lambda-local:testing" + cls.image_name = image_name_base if cls.ARCH == "" else f"{image_name_base}-{cls.ARCH}" architecture = cls.ARCH if cls.ARCH == "arm64" else "amd64" docker_arch = cls.ARCH if cls.ARCH == "arm64" else "x86_64" @@ -37,27 +37,25 @@ def setUpClass(cls): "--platform", f"linux/{architecture}", "-t", - image_name, + cls.image_name, "-f", str(dockerfile_path), str(testdata_path), "--build-arg", f"IMAGE_ARCH={docker_arch}", ] - print (build_cmd) Popen(build_cmd).communicate() @classmethod def tearDownClass(cls): - arch_tag = "" if cls.ARCH == "" else f"-{cls.ARCH}" - Popen(f"docker rmi {cls.image_name}{arch_tag}".split(" ")).communicate() + Popen(f"docker rmi {cls.image_name}".split(" ")).communicate() - def tagged_name(self, name, architecture): - tag = self.get_tag(architecture) - return (name + tag, "aws-lambda-rie" + tag, self.image_name + tag) + def tagged_name(self, name): + tag = self.get_tag() + return (name + tag, "aws-lambda-rie" + tag, self.image_name) - def get_tag(self, architecture): - return "" if architecture == "" else str(f"-{architecture}") + def get_tag(self): + return "" if self.ARCH == "" else str(f"-{self.ARCH}") def run_command(self, cmd): Popen(cmd.split(" ")).communicate() @@ -65,16 +63,16 @@ def run_command(self, cmd): def sleep_1s(self): time.sleep(SLEEP_TIME) - def invoke_function(self, port): + def invoke_function(self): return requests.post( - f"http://localhost:{port}/2015-03-31/functions/function/invocations", json={} + f"http://localhost:{self.PORT}/2015-03-31/functions/function/invocations", json={} ) @contextmanager - def create_container(self, cmd, image): + def create_container(self, param, image): try: platform = "x86_64" if self.ARCH == "" else self.ARCH - cmd_full = f"docker run --platform linux/{platform} {cmd}" + cmd_full = f"docker run --platform linux/{platform} {param}" self.run_command(cmd_full) # sleep 1s to give enough time for the endpoint to be up to curl @@ -88,149 +86,150 @@ def create_container(self, cmd, image): self.run_command(f"docker rm -f {image}") - def test_env_var_with_equal_sign(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("envvarcheck", arch) - cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_env_var_handler" + def test_env_var_with_equal_sign(self): + image, rie, image_name = self.tagged_name("envvarcheck") + cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_env_var_handler" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() self.assertEqual(b'"4=4"', r.content) - def test_two_invokes(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("twoinvokes", arch) + def test_two_invokes(self): + image, rie, image_name = self.tagged_name("twoinvokes") - cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler" + cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() self.assertEqual(b'"My lambda ran succesfully"', r.content) # Make sure we can invoke the function twice - r = self.invoke_function(port) + r = self.invoke_function() self.assertEqual(b'"My lambda ran succesfully"', r.content) - def test_lambda_function_arn_exists(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("arnexists", arch) + def test_lambda_function_arn_exists(self): + image, rie, image_name = self.tagged_name("arnexists") - cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context" + cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() self.assertEqual(b'"My lambda ran succesfully"', r.content) - def test_lambda_function_arn_exists_with_defining_custom_name(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("customname", arch) + def test_lambda_function_arn_exists_with_defining_custom_name(self): + image, rie, image_name = self.tagged_name("customname") - cmd = f"--name {image} --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context" + cmd = f"--name {image} --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_lambda_arn_in_context" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() self.assertEqual(b'"My lambda ran succesfully"', r.content) - def test_timeout_invoke(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("timeout", arch) + def test_timeout_invoke(self): + image, rie, image_name = self.tagged_name("timeout") - cmd = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=1 -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.sleep_handler" + cmd = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=1 -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.sleep_handler" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() self.assertEqual(b"Task timed out after 1.00 seconds", r.content) - def test_exception_returned(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("exception", arch) + def test_exception_returned(self): + image, rie, image_name = self.tagged_name("exception") - cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.exception_handler" + cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.exception_handler" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() - # ignore request_id in python3.12 lambda + # Except the 3 fields assrted below, there's another field `request_id` included start from python3.12 runtime. + # We should ignore asserting the field `request_id` for it is in a UUID like format and chages everytime result = r.json() self.assertEqual(result["errorMessage"],"Raising an exception") self.assertEqual(result["errorType"],"Exception") self.assertEqual(result["stackTrace"],[" File \"/var/task/main.py\", line 13, in exception_handler\n raise Exception(\"Raising an exception\")\n"]) - def test_context_get_remaining_time_in_three_seconds(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("remaining_time_in_three_seconds", arch) + def test_context_get_remaining_time_in_three_seconds(self): + image, rie, image_name = self.tagged_name("remaining_time_in_three_seconds") - cmd = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=3 -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" + cmd = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=3 -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() # Execution time is not decided, 1.0s ~ 3.0s is a good estimation self.assertLess(int(r.content), 3000) self.assertGreater(int(r.content), 1000) - def test_context_get_remaining_time_in_ten_seconds(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("remaining_time_in_ten_seconds", arch) + def test_context_get_remaining_time_in_ten_seconds(self): + image, rie, image_name = self.tagged_name("remaining_time_in_ten_seconds") - cmd = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=10 -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" + cmd = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_TIMEOUT=10 -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() # Execution time is not decided, 8.0s ~ 10.0s is a good estimation self.assertLess(int(r.content), 10000) self.assertGreater(int(r.content), 8000) - def test_context_get_remaining_time_in_default_deadline(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("remaining_time_in_default_deadline", arch) + def test_context_get_remaining_time_in_default_deadline(self): + image, rie, image_name = self.tagged_name("remaining_time_in_default_deadline") - cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" + cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.check_remaining_time_handler" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() # Executation time is not decided, 298.0s ~ 300.0s is a good estimation self.assertLess(int(r.content), 300000) self.assertGreater(int(r.content), 298000) - def test_invoke_with_pre_runtime_api_runtime(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("pre-runtime-api", arch) + def test_invoke_with_pre_runtime_api_runtime(self): + image, rie, image_name = self.tagged_name("pre-runtime-api") - cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler" + cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() self.assertEqual(b'"My lambda ran succesfully"', r.content) - def test_function_name_is_overriden(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("assert-overwritten", arch) + def test_function_name_is_overriden(self): + image, rie, image_name = self.tagged_name("assert-overwritten") - cmd = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_env_var_is_overwritten" + cmd = f"--name {image} -d --env AWS_LAMBDA_FUNCTION_NAME=MyCoolName -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8080 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.assert_env_var_is_overwritten" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() self.assertEqual(b'"My lambda ran succesfully"', r.content) - def test_port_override(self, arch=ARCH, port=PORT): - image, rie, image_name = self.tagged_name("port_override", arch) + def test_port_override(self): + image, rie, image_name = self.tagged_name("port_override") # Use port 8081 inside the container instead of 8080 - cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {port}:8081 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler --runtime-interface-emulator-address 0.0.0.0:8081" + cmd = f"--name {image} -d -v {self.path_to_binary}:/local-lambda-runtime-server -p {self.PORT}:8081 --entrypoint /local-lambda-runtime-server/{rie} {image_name} {DEFAULT_1P_ENTRYPOINT} main.success_handler --runtime-interface-emulator-address 0.0.0.0:8081" with self.create_container(cmd, image): - r = self.invoke_function(port) + r = self.invoke_function() self.assertEqual(b'"My lambda ran succesfully"', r.content)