forked from wallberg/umd-lib-opendata
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathtest_python_examples.py
More file actions
executable file
·305 lines (258 loc) · 9.61 KB
/
Copy pathtest_python_examples.py
File metadata and controls
executable file
·305 lines (258 loc) · 9.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
#!/usr/bin/env python3
"""
Test runner for Python code examples in static/code/
This script runs each Python example file (except drum-harvest.py) and validates:
- The script runs without errors
- The script produces output
- The script exits with code 0
Usage:
python test_python_examples.py
python test_python_examples.py --verbose
python test_python_examples.py --file drum-api.py
"""
import argparse
import subprocess
import sys
from pathlib import Path
from typing import Dict, List, Tuple
# Files to skip (e.g., too slow, interactive, or require special setup)
SKIP_FILES = {
'drum-harvest.py', # Harvests large amounts of data, not suitable for quick testing
}
# Files expected to fail (known issues)
EXPECTED_FAIL_FILES = {
'geoportal-search.py', # Service currently unavailable or endpoint changed
}
# Timeout in seconds for each script
TIMEOUT_SECONDS = 30
class TestResult:
"""Container for test results"""
def __init__(self, filename: str, success: bool, output: str, error: str,
return_code: int, skipped: bool = False, skip_reason: str = "",
expected_fail: bool = False):
self.filename = filename
self.success = success
self.output = output
self.error = error
self.return_code = return_code
self.skipped = skipped
self.skip_reason = skip_reason
self.expected_fail = expected_fail
def run_python_file(filepath: Path, timeout: int = TIMEOUT_SECONDS) -> TestResult:
"""
Run a single Python file and capture its output
Args:
filepath: Path to the Python file to run
timeout: Maximum time to wait for the script (seconds)
Returns:
TestResult object with execution details
"""
filename = filepath.name
try:
# Run the Python file
result = subprocess.run(
[sys.executable, str(filepath)],
capture_output=True,
text=True,
timeout=timeout,
cwd=filepath.parent
)
success = result.returncode == 0 and len(result.stdout) > 0
# Check for common error indicators in output
if success and any(indicator in result.stderr.lower() for indicator in
['error', 'exception', 'traceback']):
success = False
return TestResult(
filename=filename,
success=success,
output=result.stdout,
error=result.stderr,
return_code=result.returncode
)
except subprocess.TimeoutExpired:
return TestResult(
filename=filename,
success=False,
output="",
error=f"Script exceeded timeout of {timeout} seconds",
return_code=-1
)
except Exception as e:
return TestResult(
filename=filename,
success=False,
output="",
error=f"Exception running script: {str(e)}",
return_code=-1
)
def find_python_files(code_dir: Path, skip_files: set) -> List[Path]:
"""
Find all Python files in the code directory
Args:
code_dir: Directory containing Python files
skip_files: Set of filenames to skip
Returns:
List of Path objects for Python files to test
"""
all_files = sorted(code_dir.glob("*.py"))
return [f for f in all_files if f.name not in skip_files]
def print_result_summary(results: List[TestResult], verbose: bool = False) -> None:
"""
Print a summary of test results
Args:
results: List of TestResult objects
verbose: Whether to print detailed output
"""
print("\n" + "=" * 80)
print("TEST RESULTS SUMMARY")
print("=" * 80)
passed = sum(1 for r in results if r.success and not r.skipped and not r.expected_fail)
failed = sum(1 for r in results if not r.success and not r.skipped and not r.expected_fail)
expected_fail = sum(1 for r in results if r.expected_fail)
skipped = sum(1 for r in results if r.skipped)
total = len(results)
print(f"\nTotal: {total} | Passed: {passed} | Failed: {failed} | Expected Fail: {expected_fail} | Skipped: {skipped}")
# Print failed tests
if failed > 0:
print("\n" + "-" * 80)
print("FAILED TESTS:")
print("-" * 80)
for result in results:
if not result.success and not result.skipped:
print(f"\n❌ {result.filename}")
print(f" Return code: {result.return_code}")
if result.error:
print(f" Error output:")
for line in result.error.split('\n')[:10]: # First 10 lines
print(f" {line}")
if verbose and result.output:
print(f" Standard output:")
for line in result.output.split('\n')[:10]: # First 10 lines
print(f" {line}")
# Print passed tests
if passed > 0:
print("\n" + "-" * 80)
print("PASSED TESTS:")
print("-" * 80)
for result in results:
if result.success and not result.skipped:
output_lines = result.output.count('\n')
print(f"✅ {result.filename} ({output_lines} lines of output)")
if verbose and result.output:
print(f" Output preview:")
for line in result.output.split('\n')[:5]: # First 5 lines
print(f" {line}")
# Print expected failures
if expected_fail > 0:
print("\n" + "-" * 80)
print("EXPECTED FAILURES:")
print("-" * 80)
for result in results:
if result.expected_fail:
status = "✓" if not result.success else "⚠"
status_text = "Failed as expected" if not result.success else "Unexpectedly passed!"
print(f"{status} {result.filename} - {status_text}")
if result.error and verbose:
print(f" Error output:")
for line in result.error.split('\n')[:5]:
print(f" {line}")
# Print skipped tests
if skipped > 0:
print("\n" + "-" * 80)
print("SKIPPED TESTS:")
print("-" * 80)
for result in results:
if result.skipped:
print(f"⊘ {result.filename} - {result.skip_reason}")
print("\n" + "=" * 80)
def main():
"""Main test execution function"""
parser = argparse.ArgumentParser(
description='Test Python code examples in static/code/',
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'--verbose', '-v',
action='store_true',
help='Show detailed output from each test'
)
parser.add_argument(
'--file', '-f',
type=str,
help='Test only a specific file (e.g., drum-api.py)'
)
parser.add_argument(
'--timeout', '-t',
type=int,
default=TIMEOUT_SECONDS,
help=f'Timeout in seconds for each script (default: {TIMEOUT_SECONDS})'
)
parser.add_argument(
'--code-dir',
type=Path,
default=Path(__file__).parent / 'static' / 'code',
help='Path to code directory (default: static/code/)'
)
args = parser.parse_args()
# Validate code directory
if not args.code_dir.exists():
print(f"Error: Code directory not found: {args.code_dir}", file=sys.stderr)
sys.exit(1)
# Find Python files to test
if args.file:
# Test specific file
filepath = args.code_dir / args.file
if not filepath.exists():
print(f"Error: File not found: {filepath}", file=sys.stderr)
sys.exit(1)
python_files = [filepath]
else:
# Test all files except skipped ones
python_files = find_python_files(args.code_dir, SKIP_FILES)
if not python_files:
print("No Python files found to test", file=sys.stderr)
sys.exit(1)
print(f"Testing {len(python_files)} Python files from {args.code_dir}")
print(f"Timeout: {args.timeout} seconds per script")
if SKIP_FILES and not args.file:
print(f"Skipping: {', '.join(sorted(SKIP_FILES))}")
print()
# Run tests
results = []
for i, filepath in enumerate(python_files, 1):
print(f"[{i}/{len(python_files)}] Testing {filepath.name}...", end=' ', flush=True)
if filepath.name in SKIP_FILES:
result = TestResult(
filename=filepath.name,
success=False,
output="",
error="",
return_code=0,
skipped=True,
skip_reason="Excluded from testing"
)
else:
result = run_python_file(filepath, timeout=args.timeout)
# Mark as expected failure if in EXPECTED_FAIL_FILES
if filepath.name in EXPECTED_FAIL_FILES:
result.expected_fail = True
results.append(result)
if result.skipped:
print("SKIPPED")
elif result.expected_fail:
if result.success:
print("EXPECTED FAIL (but passed!)")
else:
print("EXPECTED FAIL")
elif result.success:
print("PASSED")
else:
print("FAILED")
# Print summary
print_result_summary(results, verbose=args.verbose)
# Exit with appropriate code
# Only count unexpected failures (not expected failures or skipped tests)
failed_count = sum(1 for r in results if not r.success and not r.skipped and not r.expected_fail)
sys.exit(0 if failed_count == 0 else 1)
if __name__ == '__main__':
main()