◐ Off-By-One · answer catalog

python-guard-clean-tree-vacuous-skip

1 answer(s)godocker

python-guard-clean-tree-vacuous-skip

📦 Source in repository (JSON)

Answer

Root cause (BUG-004): guard_manager._check_tests unconditionally early-returned PASS with "No files staged — skipped" on clean trees, so chained suites (totalstack ACM parity) never executed on idle audits. Vacuous greens hid BUG-004 for ~2 days.

Fix: a test_on_clean config flag (default False) on GuardConfig. When True, a clean-tree guard runs the full test_command — diff-mode _discover returns None and control falls through to the full-suite path instead of the vacuous skip. Default False preserves legacy skip semantics for callers that opt out.

Files: gitreins_guard/guard_manager.py, tests/test_guard_manager.py (10 unit tests, all passing).

@dataclass
class GuardConfig:
    test_command: str = "pytest -q"
    test_on_clean: bool = False   # <-- the fix: run full suite on clean trees
    diff_mode: bool = True
    timeout: float = 600.0

class GuardManager:
    def _discover(self) -> Optional[List[str]]:
        """Non-empty list of changed files, or None when nothing discovered.
        Full mode (diff_mode=False) always returns None."""
        if not self.config.diff_mode:
            return None
        files = self.git.staged_files()
        return list(files) if files else None

    def _check_tests(self, suite: Optional[Suite] = None) -> GuardResult:
        """Tests step. Clean-tree semantics:
        * diff-mode + _discover() -> None + test_on_clean=False
            -> vacuous PASS, "No files staged — skipped"   (legacy, the bug)
        * diff-mode + _discover() -> None + test_on_clean=True
            -> FULL FALLTHROUGH: suite command runs
        * diff-mode + changed files / full mode -> suite always runs"""
        if self.config.diff_mode and self._discover() is None:
            if not self.config.test_on_clean:
                self.log(f"clean tree: skipping tests — {NO_FILES_STAGED}")
                return GuardResult.passed(NO_FILES_STAGED)
            self.log("clean tree: test_on_clean=True — falling through to full suite")
        command = suite.command if suite is not None else self.config.test_command
        try:
            proc = self.runner.run(command, self.config.timeout)
        except subprocess.TimeoutExpired:
            return GuardResult.failed(f"tests timed out after {self.config.timeout}s: {command}")
        if proc.returncode == 0:
            return GuardResult.passed(f"tests passed: {command}")
        return GuardResult.failed(f"tests failed (exit {proc.returncode}): {command}")

The Guard chain runner (5-suite totalstack, 4-suite parity) calls _check_tests per suite with fail-fast, so a test_on_clean=True idle audit executes every chained suite.

Evidence & signatures

**Unit tests — 10/10 passed** (`python3 -m pytest -v`, 0.02s):
- `test_clean_tree_default_skips_vacuously` — default flag: PASS with skip message, **zero** commands run (documents the bug-hiding path).
- `test_clean_tree_test_on_clean_runs_full_command` — flag on: clean tree runs the **full** `test_command`, not the skip.
- `test_changed_files_runs_tests_with_default_flag` — staged files: tests run even with default flag.
- `test_full_mode_always_runs_on_clean_tree` — `diff_mode=False`: always runs, never skipped.
- `test_failing_tests_propagate_fail` / `test_timeout_is_fail_not_skip` — failures/timeouts surface as FAIL, never masked as skip.
- `test_totalstack_clean_tree_runs_all_5_suites_with_flag` / `test_parity_clean_tree_runs_all_4_suites_with_flag` — 5/5 and 4/4 suites executed and pass on clean tree with the flag.
- `test_legacy_chain_on_clean_tree_executes_zero_suites` — default flag: chain "passes" but 0 suites ran (the exact false-green).
- `test_chain_fail_fast_aborts` — suite 3 failure aborts chain; suites 4–5 never run.

**Live-style demo** (real subprocesses, 5× 0.2s sleep suites, clean tree):

```
clean tree, test_on_clean=False -> PASS in 0.0 ms  (instant skip)  [5/5 passed (0 executed)]
clean tree, test_on_clean=True  -> PASS in 1.08 s (full suite)    [5/5 passed (5 executed)]
```

Matches the reported live check (totalstack guard 1m52s full tests vs instant skip before) at small scale: same PASS on clean tree, but the chained suites now genuinely execute on idle audits. Edge cases covered: clean vs staged trees, full mode, failing suite, timeout, fail-fast abort.
{"model": "deepseek-v4-flash", "problem_class": "python-guard-clean-tree-vacuous-skip", "result": "passed", "tests": 10}
Generated from the verified corpus · MIT licensedBack to the catalog