Repository navigation
sync: mirror from thyn-ai/codna@3177a0b861e68aede678cf2c28e8697690cf3855 #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -162,6 +162,7 @@ def _detect_pytest_suites(repo_dir: str) -> list[str]: | |||||||||||||||||||||
| continue | ||||||||||||||||||||||
| if _has_pytest_config(sub): | ||||||||||||||||||||||
| suites.append(sub) | ||||||||||||||||||||||
| continue | ||||||||||||||||||||||
| if name not in _CONTAINER_DIRS: | ||||||||||||||||||||||
| continue | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
|
|
@@ -344,17 +345,19 @@ def discover_failing_tests(repo_dir: str, test_cmd: str | None = None, *, timeou | |||||||||||||||||||||
| "budget. Point Codna at the right one with fix.test_command in codna.yaml or " | ||||||||||||||||||||||
| "--test-cmd \"<your test command>\" (writing JUnit to $CODNA_JUNIT)." | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| # Each suite runs from its own root so its pyproject/pytest.ini applies. Distribute the | ||||||||||||||||||||||
| # caller's total integer-second budget as evenly as possible without exceeding it. | ||||||||||||||||||||||
| base_budget, extra_seconds = divmod(timeout_seconds, len(suites)) | ||||||||||||||||||||||
| # Each suite runs from its own root so its pyproject/pytest.ini applies; the overall budget | ||||||||||||||||||||||
| # is split evenly (deterministic). The 60s floor is safe: _MAX_SUITES caps the count, so | ||||||||||||||||||||||
| # floor * suites can never exceed 8*60s = 480s even when timeout_seconds is small. | ||||||||||||||||||||||
| per_suite = max(60, timeout_seconds // len(suites)) | ||||||||||||||||||||||
| runs = [] | ||||||||||||||||||||||
| for i, suite in enumerate(suites): | ||||||||||||||||||||||
| suite_junit = junit if len(suites) == 1 else f"{os.path.splitext(junit)[0]}-{i}.xml" | ||||||||||||||||||||||
| argv = [sys.executable, "-m", "pytest", "-q"] | ||||||||||||||||||||||
| # A container suite must not recursively collect nested roots that run independently. | ||||||||||||||||||||||
| argv.extend(f"--ignore={candidate}" for candidate in suites if os.path.dirname(candidate) == suite) | ||||||||||||||||||||||
| argv.append(f"--junitxml={suite_junit}") | ||||||||||||||||||||||
| runs.append((suite, argv, suite_junit, base_budget + (1 if i < extra_seconds else 0))) | ||||||||||||||||||||||
| runs.append(( | ||||||||||||||||||||||
| suite, | ||||||||||||||||||||||
| [sys.executable, "-m", "pytest", "-q", f"--junitxml={suite_junit}"], | ||||||||||||||||||||||
| suite_junit, | ||||||||||||||||||||||
| per_suite, | ||||||||||||||||||||||
| )) | ||||||||||||||||||||||
|
Comment on lines
+357
to
+360
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 HIGH · correctness — Removed --ignore flags cause nested suites to be double-collected The diff deletes the
Suggested change
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| # Actually ENFORCE network denial (bwrap/unshare on Linux CI) — not just record it — so untrusted | ||||||||||||||||||||||
| # test code can't exfiltrate. When fail-closed egress is required (privacy.egress) but no kernel | ||||||||||||||||||||||
|
|
@@ -368,15 +371,14 @@ def discover_failing_tests(repo_dir: str, test_cmd: str | None = None, *, timeou | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| failing: list[str] = [] | ||||||||||||||||||||||
| per_suite_failing: list[tuple[str, list[str]]] = [] | ||||||||||||||||||||||
| crashed: list[tuple[str, bool]] = [] | ||||||||||||||||||||||
| crashed: list[str] = [] | ||||||||||||||||||||||
| saw_failure_rc = False | ||||||||||||||||||||||
| last_tail = "" | ||||||||||||||||||||||
| for run_dir, argv, run_junit, budget in runs: | ||||||||||||||||||||||
| run_env = {**env, "CODNA_JUNIT": run_junit} | ||||||||||||||||||||||
| # pytest exits 1 when tests fail — that is the expected/accepted case here (not an error). | ||||||||||||||||||||||
| result = Sandbox( | ||||||||||||||||||||||
| network="deny", timeout_seconds=budget, env=run_env, network_backend=backend | ||||||||||||||||||||||
| ).run(argv, cwd=run_dir, accepted_exit_codes=(0, 1)) | ||||||||||||||||||||||
| result = Sandbox(network="deny", timeout_seconds=budget, env=env, network_backend=backend).run( | ||||||||||||||||||||||
| argv, cwd=run_dir, accepted_exit_codes=(0, 1) | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| if result.timed_out: | ||||||||||||||||||||||
| raise RuntimeError(f"test run timed out after {budget}s") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -413,7 +415,7 @@ def discover_failing_tests(repo_dir: str, test_cmd: str | None = None, *, timeou | |||||||||||||||||||||
| failing.extend(suite_failing) | ||||||||||||||||||||||
| elif result.returncode not in (0,): | ||||||||||||||||||||||
| saw_failure_rc = True | ||||||||||||||||||||||
| crashed.append((run_dir, os.path.isfile(run_junit))) | ||||||||||||||||||||||
| crashed.append(run_dir) | ||||||||||||||||||||||
| last_tail = (result.stdout or result.stderr or "")[-1500:] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if failing: | ||||||||||||||||||||||
|
|
@@ -425,12 +427,8 @@ def discover_failing_tests(repo_dir: str, test_cmd: str | None = None, *, timeou | |||||||||||||||||||||
| ] | ||||||||||||||||||||||
| # A suite that crashed without producing JUnit ids is never dropped from the report just | ||||||||||||||||||||||
| # because a sibling suite produced some — its tail is real failure evidence too. | ||||||||||||||||||||||
| for suite, has_report in crashed: | ||||||||||||||||||||||
| message = ( | ||||||||||||||||||||||
| "exited non-zero with JUnit report but no failing test ids" | ||||||||||||||||||||||
| if has_report else "exited non-zero with no JUnit report" | ||||||||||||||||||||||
| ) | ||||||||||||||||||||||
| parts.append(f"{os.path.relpath(suite, repo_dir)}: {message}") | ||||||||||||||||||||||
| for suite in crashed: | ||||||||||||||||||||||
| parts.append(f"{os.path.relpath(suite, repo_dir)}: exited non-zero with no JUnit report") | ||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 MEDIUM · correctness — Crashed-suite diagnostic always reports 'no JUnit report' even when one exists The old code stored Reply |
||||||||||||||||||||||
| involved = len(per_suite_failing) + len(crashed) | ||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
| f"{len(failing)} failing test(s) across {involved} suite(s) — " | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 MEDIUM · correctness — Per-suite budget floor can cause total wall time to exceed timeout_seconds
per_suite = max(60, timeout_seconds // len(suites))— whentimeout_seconds < 60 * len(suites), each suite is allocated 60 s and the aggregate budget becomes60 * len(suites), which can be up to60 * 8 = 480 sregardless of whattimeout_secondswas. A caller that passestimeout_seconds=120with 4 suites will wait up to 240 s before the last suite times out, violating the contract. The comment's reasoning ('floor * suites can never exceed 8*60s') describes an absolute maximum but not a bound relative totimeout_seconds.Reply
@codna fixand Codna opens a verified, test-green, risk-gated fix PR.