-
Notifications
You must be signed in to change notification settings - Fork 31
Expand file tree
/
Copy pathscore.py
More file actions
110 lines (89 loc) · 3.25 KB
/
score.py
File metadata and controls
110 lines (89 loc) · 3.25 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
from __future__ import annotations
import argparse
from dataclasses import dataclass
import io
import os
from pathlib import Path
import sys
import unittest
ROOT = Path(__file__).resolve().parent
@dataclass
class SuiteSummary:
label: str
total: int
passed: int
failures: int
errors: int
skipped: int
@property
def successful(self) -> bool:
return self.failures == 0 and self.errors == 0
def discover(relative_path: str) -> unittest.TestSuite:
loader = unittest.TestLoader()
return loader.discover(
start_dir=str(ROOT / relative_path),
pattern="test_*.py",
top_level_dir=str(ROOT),
)
def summarize(label: str, result: unittest.TestResult) -> SuiteSummary:
failures = len(result.failures) + len(getattr(result, "unexpectedSuccesses", []))
errors = len(result.errors)
skipped = len(getattr(result, "skipped", [])) + len(getattr(result, "expectedFailures", []))
passed = max(0, result.testsRun - failures - errors - skipped)
return SuiteSummary(
label=label,
total=result.testsRun,
passed=passed,
failures=failures,
errors=errors,
skipped=skipped,
)
def run_suite(label: str, relative_path: str, *, verbosity: int) -> SuiteSummary:
print(f"\n== {label} ==")
suite = discover(relative_path)
stream = sys.stdout if verbosity > 1 else io.StringIO()
runner = unittest.TextTestRunner(stream=stream, verbosity=verbosity)
result = runner.run(suite)
summary = summarize(label, result)
print(
f"{summary.label}: {summary.passed}/{summary.total} passed "
f"({summary.failures} failures, {summary.errors} errors, {summary.skipped} skipped)"
)
if verbosity == 1 and not summary.successful:
print("Run with --verbose to see full failure details.")
return summary
def main() -> int:
parser = argparse.ArgumentParser(description="Run the shared buffer score harness.")
parser.add_argument("--module", default="solution", help="Module name to score. Defaults to solution.")
parser.add_argument(
"--include-applicant-tests",
dest="include_applicant_tests",
action="store_true",
help="Also run tests under tests/applicant.",
)
parser.add_argument(
"--strict",
action="store_true",
help="Exit non-zero when any official test fails.",
)
parser.add_argument("--verbose", action="store_true", help="Use verbose unittest output.")
args = parser.parse_args()
os.environ["SHARED_BUFFER_MODULE"] = args.module
verbosity = 2 if args.verbose else 1
print(f"Scoring module: {args.module}")
official = run_suite("Official Tests", "tests/official", verbosity=verbosity)
applicant = None
if args.include_applicant_tests:
applicant = run_suite("Applicant Tests", "tests/applicant", verbosity=verbosity)
if applicant is None:
print(f"\nCurrent official score: {official.passed}/{official.total}")
else:
print(
f"\nCurrent score: {official.passed}/{official.total} official, "
f"{applicant.passed}/{applicant.total} applicant"
)
if args.strict and not official.successful:
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())