Skip to content

Commit 0b7465c

Browse files
committed
Apply hardening to runner
1 parent 839ff0c commit 0b7465c

2 files changed

Lines changed: 39 additions & 27 deletions

File tree

pre-commit.sh

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,6 @@ fi
4747

4848
# --- Use the specific Python executable for all commands ---
4949

50-
# Run all examples
51-
"$PYTHON_EXEC" runner.py
52-
5350
# Lint and format checks (ruff and isort are installed in the venv)
5451
"$PYTHON_EXEC" -m ruff check
5552
"$PYTHON_EXEC" -m isort --check --diff .

runner.py

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from importlib import import_module
23
from inspect import isfunction, signature
34
from pkgutil import walk_packages
@@ -7,51 +8,65 @@
78

89
# Module-level constants
910
_STYLE_SUCCESS = "\033[92m"
11+
_STYLE_FAILURE = "\033[91m"
1012
_STYLE_BOLD = "\033[1m"
1113
_STYLE_END = "\033[0m"
1214
_RUNNER_PROGRESS = "->"
1315
_RUNNER_MAIN = "main"
1416

1517

16-
def success_text(text: str) -> str:
17-
"""Get success text."""
18-
return f"{_STYLE_SUCCESS}{bold_text(text)}{_STYLE_END}"
18+
def style_text(text: str, color: str = "") -> str:
19+
"""Get styled text."""
20+
return f"{color}{_STYLE_BOLD}{text}{_STYLE_END}"
1921

2022

21-
def bold_text(text: str) -> str:
22-
"""Get bold text."""
23-
return f"{_STYLE_BOLD}{text}{_STYLE_END}"
23+
def main() -> None:
24+
# Get filter from command line arguments
25+
filter_str = sys.argv[1] if len(sys.argv) > 1 else None
2426

27+
print(style_text(f"Start {root_name} runner"))
2528

26-
def main() -> None:
27-
print(bold_text(f"Start {root_name} runner"))
29+
stats = {"passed": 0, "failed": 0, "skipped": 0}
2830

2931
for item in walk_packages(root_path, f"{root_name}."):
32+
# Skip packages (folders), only run modules (files)
33+
if item.ispkg:
34+
continue
35+
36+
# Filter modules based on command line argument
37+
if filter_str and filter_str not in item.name:
38+
continue
39+
3040
try:
3141
mod = import_module(item.name)
3242
except (ImportError, SyntaxError) as e:
3343
print(f"{_RUNNER_PROGRESS} Skip {item.name}: {e}")
44+
stats["skipped"] += 1
3445
continue
3546

36-
# Skip modules without a main object
37-
if not hasattr(mod, _RUNNER_MAIN):
47+
# Skip modules without a valid main object
48+
mod_main = getattr(mod, _RUNNER_MAIN, None)
49+
if not isfunction(mod_main) or len(signature(mod_main).parameters) != 0:
50+
print(f"{_RUNNER_PROGRESS} Skip {item.name}: No valid {_RUNNER_MAIN}() function")
51+
stats["skipped"] += 1
3852
continue
3953

40-
# By this point, there is a main object in the module
41-
mod_main = getattr(mod, _RUNNER_MAIN)
42-
43-
# The main object is a function
44-
assert isfunction(mod_main)
45-
46-
# The main function has zero parameters
47-
assert len(signature(mod_main).parameters) == 0
48-
49-
# The main function should not throw any errors
54+
# Execution phase
5055
print(f"{_RUNNER_PROGRESS} Run {mod.__name__}:{_RUNNER_MAIN}", end="")
51-
mod_main()
52-
print(" [PASS]")
53-
54-
print(success_text(f"Finish {root_name} runner"))
56+
try:
57+
mod_main()
58+
print(style_text(" [PASS]", _STYLE_SUCCESS))
59+
stats["passed"] += 1
60+
except Exception as e:
61+
print(style_text(" [FAIL]", _STYLE_FAILURE))
62+
print(f" Error in {item.name}: {e}")
63+
stats["failed"] += 1
64+
65+
# Summary report
66+
print("\n" + "=" * 30)
67+
print(style_text(f"Finish {root_name} runner", _STYLE_SUCCESS))
68+
print(f"Passed: {stats['passed']} | Failed: {stats['failed']} | Skipped: {stats['skipped']}")
69+
print("=" * 30)
5570

5671

5772
if __name__ == "__main__":

0 commit comments

Comments
 (0)