9
authorSam Mirazi <sasan345@gmail.com>
Sun, 1 Jun 2025 04:42:34 +0000 (21:42 -0700)
committerSam Mirazi <sasan345@gmail.com>
Sun, 1 Jun 2025 04:42:34 +0000 (21:42 -0700)
app_fastapi/__pycache__/app.cpython-312.pyc [new file with mode: 0644]
app_fastapi/app.py [new file with mode: 0644]
app_flask/flask_application.py
benchmark/run_benchmark.py [new file with mode: 0644]
tests/__pycache__/test_fastapi_route.cpython-312-pytest-8.3.5.pyc
tests/__pycache__/test_fastapi_route.cpython-312.pyc [new file with mode: 0644]
tests/__pycache__/test_flask_route.cpython-312-pytest-8.3.5.pyc
tests/test_fastapi_route.py
tests/test_flask_route.py

diff --git a/app_fastapi/__pycache__/app.cpython-312.pyc b/app_fastapi/__pycache__/app.cpython-312.pyc
new file mode 100644 (file)
index 0000000..16396b3
Binary files /dev/null and b/app_fastapi/__pycache__/app.cpython-312.pyc differ
diff --git a/app_fastapi/app.py b/app_fastapi/app.py
new file mode 100644 (file)
index 0000000..bc0f0cf
--- /dev/null
@@ -0,0 +1,11 @@
+# app_fastapi/app.py
+from fastapi import FastAPI, Response
+import asyncio
+
+app = FastAPI()
+
+@app.get("/")
+async def home():
+    await asyncio.sleep(3)  # simulate slow work (non-blocking)
+    html = "<h1>FastAPI Server: 3s Artificial Delay Demo</h1>"
+    return Response(content=html, media_type="text/html") 
\ No newline at end of file
index 5df2db9741000e1e748a2411d784c2a156f90e94..5da77ea27abc9b0aab8ecae89b8a2ddda88914b5 100644 (file)
@@ -7,7 +7,7 @@ app = Flask(__name__)
 @app.route("/")
 def home():
     time.sleep(3)                # simulate slow work
-    html = "<h1>Slow Flask Demo</h1>" # TDD Phase 2 content
+    html = "<h1>Flask Server: 3s Artificial Delay Demo</h1>" # Updated content
     return Response(html, mimetype="text/html")
 
 if __name__ == "__main__":
diff --git a/benchmark/run_benchmark.py b/benchmark/run_benchmark.py
new file mode 100644 (file)
index 0000000..73b7798
--- /dev/null
@@ -0,0 +1,77 @@
+# benchmark/run_benchmark.py
+import argparse
+import time
+import asyncio
+import httpx
+import requests
+from concurrent.futures import ThreadPoolExecutor
+
+# Server URLs (ensure these match your running servers)
+FLASK_URL = "http://127.0.0.1:3000/"
+FASTAPI_URL = "http://127.0.0.1:8000/"
+NUM_REQUESTS = 100
+
+def fetch_url_sync(url):
+    try:
+        response = requests.get(url, timeout=10) # Increased timeout for potentially slow server
+        response.raise_for_status() # Raise an exception for bad status codes
+        return response.status_code
+    except requests.exceptions.RequestException as e:
+        print(f"Request to {url} failed: {e}")
+        return None
+
+def run_flask_benchmark():
+    print(f"Starting Flask benchmark: {NUM_REQUESTS} requests to {FLASK_URL}...")
+    start_time = time.perf_counter()
+    
+    with ThreadPoolExecutor(max_workers=NUM_REQUESTS) as executor:
+        futures = [executor.submit(fetch_url_sync, FLASK_URL) for _ in range(NUM_REQUESTS)]
+        results = [future.result() for future in futures]
+    
+    end_time = time.perf_counter()
+    total_time = end_time - start_time
+    successful_requests = sum(1 for r in results if r == 200)
+    print(f"Flask benchmark: {successful_requests}/{NUM_REQUESTS} successful requests in {total_time:.2f} seconds.")
+    return total_time
+
+async def fetch_url_async(client, url):
+    try:
+        response = await client.get(url, timeout=10) # Increased timeout
+        response.raise_for_status()
+        return response.status_code
+    except httpx.RequestError as e:
+        print(f"Request to {url} failed: {e}")
+        return None
+
+async def run_fastapi_benchmark_async():
+    print(f"Starting FastAPI benchmark: {NUM_REQUESTS} requests to {FASTAPI_URL}...")
+    start_time = time.perf_counter()
+    
+    async with httpx.AsyncClient() as client:
+        tasks = [fetch_url_async(client, FASTAPI_URL) for _ in range(NUM_REQUESTS)]
+        results = await asyncio.gather(*tasks)
+    
+    end_time = time.perf_counter()
+    total_time = end_time - start_time
+    successful_requests = sum(1 for r in results if r == 200)
+    print(f"FastAPI benchmark: {successful_requests}/{NUM_REQUESTS} successful requests in {total_time:.2f} seconds.")
+    return total_time
+
+def run_fastapi_benchmark():
+    return asyncio.run(run_fastapi_benchmark_async())
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(description="Run web server benchmarks.")
+    parser.add_argument(
+        "framework", 
+        choices=["flask", "fastapi"], 
+        help="Specify the framework to benchmark (flask or fastapi)"
+    )
+    args = parser.parse_args()
+
+    if args.framework == "flask":
+        run_flask_benchmark()
+    elif args.framework == "fastapi":
+        run_fastapi_benchmark()
+    else:
+        print("Invalid framework specified. Choose 'flask' or 'fastapi'.") 
\ No newline at end of file
index bb357836760e7f8e53d6b9875610e219458fce22..d59111794cb699e712765566e5dc7cc95fb75cf1 100644 (file)
Binary files a/tests/__pycache__/test_fastapi_route.cpython-312-pytest-8.3.5.pyc and b/tests/__pycache__/test_fastapi_route.cpython-312-pytest-8.3.5.pyc differ
diff --git a/tests/__pycache__/test_fastapi_route.cpython-312.pyc b/tests/__pycache__/test_fastapi_route.cpython-312.pyc
new file mode 100644 (file)
index 0000000..0dc75b3
Binary files /dev/null and b/tests/__pycache__/test_fastapi_route.cpython-312.pyc differ
index 01df2d8f6357bf2d2b7d4b2087e8777c359de92c..5dbc3548e370d4b9fb6700a9f94623571556da27 100644 (file)
Binary files a/tests/__pycache__/test_flask_route.cpython-312-pytest-8.3.5.pyc and b/tests/__pycache__/test_flask_route.cpython-312-pytest-8.3.5.pyc differ
index f873426dd9c924ade6b8ba9dac9b34dde17fe487..015dbd62b68ff7cd4ca5c191a2442cf5ea417914 100644 (file)
@@ -1,41 +1,61 @@
 # tests/test_fastapi_route.py
 import httpx
-import subprocess, asyncio, os, signal, time # signal and time may not be needed if Popen is handled well
+import asyncio
 import pytest # For @pytest.mark.asyncio
+import uvicorn
+import threading
+import time
+from multiprocessing import Process # Using Process for better isolation
+
+# Server configuration
+HOST = "127.0.0.1"
+PORT = 8000
+
+class UvicornServer(threading.Thread):
+    def __init__(self, app_module_str):
+        super().__init__(daemon=True)
+        self.app_module_str = app_module_str
+        self.server_started = threading.Event()
+        self.config = uvicorn.Config(app_module_str, host=HOST, port=PORT, log_level="info")
+        self.server = uvicorn.Server(config=self.config)
+
+    def run(self):
+        # Need to set a new event loop for the thread if running asyncio components
+        # For uvicorn.Server.serve(), it typically manages its own loop or integrates
+        # with an existing one if started from an async context. 
+        # However, running it in a separate thread needs care.
+        # Simpler: uvicorn.run() which handles loop creation.
+        uvicorn.run(self.app_module_str, host=HOST, port=PORT, log_level="warning") # log_level warning to reduce noise
+
+    # UvicornServer using Process for cleaner start/stop
+    # This might be more robust for test isolation.
+
+def run_server_process(app_module_str, host, port):
+    uvicorn.run(app_module_str, host=host, port=port, log_level="warning")
 
-# Adjusted to be an async function and use uvicorn
 async def start_server_fastapi():
-    # Ensure CWD is project root
-    project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
-    # Command to run FastAPI app with uvicorn
-    # Assuming app_fastapi/app.py and app instance is named 'app'
-    cmd = ["uvicorn", "app_fastapi.app:app", "--host", "0.0.0.0", "--port", "8000"]
-    
-    proc = subprocess.Popen(
-        cmd,
-        cwd=project_root,
-        stdout=subprocess.PIPE,
-        stderr=subprocess.PIPE
-    )
-    await asyncio.sleep(1.5) # Allow uvicorn to start
-    return proc
+    # Using Process to run Uvicorn. This provides better isolation and cleanup.
+    proc = Process(target=run_server_process, args=("app_fastapi.app:app", HOST, PORT), daemon=True)
+    proc.start()
+    await asyncio.sleep(2.0) # Increased sleep to ensure server is fully up
+    if not proc.is_alive():
+        raise RuntimeError("FastAPI server process failed to start.")
+    return proc # Return the process object
 
 @pytest.mark.asyncio
 async def test_home_returns_html_fastapi():
-    proc = await start_server_fastapi()
+    server_process = await start_server_fastapi()
     try:
-        async with httpx.AsyncClient() as client:
-            r = await client.get("http://127.0.0.1:8000/") # Default FastAPI port
+        async with httpx.AsyncClient(timeout=10) as client:
+            r = await client.get(f"http://{HOST}:{PORT}/")
             assert r.status_code == 200
-            assert "<h1>Slow FastAPI Demo</h1>" in r.text # Expected content
+            assert "<h1>FastAPI Server: 3s Artificial Delay Demo</h1>" in r.text # Expected content
     finally:
-        proc.terminate()
-        try:
-            # Use communicate to get output and ensure process is reaped
-            stdout, stderr = proc.communicate(timeout=5)
-            # print(f"FastAPI stdout:\n{stdout.decode(errors='replace')}") # Optional: for debugging
-            # print(f"FastAPI stderr:\n{stderr.decode(errors='replace')}") # Optional: for debugging
-        except subprocess.TimeoutExpired:
-            print("FastAPI server did not terminate/communicate gracefully, killing.")
-            proc.kill()
-            proc.wait() # Ensure kill completes 
\ No newline at end of file
+        if server_process and server_process.is_alive():
+            server_process.terminate() # Send SIGTERM
+            server_process.join(timeout=5) # Wait for termination
+            if server_process.is_alive(): # Still alive after timeout
+                print("FastAPI server process did not terminate gracefully, killing.")
+                server_process.kill() # Force kill
+                server_process.join(timeout=5) # Wait for kill
+            # print("FastAPI server process stopped.") # Optional debug 
\ No newline at end of file
index d9a7e617567ce6eed8e13a4ec5fa833b12f878c9..5185c79c28650d7ea9ec155f9ed3a39c8dfd5769 100644 (file)
@@ -27,7 +27,7 @@ def test_home_returns_html():
         }
         r = httpx.get("http://127.0.0.1:3000/", timeout=10, headers=headers)
         assert r.status_code == 200
-        assert "<h1>Slow Flask Demo</h1>" in r.text
+        assert "<h1>Flask Server: 3s Artificial Delay Demo</h1>" in r.text
     finally:
         proc.terminate()
         try: