Skip to content

Commit 81cfa0e

Browse files
fix: clean up test runner output with summary table (#32)
Co-authored-by: Shubhank <72601061+Sagsgit@users.noreply.github.com>
1 parent 241aae9 commit 81cfa0e

1 file changed

Lines changed: 109 additions & 38 deletions

File tree

scripts/run_tests.py

Lines changed: 109 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
python scripts/run_tests.py
2323
"""
2424

25+
import re
2526
import subprocess
2627
import sys
2728
from pathlib import Path
@@ -74,45 +75,115 @@ def get_integration_dirs(args: list[str]) -> list[Path]:
7475
)
7576

7677

77-
def install_integration_deps(integration_dir: Path) -> bool:
78-
"""Install an integration's requirements.txt. Returns True on success."""
78+
def install_integration_deps(integration_dir: Path) -> tuple[bool, str]:
79+
"""Install an integration's requirements.txt. Returns (success, error_output)."""
7980
req_file = integration_dir / "requirements.txt"
8081
if not req_file.is_file():
81-
return True
82+
return True, ""
8283

8384
result = subprocess.run(
8485
[sys.executable, "-m", "pip", "install", "-r", str(req_file), "-q"],
8586
capture_output=True,
8687
text=True,
8788
)
8889
if result.returncode != 0:
89-
print(f" ❌ Failed to install dependencies for {integration_dir.name}")
90-
if result.stderr.strip():
91-
for line in result.stderr.strip().splitlines():
92-
print(f" {line}")
93-
return False
94-
return True
90+
return False, result.stderr.strip()
91+
return True, ""
9592

9693

97-
def run_integration_tests(integration_dir: Path, test_files: list[Path]) -> int:
98-
"""Run pytest for a single integration. Returns the pytest exit code."""
94+
def run_integration_tests(integration_dir: Path, test_files: list[Path]) -> tuple[int, str]:
95+
"""Run pytest for a single integration. Returns (exit_code, output)."""
9996
cmd = [
10097
sys.executable,
10198
"-m",
10299
"pytest",
103100
"--import-mode=importlib",
104101
"-m",
105102
"unit",
106-
"-v",
107103
"--tb=short",
108104
"--no-header",
105+
"-q",
109106
"--cov",
110107
str(integration_dir),
111108
"--cov-report=term-missing:skip-covered",
112109
*[str(f) for f in test_files],
113110
]
114111

115-
return subprocess.run(cmd).returncode
112+
result = subprocess.run(cmd, capture_output=True, text=True)
113+
return result.returncode, result.stdout + result.stderr
114+
115+
116+
def parse_results(output: str) -> tuple[int, int, str]:
117+
"""Parse pytest output for passed count, failed count, and coverage."""
118+
passed = failed = 0
119+
coverage = "n/a"
120+
121+
# Match summary line e.g. "37 passed" or "2 failed, 35 passed"
122+
summary = re.search(r"(\d+) passed", output)
123+
if summary:
124+
passed = int(summary.group(1))
125+
fail_match = re.search(r"(\d+) failed", output)
126+
if fail_match:
127+
failed = int(fail_match.group(1))
128+
129+
# Match coverage total e.g. "TOTAL 166 13 92%"
130+
cov_match = re.search(r"^TOTAL\s+\d+\s+\d+\s+(\d+%)", output, re.MULTILINE)
131+
if cov_match:
132+
coverage = cov_match.group(1)
133+
134+
return passed, failed, coverage
135+
136+
137+
def print_table(rows: list[tuple[str, str, str, str, str]], failed_outputs: dict[str, str]) -> None:
138+
"""Print a formatted results table."""
139+
col_widths = [
140+
max(len(r[0]) for r in rows) + 2,
141+
8, # Tests
142+
10, # Coverage
143+
14, # Status
144+
]
145+
146+
header = (
147+
f"{'Integration':<{col_widths[0]}}"
148+
f"{'Tests':>{col_widths[1]}}"
149+
f"{'Coverage':>{col_widths[2]}}"
150+
f"{'Status':>{col_widths[3]}}"
151+
)
152+
divider = "-" * len(header)
153+
154+
print()
155+
print(header)
156+
print(divider)
157+
for name, tests, coverage, status, _ in rows:
158+
print(
159+
f"{name:<{col_widths[0]}}"
160+
f"{tests:>{col_widths[1]}}"
161+
f"{coverage:>{col_widths[2]}}"
162+
f"{status:>{col_widths[3]}}"
163+
)
164+
print(divider)
165+
166+
# Total row
167+
total_passed = sum(int(r[1].split("/")[0]) for r in rows if "/" in r[1])
168+
total_total = sum(int(r[1].split("/")[1]) for r in rows if "/" in r[1])
169+
all_passed = all(r[4] == "pass" for r in rows)
170+
total_status = "✅ All passed" if all_passed else "❌ Some failed"
171+
print(
172+
f"{'Total':<{col_widths[0]}}"
173+
f"{f'{total_passed}/{total_total}':>{col_widths[1]}}"
174+
f"{'':>{col_widths[2]}}"
175+
f"{total_status:>{col_widths[3]}}"
176+
)
177+
print()
178+
179+
# Print full output only for failures
180+
for name, _, _, _, outcome in rows:
181+
if outcome == "fail" and name in failed_outputs:
182+
print(f"{'=' * 60}")
183+
print(f" {name} — failure detail")
184+
print(f"{'=' * 60}")
185+
print(failed_outputs[name])
186+
print()
116187

117188

118189
def main() -> int:
@@ -140,37 +211,37 @@ def main() -> int:
140211
print("⚠️ No unit tests to run")
141212
return 0
142213

143-
print(f"🧪 Running unit tests for: {', '.join(d.name for d, _ in testable)}")
144-
print()
145-
146214
# Run each integration separately with its own dependencies
147-
failed = []
148-
passed = []
149-
for d, test_files in testable:
150-
print(f"{'=' * 60}")
151-
print(f" {d.name}")
152-
print(f"{'=' * 60}")
215+
rows = []
216+
failed_outputs = {}
153217

154-
if not install_integration_deps(d):
155-
failed.append(d.name)
218+
for d, test_files in testable:
219+
ok, err = install_integration_deps(d)
220+
if not ok:
221+
rows.append((d.name, "n/a", "n/a", "❌ Dep install failed", "fail"))
222+
failed_outputs[d.name] = err
156223
continue
157224

158-
exit_code = run_integration_tests(d, test_files)
225+
exit_code, output = run_integration_tests(d, test_files)
226+
passed, failed, coverage = parse_results(output)
227+
total = passed + failed
228+
tests_str = f"{passed}/{total}"
229+
159230
if exit_code == 0:
160-
passed.append(d.name)
231+
rows.append((d.name, tests_str, coverage, "✅ Passed", "pass"))
161232
else:
162-
failed.append(d.name)
163-
print()
164-
165-
# Summary
166-
print("=" * 60)
167-
if passed:
168-
print(f"✅ Tests passed: {', '.join(passed)}")
169-
if failed:
170-
print(f"❌ Tests failed: {', '.join(failed)}")
171-
print("=" * 60)
172-
173-
return 1 if failed else 0
233+
rows.append((d.name, tests_str, coverage, "❌ Failed", "fail"))
234+
failed_outputs[d.name] = output
235+
236+
print_table(rows, failed_outputs)
237+
238+
any_failed = any(r[4] == "fail" for r in rows)
239+
if any_failed:
240+
print(f"❌ Tests failed: {', '.join(r[0] for r in rows if r[4] == 'fail')}")
241+
else:
242+
print(f"✅ Tests passed: {', '.join(r[0] for r in rows if r[4] == 'pass')}")
243+
244+
return 1 if any_failed else 0
174245

175246

176247
if __name__ == "__main__":

0 commit comments

Comments
 (0)