feat(certify): enhance test detection, running, and CI config checks
- Add language detection to condition test existence checks - Improve C/C++ test running: try make test/check, fallback to ctest - Run Python tests only if tests are detected to avoid false failures - Update no-test-runner message for clarity - Add support for Gitea workflows in CI config detection - chore: ignore .venv in .gitignore
This commit is contained in:
parent
26b9299d44
commit
7c5edce532
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.idea/
|
||||
.venv/
|
||||
poetry.lock
|
||||
**/__pycache__/
|
||||
|
||||
@ -295,11 +295,13 @@ def _is_visible(f: Path) -> bool:
|
||||
|
||||
|
||||
def _check_tests_exist(path: Path) -> CheckResult:
|
||||
langs = _detect_languages(path)
|
||||
|
||||
def rglob_visible(pattern: str) -> list[Path]:
|
||||
return [f for f in path.rglob(pattern) if _is_visible(f)]
|
||||
|
||||
# Python
|
||||
python_test = (
|
||||
python_test = "python" in langs and (
|
||||
(path / "tests").is_dir()
|
||||
or (path / "test").is_dir()
|
||||
or bool(rglob_visible("test_*.py"))
|
||||
@ -307,7 +309,7 @@ def _check_tests_exist(path: Path) -> CheckResult:
|
||||
or bool(rglob_visible("tests.py"))
|
||||
)
|
||||
# JavaScript / TypeScript
|
||||
js_test = (
|
||||
js_test = ("javascript" in langs or "typescript" in langs) and (
|
||||
any(
|
||||
f for f in path.rglob("*")
|
||||
if f.is_file() and _is_visible(f)
|
||||
@ -320,31 +322,34 @@ def _check_tests_exist(path: Path) -> CheckResult:
|
||||
)
|
||||
)
|
||||
# Go
|
||||
go_test = bool(rglob_visible("*_test.go"))
|
||||
go_test = "go" in langs and bool(rglob_visible("*_test.go"))
|
||||
# Rust — tests/ subdir or inline test modules
|
||||
rust_test = bool(
|
||||
rust_test = "rust" in langs and (
|
||||
any(
|
||||
f for f in path.rglob("*.rs")
|
||||
if f.is_file() and _is_visible(f) and "tests" in f.parts
|
||||
) or (
|
||||
)
|
||||
or (
|
||||
(path / "tests").is_dir()
|
||||
and any((path / "tests").rglob("*.rs"))
|
||||
)
|
||||
)
|
||||
# Java
|
||||
java_test = (
|
||||
java_test = "java" in langs and (
|
||||
(path / "src" / "test").exists()
|
||||
or bool(rglob_visible("*Test.java"))
|
||||
or bool(rglob_visible("*Tests.java"))
|
||||
or bool(rglob_visible("*Spec.java"))
|
||||
)
|
||||
# Ruby
|
||||
ruby_test = (
|
||||
ruby_test = "ruby" in langs and (
|
||||
(path / "spec").is_dir()
|
||||
or bool(rglob_visible("*_spec.rb"))
|
||||
or bool(rglob_visible("*_test.rb"))
|
||||
)
|
||||
# C / C++ — Makefile with a 'test' target, or conventional test file patterns
|
||||
_makefiles = [path / name for name in ("Makefile", "GNUmakefile", "makefile")]
|
||||
c_test = (
|
||||
c_test = ("c" in langs or "cpp" in langs) and (
|
||||
any(m.exists() for m in _makefiles)
|
||||
and (
|
||||
bool(rglob_visible("test_*.c"))
|
||||
@ -463,28 +468,65 @@ def _check_tests_pass(path: Path) -> CheckResult:
|
||||
except subprocess.TimeoutExpired:
|
||||
failed_runners.append("Gradle: timed out")
|
||||
|
||||
# --- C / C++ (make test) ---
|
||||
# --- C / C++ (make test → make check → ctest) ---
|
||||
_makefiles = [path / name for name in ("Makefile", "GNUmakefile", "makefile")]
|
||||
_langs = _detect_languages(path)
|
||||
if ("c" in _langs or "cpp" in _langs) and any(m.exists() for m in _makefiles):
|
||||
if "c" in _langs or "cpp" in _langs:
|
||||
_c_ran = False
|
||||
# Try make targets first
|
||||
if any(m.exists() for m in _makefiles):
|
||||
for make_target in ("test", "check"):
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["make", "test"],
|
||||
["make", make_target],
|
||||
cwd=path, capture_output=True, text=True, timeout=120,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
passed_runners.append("C/C++ (make test)")
|
||||
passed_runners.append(f"C/C++ (make {make_target})")
|
||||
_c_ran = True
|
||||
break
|
||||
elif "No rule to make target" in (r.stdout + r.stderr):
|
||||
pass # no test target — skip gracefully
|
||||
continue # target not found — try next
|
||||
else:
|
||||
snippet = (r.stdout + r.stderr).strip().split("\n")[-1]
|
||||
failed_runners.append(f"C/C++: {snippet}")
|
||||
_c_ran = True
|
||||
break
|
||||
except FileNotFoundError:
|
||||
break # make not installed — skip
|
||||
except subprocess.TimeoutExpired:
|
||||
failed_runners.append(f"C/C++: make {make_target} timed out")
|
||||
_c_ran = True
|
||||
break
|
||||
# Fall back to ctest (CMake projects)
|
||||
if not _c_ran and (path / "CMakeLists.txt").exists():
|
||||
try:
|
||||
r = subprocess.run(
|
||||
["ctest", "--test-dir", "build", "--output-on-failure"],
|
||||
cwd=path, capture_output=True, text=True, timeout=120,
|
||||
)
|
||||
output = r.stdout + r.stderr
|
||||
if "No tests were found" in output:
|
||||
pass # ctest found nothing — skip
|
||||
elif r.returncode == 0:
|
||||
passed_runners.append("C/C++ (ctest)")
|
||||
else:
|
||||
snippet = (r.stdout + r.stderr).strip().split("\n")[-1]
|
||||
failed_runners.append(f"C/C++: {snippet}")
|
||||
except FileNotFoundError:
|
||||
pass # make not installed — skip
|
||||
pass # ctest not installed — skip
|
||||
except subprocess.TimeoutExpired:
|
||||
failed_runners.append("C/C++: make test timed out")
|
||||
failed_runners.append("C/C++: ctest timed out")
|
||||
|
||||
# --- Python (pytest, then unittest) ---
|
||||
_py_has_tests = "python" in _langs and (
|
||||
(path / "tests").is_dir()
|
||||
or (path / "test").is_dir()
|
||||
or any(f for f in path.rglob("test_*.py") if _is_visible(f))
|
||||
or any(f for f in path.rglob("*_test.py") if _is_visible(f))
|
||||
or any(f for f in path.rglob("tests.py") if _is_visible(f))
|
||||
)
|
||||
if _py_has_tests:
|
||||
for cmd in (
|
||||
["python", "-m", "pytest", "-x", "-q"],
|
||||
["python", "-m", "unittest", "discover", "-s", "."],
|
||||
@ -523,7 +565,7 @@ def _check_tests_pass(path: Path) -> CheckResult:
|
||||
return CheckResult(
|
||||
name="Tests pass",
|
||||
passed=False,
|
||||
message="Could not find a test runner. Neither pytest nor unittest obliged.",
|
||||
message="No test runner found. If tests exist, they're keeping a low profile.",
|
||||
level=Level.SILVER,
|
||||
)
|
||||
|
||||
@ -583,7 +625,14 @@ def _check_ci_config_exists(path: Path) -> CheckResult:
|
||||
path / ".travis.yml",
|
||||
path / "azure-pipelines.yml",
|
||||
]
|
||||
found = any(p.exists() for p in ci_paths)
|
||||
gitea_workflows = path / ".gitea" / "workflows"
|
||||
found = (
|
||||
any(p.exists() for p in ci_paths)
|
||||
or (
|
||||
gitea_workflows.is_dir()
|
||||
and any(gitea_workflows.glob("*.y*ml"))
|
||||
)
|
||||
)
|
||||
return CheckResult(
|
||||
name="CI configuration exists",
|
||||
passed=found,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user