FastLED 3.9.15
Loading...
Searching...
No Matches
test_client.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""
3HTTP Server Test Client for FastLED Asio Server Example
4
5Tests the Server.ino HTTP server using httpx.
6
7Usage:
8 uv run python examples/Asio/Server/test_client.py
9 uv run python examples/Asio/Server/test_client.py --host 192.168.1.100 --port 80
10 uv run pytest examples/Asio/Server/test_client.py
11"""
12
13import _thread
14import argparse
15import sys
16import time
17from dataclasses import dataclass
18
19import httpx
20from rich.console import Console
21from rich.table import Table
22
23
24console = Console()
25
26
27@dataclass
29 host: str = "localhost"
30 port: int = 8080
31 num_requests: int = 5
32 max_retries: int = 10
33 retry_delay: float = 0.5
34 timeout: float = 5.0
35
36
37@dataclass
39 success: bool
40 status_code: int | None
41 response_text: str | None
42 response_time_ms: float
43 error_message: str | None = None
44
45
46def wait_for_server(config: TestConfig) -> bool:
47 """Wait for server to become available with retry logic."""
48 url = f"http://{config.host}:{config.port}/ping"
49
50 console.print(f"Waiting for server at {url}...")
51
52 for attempt in range(1, config.max_retries + 1):
53 try:
54 with httpx.Client(timeout=config.timeout) as client:
55 response = client.get(url)
56 if response.status_code == 200:
57 console.print("✓ Server is ready!", style="green")
58 return True
59 except (httpx.ConnectError, httpx.TimeoutException):
60 delay = config.retry_delay * (2 ** (attempt - 1))
61 console.print(
62 f" Attempt {attempt}/{config.max_retries}: Not ready (retry in {delay:.1f}s)"
63 )
64 if attempt < config.max_retries:
65 time.sleep(delay)
66 except KeyboardInterrupt:
67 console.print("\nInterrupted by user", style="yellow")
68 _thread.interrupt_main()
69 raise
70
71 console.print("✗ Server not reachable", style="red")
72 return False
73
74
75def send_request(client: httpx.Client, url: str, config: TestConfig) -> RequestResult:
76 """Send HTTP GET request and measure response time."""
77 try:
78 start_time = time.perf_counter()
79 response = client.get(url, timeout=config.timeout)
80 elapsed_ms = (time.perf_counter() - start_time) * 1000
81
82 return RequestResult(
83 success=response.status_code == 200,
84 status_code=response.status_code,
85 response_text=response.text.strip(),
86 response_time_ms=elapsed_ms,
87 )
88 except httpx.ConnectError:
89 return RequestResult(False, None, None, 0.0, "Connection refused")
90 except httpx.TimeoutException:
91 return RequestResult(False, None, None, 0.0, "Request timeout")
92 except KeyboardInterrupt:
93 _thread.interrupt_main()
94 raise
95 except Exception as e:
96 return RequestResult(False, None, None, 0.0, str(e))
97
98
99def run_tests(config: TestConfig) -> list[RequestResult]:
100 """Run test sequence with multiple requests."""
101 url = f"http://{config.host}:{config.port}/"
102 results: list[RequestResult] = []
103
104 console.print(f"\nSending {config.num_requests} requests to {url}...\n")
105
106 with httpx.Client() as client:
107 for i in range(1, config.num_requests + 1):
108 result = send_request(client, url, config)
109 results.append(result)
110
111 if result.success:
112 console.print(
113 f"Request {i}: ✓ {result.status_code} ({result.response_time_ms:.1f} ms)",
114 style="green",
115 )
116 console.print(f" Response: {result.response_text!r}")
117 else:
118 console.print(f"Request {i}: ✗ {result.error_message}", style="red")
119
120 time.sleep(0.1) # Small delay between requests
121
122 return results
123
124
125def display_statistics(results: list[RequestResult]) -> None:
126 """Display test statistics."""
127 successful = [r for r in results if r.success]
128 failed = [r for r in results if not r.success]
129
130 if not successful:
131 console.print("\n✗ All requests failed", style="red")
132 return
133
134 times = [r.response_time_ms for r in successful]
135
136 table = Table(title="Test Statistics")
137 table.add_column("Metric", style="cyan")
138 table.add_column("Value", style="magenta")
139
140 table.add_row("Total Requests", str(len(results)))
141 table.add_row(
142 "Successful", f"{len(successful)} ({len(successful) / len(results) * 100:.0f}%)"
143 )
144 table.add_row("Failed", f"{len(failed)} ({len(failed) / len(results) * 100:.0f}%)")
145 table.add_row("Min Response Time", f"{min(times):.1f} ms")
146 table.add_row("Max Response Time", f"{max(times):.1f} ms")
147 table.add_row("Avg Response Time", f"{sum(times) / len(times):.1f} ms")
148
149 console.print()
150 console.print(table)
151
152 if len(successful) == len(results):
153 console.print("\n✓ All tests passed!", style="green bold")
154 else:
155 console.print(f"\n⚠ {len(failed)} test(s) failed", style="yellow bold")
156
157
158def main() -> int:
159 """Main entry point."""
160 parser = argparse.ArgumentParser(description="Test FastLED Network example")
161 parser.add_argument("--host", default="localhost", help="Server host")
162 parser.add_argument("--port", type=int, default=8080, help="Server port")
163 parser.add_argument("--requests", type=int, default=5, help="Number of requests")
164 args = parser.parse_args()
165
166 config = TestConfig(host=args.host, port=args.port, num_requests=args.requests)
167
168 console.print("[bold]FastLED Network Example Test Client[/bold]")
169 console.print(f"Server: http://{config.host}:{config.port}\n")
170
171 # Wait for server
172 if not wait_for_server(config):
173 console.print("\nERROR: Server not available", style="red bold")
174 console.print("\nTroubleshooting:")
175 console.print("1. Compile: bash compile posix --examples Server")
176 console.print("2. Run: .build/meson-quick/examples/Server.exe")
177 return 1
178
179 # Run tests
180 results = run_tests(config)
181
182 # Display statistics
183 display_statistics(results)
184
185 return 0 if all(r.success for r in results) else 1
186
187
189 """Pytest test for Network example."""
190 config = TestConfig(num_requests=3, max_retries=5)
191
192 if not wait_for_server(config):
193 raise RuntimeError("Server not available")
194
195 url = f"http://{config.host}:{config.port}/"
196 with httpx.Client() as client:
197 result = send_request(client, url, config)
198 assert result.success, f"Request failed: {result.error_message}"
199 assert result.status_code == 200
200 assert result.response_time_ms < 1000
201
202
203if __name__ == "__main__":
204 try:
205 sys.exit(main())
206 except KeyboardInterrupt:
207 console.print("\nTest interrupted", style="yellow")
208 _thread.interrupt_main()
209 sys.exit(130)
bool wait_for_server(TestConfig config)
list[RequestResult] run_tests(TestConfig config)
None test_network_example()
None display_statistics(list[RequestResult] results)
RequestResult send_request(httpx.Client client, str url, TestConfig config)