80 """Start HTTP server in background."""
81 runner_path = Path(
".build/meson-quick/examples/example_runner.exe").absolute()
82 dll_path = Path(
".build/meson-quick/examples/example-Server.dll").absolute()
84 if not runner_path.exists()
or not dll_path.exists():
85 console.print(
"[red]✗ Server executable not found - compile first:[/red]")
86 console.print(
" bash test Server --examples --build")
89 proc = RunningProcess(
90 [str(runner_path), str(dll_path)],
99 time.sleep(config.server_start_delay)
103 with httpx.Client(timeout=config.timeout)
as client:
104 response = client.get(f
"http://{config.host}:{config.port}/ping")
105 if response.status_code == 200:
107 except KeyboardInterrupt:
110 _thread.interrupt_main()
111 except Exception
as e:
114 console.print(f
"[red]✗ Server not responsive: {e}[/red]")
119 console.print(
"[red]✗ Server failed to start[/red]")
124 config: StressTestConfig, results: StressTestResults, client: httpx.Client
126 """Make a single HTTP request and record results."""
127 url = f
"http://{config.host}:{config.port}/"
130 start_time = time.perf_counter()
131 response = client.get(url, timeout=config.timeout)
132 elapsed_ms = (time.perf_counter() - start_time) * 1000
134 if response.status_code == 200:
135 results.record_success(elapsed_ms)
137 results.record_failure(
"http_error")
139 except httpx.ConnectError:
140 results.record_failure(
"connection")
141 except httpx.TimeoutException:
142 results.record_failure(
"timeout")
143 except KeyboardInterrupt:
144 _thread.interrupt_main()
146 results.record_failure(
"other")
150 config: StressTestConfig, results: StressTestResults
152 """Test: Multiple concurrent connections."""
153 console.print(
"\n[bold cyan]Test 1: Concurrent Connections[/bold cyan]")
154 console.print(f
"Making {config.num_connections} concurrent requests...")
158 TextColumn(
"[progress.description]{task.description}"),
160 TaskProgressColumn(),
163 task = progress.add_task(
"Requesting...", total=config.num_connections)
165 threads: list[threading.Thread] = []
166 with httpx.Client()
as client:
167 for _
in range(config.num_connections):
168 thread: threading.Thread = threading.Thread(
169 target=make_request, args=(config, results, client)
172 threads.append(thread)
174 for thread
in threads:
176 progress.update(task, advance=1)
179 (results.successful_requests / results.total_requests * 100)
180 if results.total_requests > 0
184 f
" Success rate: {success_rate:.1f}% ({results.successful_requests}/{results.total_requests})"
189 """Test: Rapid sequential requests."""
190 console.print(
"\n[bold cyan]Test 2: Rapid Sequential Requests[/bold cyan]")
191 console.print(f
"Making {config.num_requests} rapid sequential requests...")
193 initial_count = results.total_requests
197 TextColumn(
"[progress.description]{task.description}"),
199 TaskProgressColumn(),
202 task = progress.add_task(
"Requesting...", total=config.num_requests)
204 with httpx.Client()
as client:
205 for _
in range(config.num_requests):
207 progress.update(task, advance=1)
209 test_requests = results.total_requests - initial_count
210 test_success = results.successful_requests - (
211 initial_count - (initial_count - results.successful_requests)
213 success_rate = (test_success / test_requests * 100)
if test_requests > 0
else 0
215 f
" Success rate: {success_rate:.1f}% ({test_success}/{test_requests})"
220 """Display test results in a formatted table."""
221 console.print(
"\n[bold]Stress Test Results[/bold]")
223 table = Table(title=
"Summary")
224 table.add_column(
"Metric", style=
"cyan")
225 table.add_column(
"Value", style=
"magenta")
227 table.add_row(
"Total Requests", str(results.total_requests))
230 f
"{results.successful_requests} ({results.successful_requests / results.total_requests * 100:.1f}%)",
234 f
"{results.failed_requests} ({results.failed_requests / results.total_requests * 100:.1f}%)",
236 table.add_row(
"Connection Errors", str(results.connection_errors))
237 table.add_row(
"Timeout Errors", str(results.timeout_errors))
238 table.add_row(
"Other Errors", str(results.other_errors))
240 if results.response_times:
241 table.add_row(
"Min Response Time", f
"{min(results.response_times):.1f} ms")
242 table.add_row(
"Max Response Time", f
"{max(results.response_times):.1f} ms")
245 f
"{sum(results.response_times) / len(results.response_times):.1f} ms",
252 (results.successful_requests / results.total_requests * 100)
253 if results.total_requests > 0
257 if success_rate >= 95:
259 "\n[green bold]✓ Phase 3 PASSED - Server handled stress test successfully (≥95% success)[/green bold]"
262 elif success_rate >= 80:
264 f
"\n[yellow bold]⚠ Phase 3 MARGINAL - Server mostly stable ({success_rate:.1f}% success, threshold: 95%)[/yellow bold]"
269 f
"\n[red bold]✗ Phase 3 FAILED - Server unstable under stress ({success_rate:.1f}% success)[/red bold]"
275 parser = argparse.ArgumentParser(
276 description=
"Stress test FastLED Network HTTP server"
278 parser.add_argument(
"--host", default=
"localhost", help=
"Server host")
279 parser.add_argument(
"--port", type=int, default=8080, help=
"Server port")
281 "--connections", type=int, default=20, help=
"Number of concurrent connections"
284 "--requests", type=int, default=50, help=
"Number of sequential requests"
286 args = parser.parse_args()
291 num_connections=args.connections,
292 num_requests=args.requests,
295 console.print(
"[bold]FastLED Network HTTP Server - Phase 3 Stress Test[/bold]")
296 console.print(f
"Server: http://{config.host}:{config.port}\n")
298 console.print(
"[cyan]Starting server...[/cyan]")
300 console.print(
"[green]✓ Server started and responding[/green]")
316 except KeyboardInterrupt:
317 console.print(
"\n[yellow]Test interrupted by user[/yellow]")
318 _thread.interrupt_main()
322 console.print(
"\n[cyan]Stopping server...[/cyan]")
323 server_proc.terminate()
325 server_proc.wait(timeout=5)
329 console.print(
"[green]✓ Server stopped[/green]")