From 260b5b769f64253a511208255eed9850a7f68dbf Mon Sep 17 00:00:00 2001 From: Adam Fisher Date: Thu, 12 Oct 2023 19:29:28 +0300 Subject: [PATCH] test: fastapi tests running locally, reduced external dependencies, removed duplicate test --- noxfile.py | 37 ---------------- src/test/components/start_uvicorn.py | 15 +++++++ src/test/components/tests/app_runner.py | 16 +++---- .../components/tests/test_attr_max_size.py | 44 ------------------- .../components/tests/test_execution_tags.py | 11 +++-- src/test/integration/fastapi/app/__init__.py | 6 +-- .../fastapi/fastapi_external_apis/__init__.py | 0 .../fastapi/fastapi_external_apis/app.py | 16 +++++++ src/test/integration/fastapi/start_uvicorn.py | 11 +++-- .../integration/fastapi/tests/app_runner.py | 12 ++--- .../integration/fastapi/tests/test_fastapi.py | 38 ++++++++++------ 11 files changed, 85 insertions(+), 121 deletions(-) create mode 100644 src/test/components/start_uvicorn.py delete mode 100644 src/test/components/tests/test_attr_max_size.py create mode 100644 src/test/integration/fastapi/fastapi_external_apis/__init__.py create mode 100644 src/test/integration/fastapi/fastapi_external_apis/app.py diff --git a/noxfile.py b/noxfile.py index 5d9dbc78..1f593a32 100755 --- a/noxfile.py +++ b/noxfile.py @@ -382,11 +382,6 @@ def integration_tests_fastapi( @nox.session(python=python_versions()) def component_tests(session): - component_tests_attr_max_size( - session=session, - fastapi_version="0.78.0", # arbitrary version - uvicorn_version="0.16.0", # TODO don't update, see https://lumigo.atlassian.net/browse/RD-11466 - ) component_tests_execution_tags( session=session, fastapi_version="0.78.0", # arbitrary version @@ -394,38 +389,6 @@ def component_tests(session): ) -def component_tests_attr_max_size( - session, - fastapi_version, - uvicorn_version, -): - install_package("uvicorn", uvicorn_version, session) - install_package("fastapi", fastapi_version, session) - - session.install(".") - - temp_file = create_component_tempfile("attr_max_size") - with session.chdir("src/test/components"): - session.install("-r", OTHER_REQUIREMENTS) - - try: - session.run( - "pytest", - "--tb", - "native", - "--log-cli-level=INFO", - "--color=yes", - "-v", - "./tests/test_attr_max_size.py", - env={ - "LUMIGO_DEBUG_SPANDUMP": temp_file, - "OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT": "1", - }, - ) - finally: - clean_outputs(temp_file, session) - - def component_tests_execution_tags( session, fastapi_version, diff --git a/src/test/components/start_uvicorn.py b/src/test/components/start_uvicorn.py new file mode 100644 index 00000000..67e98b16 --- /dev/null +++ b/src/test/components/start_uvicorn.py @@ -0,0 +1,15 @@ +import argparse +import uvicorn + + +def serve(app, port): + """Serve the web application.""" + uvicorn.run(app, port=port) + + +if __name__ == "__main__": + argParser = argparse.ArgumentParser() + argParser.add_argument("--app", help="app name") + argParser.add_argument("--port", type=int, help="port number") + args = argParser.parse_args() + serve(args.app, args.port) diff --git a/src/test/components/tests/app_runner.py b/src/test/components/tests/app_runner.py index 9282b730..61422fd0 100644 --- a/src/test/components/tests/app_runner.py +++ b/src/test/components/tests/app_runner.py @@ -5,8 +5,8 @@ from test.test_utils.processes import kill_process -class FastApiSample(object): - def __init__(self): +class FastApiApp(object): + def __init__(self, app: str, port: int): cwd = Path(__file__).parent.parent print(f"cwd = {cwd}") env = { @@ -16,19 +16,13 @@ def __init__(self): "OTEL_SERVICE_NAME": "fastapi_test_app", } print(f"env = {env}") - venv_bin_path = Path(sys.executable).parent - print(f"venv_bin_path = {venv_bin_path}") - cmd = ( - f". {venv_bin_path}/activate;" - "uvicorn fastapi_external_apis.app:app --port 8021 &" - "uvicorn app:app --port 8020 &" - ) + print(f"venv bin path = {Path(sys.executable).parent}") + cmd = [sys.executable, "start_uvicorn.py", "--app", app, "--port", str(port)] print(f"cmd = {cmd}") self.process = subprocess.Popen( cmd, cwd=cwd, env=env, - shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) @@ -39,7 +33,7 @@ def __init__(self): is_app_running = True break if not is_app_running: - raise Exception("FastApiSample app failed to start") + raise Exception(f"FastApiApp app '{app}' failed to start on port {port}") def __enter__(self): return self diff --git a/src/test/components/tests/test_attr_max_size.py b/src/test/components/tests/test_attr_max_size.py deleted file mode 100644 index 5ffd1b3d..00000000 --- a/src/test/components/tests/test_attr_max_size.py +++ /dev/null @@ -1,44 +0,0 @@ -import unittest -from test.test_utils.spans_parser import SpansContainer - -import requests - -from .app_runner import FastApiSample - - -class TestLargeSpans(unittest.TestCase): - def test_large_span_attribute_size_max_size_env_var_was_set(self): - with FastApiSample(): - response = requests.get( - "http://localhost:8020/invoke-requests-large-response" - ) - response.raise_for_status() - - body = response.json() - - assert body is not None - - spans_container = SpansContainer.get_spans_from_file( - wait_time_sec=10, expected_span_count=4 - ) - self.assertEqual(4, len(spans_container.spans)) - - # assert root - root = spans_container.get_first_root() - self.assertIsNotNone(root) - root_attributes = root["attributes"] - self.assertEqual(root_attributes["http.status_code"], 200) - - self.assert_attribute_length(root_attributes, 1) - - # assert child spans - children = spans_container.get_non_internal_children() - self.assertEqual(1, len(children)) - child_attributes = children[0]["attributes"] - self.assert_attribute_length(child_attributes, 1) - - def assert_attribute_length(self, child_attributes: dict, length: int) -> None: - for k, v in child_attributes.items(): - if isinstance(v, str): - assert len(v) == length - assert len(v) == length diff --git a/src/test/components/tests/test_execution_tags.py b/src/test/components/tests/test_execution_tags.py index 175f5d8f..15e3b2c9 100644 --- a/src/test/components/tests/test_execution_tags.py +++ b/src/test/components/tests/test_execution_tags.py @@ -3,13 +3,18 @@ import requests -from .app_runner import FastApiSample +from .app_runner import FastApiApp + +APP_PORT = 8020 +EXTERNAL_APP_PORT = 8021 class TestExecutionTags(unittest.TestCase): def test_execution_tag(self): - with FastApiSample(): - response = requests.get("http://localhost:8020/invoke-request") + with FastApiApp("app:app", APP_PORT), FastApiApp( + "fastapi_external_apis.app:app", EXTERNAL_APP_PORT + ): + response = requests.get(f"http://localhost:{APP_PORT}/invoke-request") response.raise_for_status() body = response.json() diff --git a/src/test/integration/fastapi/app/__init__.py b/src/test/integration/fastapi/app/__init__.py index 6ab75cf4..63d6ed37 100644 --- a/src/test/integration/fastapi/app/__init__.py +++ b/src/test/integration/fastapi/app/__init__.py @@ -11,15 +11,13 @@ async def root(): @app.get("/invoke-requests") def invoke_requests(): - response = requests.get("https://api.chucknorris.io/jokes/random") + response = requests.get("http://localhost:8021/little-response") return response.json() @app.get("/invoke-requests-large-response") def invoke_requests_big_response(): - response = requests.get( - "http://universities.hipolabs.com/search?country=United+States" - ) + response = requests.get("http://localhost:8021/big-response") response.raise_for_status() return response.json() diff --git a/src/test/integration/fastapi/fastapi_external_apis/__init__.py b/src/test/integration/fastapi/fastapi_external_apis/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/test/integration/fastapi/fastapi_external_apis/app.py b/src/test/integration/fastapi/fastapi_external_apis/app.py new file mode 100644 index 00000000..b2959ff9 --- /dev/null +++ b/src/test/integration/fastapi/fastapi_external_apis/app.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI, Request + +app = FastAPI() + + +@app.get("/big-response") +def get_big_response(): + return {"data": "a" * 10_000} + + +@app.get("/little-response") +def get_little_response(request: Request): + return { + "url": str(request.url), + "data": "a" * 100 + } diff --git a/src/test/integration/fastapi/start_uvicorn.py b/src/test/integration/fastapi/start_uvicorn.py index 9ca29f9e..67e98b16 100644 --- a/src/test/integration/fastapi/start_uvicorn.py +++ b/src/test/integration/fastapi/start_uvicorn.py @@ -1,10 +1,15 @@ +import argparse import uvicorn -def serve(): +def serve(app, port): """Serve the web application.""" - uvicorn.run("app:app", port=8000) + uvicorn.run(app, port=port) if __name__ == "__main__": - serve() + argParser = argparse.ArgumentParser() + argParser.add_argument("--app", help="app name") + argParser.add_argument("--port", type=int, help="port number") + args = argParser.parse_args() + serve(args.app, args.port) diff --git a/src/test/integration/fastapi/tests/app_runner.py b/src/test/integration/fastapi/tests/app_runner.py index 3018aa88..61422fd0 100644 --- a/src/test/integration/fastapi/tests/app_runner.py +++ b/src/test/integration/fastapi/tests/app_runner.py @@ -5,8 +5,8 @@ from test.test_utils.processes import kill_process -class FastApiSample(object): - def __init__(self): +class FastApiApp(object): + def __init__(self, app: str, port: int): cwd = Path(__file__).parent.parent print(f"cwd = {cwd}") env = { @@ -16,7 +16,8 @@ def __init__(self): "OTEL_SERVICE_NAME": "fastapi_test_app", } print(f"env = {env}") - cmd = [sys.executable, "start_uvicorn.py"] + print(f"venv bin path = {Path(sys.executable).parent}") + cmd = [sys.executable, "start_uvicorn.py", "--app", app, "--port", str(port)] print(f"cmd = {cmd}") self.process = subprocess.Popen( cmd, @@ -27,13 +28,12 @@ def __init__(self): ) is_app_running = False for line in self.process.stderr: + print(line) if "Uvicorn running" in str(line): - print(f"FastApiSample app: {line}") is_app_running = True break - print(line) if not is_app_running: - raise Exception("FastApiSample app failed to start") + raise Exception(f"FastApiApp app '{app}' failed to start on port {port}") def __enter__(self): return self diff --git a/src/test/integration/fastapi/tests/test_fastapi.py b/src/test/integration/fastapi/tests/test_fastapi.py index 077a7b25..60847cdf 100644 --- a/src/test/integration/fastapi/tests/test_fastapi.py +++ b/src/test/integration/fastapi/tests/test_fastapi.py @@ -3,13 +3,16 @@ import requests -from .app_runner import FastApiSample +from .app_runner import FastApiApp + +APP_PORT = 8020 +EXTERNAL_APP_PORT = 8021 class TestFastApiSpans(unittest.TestCase): def test_200_OK(self): - with FastApiSample(): - response = requests.get("http://localhost:8000/") + with FastApiApp("app:app", APP_PORT): + response = requests.get(f"http://localhost:{APP_PORT}/") response.raise_for_status() body = response.json() @@ -27,7 +30,9 @@ def test_200_OK(self): self.assertEqual(root["kind"], "SpanKind.SERVER") self.assertEqual(root["attributes"]["http.status_code"], 200) self.assertEqual(root["attributes"]["http.method"], "GET") - self.assertEqual(root["attributes"]["http.url"], "http://127.0.0.1:8000/") + self.assertEqual( + root["attributes"]["http.url"], f"http://127.0.0.1:{APP_PORT}/" + ) # assert internal spans internals = spans_container.get_internals() @@ -44,13 +49,17 @@ def test_200_OK(self): ) def test_requests_instrumentation(self): - with FastApiSample(): - response = requests.get("http://localhost:8000/invoke-requests") + with FastApiApp("app:app", APP_PORT), FastApiApp( + "fastapi_external_apis.app:app", EXTERNAL_APP_PORT + ): + response = requests.get(f"http://localhost:{APP_PORT}/invoke-requests") response.raise_for_status() body = response.json() - self.assertIn("https://api.chucknorris.io/jokes/", body["url"]) + expected_url = f"http://localhost:{EXTERNAL_APP_PORT}/little-response" + + self.assertIn(expected_url, body["url"]) spans_container = SpansContainer.get_spans_from_file( wait_time_sec=10, expected_span_count=4 @@ -64,7 +73,8 @@ def test_requests_instrumentation(self): self.assertEqual(root["attributes"]["http.status_code"], 200) self.assertIsNotNone(root["attributes"]["http.request.headers"]) self.assertEqual( - root["attributes"]["http.url"], "http://127.0.0.1:8000/invoke-requests" + root["attributes"]["http.url"], + f"http://127.0.0.1:{APP_PORT}/invoke-requests", ) # assert external request span @@ -74,7 +84,7 @@ def test_requests_instrumentation(self): self.assertEqual(external_request_span["attributes"]["http.method"], "GET") self.assertEqual( external_request_span["attributes"]["http.url"], - "https://api.chucknorris.io/jokes/random", + expected_url, ) self.assertEqual( external_request_span["attributes"]["http.status_code"], 200 @@ -102,9 +112,11 @@ def test_requests_instrumentation(self): ) def test_large_span_attribute_size_default_max_size(self): - with FastApiSample(): + with FastApiApp("app:app", APP_PORT), FastApiApp( + "fastapi_external_apis.app:app", EXTERNAL_APP_PORT + ): response = requests.get( - "http://localhost:8000/invoke-requests-large-response" + f"http://localhost:{APP_PORT}/invoke-requests-large-response" ) response.raise_for_status() @@ -124,7 +136,7 @@ def test_large_span_attribute_size_default_max_size(self): self.assertEqual(root_attributes["http.status_code"], 200) self.assertEqual( root_attributes["http.url"], - "http://127.0.0.1:8000/invoke-requests-large-response", + f"http://127.0.0.1:{APP_PORT}/invoke-requests-large-response", ) self.assertEqual(root_attributes["http.method"], "GET") @@ -135,7 +147,7 @@ def test_large_span_attribute_size_default_max_size(self): self.assertEqual(external_request_span["attributes"]["http.method"], "GET") self.assertEqual( external_request_span["attributes"]["http.url"], - "http://universities.hipolabs.com/search?country=United+States", + f"http://localhost:{EXTERNAL_APP_PORT}/big-response", ) self.assertEqual( external_request_span["attributes"]["http.status_code"], 200