test: fix hate_path leak causing flaky test_main_pcfg

hate_crack.main is imported once and shared for the whole pytest
session, so any test that mutates its module globals without restoring
them leaks into every later test. test_markov_e2e assigned
main.hate_path / main.hcatHcstat2genBin directly (and before its skip
check, so it leaked even when skipped). test_main_pcfg's
test_builds_expected_subprocess reads the ambient hate_path to locate
pcfg_guesser.py and bails early when it's missing, so a leaked value
made it intermittently fail at captured_calls[0] (IndexError).

- test_markov_e2e: use monkeypatch.setattr so the globals auto-restore.
- test_main_pcfg: pin hate_path to a tmp dir containing a stub
  pcfg_guesser.py, making the test hermetic and independent of both the
  real pcfg_cracker submodule checkout and any leaked global state.

Full suite passes 3x consecutively; previously flaky.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Justin Bollinger
2026-07-01 16:30:38 -04:00
co-authored by Claude Opus 4.8
parent 00dcd74628
commit 62a83699ac
2 changed files with 26 additions and 10 deletions
+10
View File
@@ -17,6 +17,15 @@ class TestHcatPCFG:
hash_file = str(tmp_path / "hashes.txt")
Path(hash_file).write_text("dummy")
# hcatPCFG resolves pcfg_guesser.py under the module global hate_path
# and bails early if it's missing. Pin hate_path to a tmp dir with a
# stub script so the test is hermetic — independent of the real
# pcfg_cracker submodule being checked out and of any hate_path value
# leaked by an earlier test (hate_crack.main is shared session-wide).
pcfg_dir = tmp_path / "pcfg_cracker"
pcfg_dir.mkdir()
(pcfg_dir / "pcfg_guesser.py").write_text("# stub")
captured_calls = []
class FakeProc:
@@ -27,6 +36,7 @@ class TestHcatPCFG:
with patch("hate_crack.main.subprocess.Popen", side_effect=FakeProc), \
patch("hate_crack.main._run_hcat_cmd") as mock_run, \
patch.object(main_module, "hate_path", str(tmp_path)), \
patch.object(main_module, "hcatBin", "hashcat"), \
patch.object(main_module, "hcatTuning", ""), \
patch.object(main_module, "hcatPotfilePath", ""), \
+16 -10
View File
@@ -10,14 +10,18 @@ import pytest
class TestMarkovE2E:
"""End-to-end tests for complete markov attack workflow."""
def test_markov_training_plain_text(self, tmp_path: Path) -> None:
def test_markov_training_plain_text(self, tmp_path: Path, monkeypatch) -> None:
"""Test markov training with plain text wordlist."""
from hate_crack import main
# Setup paths
main.hate_path = Path(__file__).resolve().parents[1]
main.hcatHcstat2genBin = "hcstat2gen.bin"
bin_path = main.hate_path / "hashcat-utils" / "bin" / "hcstat2gen.bin"
# Setup paths. Use monkeypatch so these module globals are restored
# after the test — hate_crack.main is imported once and shared across
# the whole session, so a raw assignment here would leak into every
# later test (e.g. test_main_pcfg, which reads the ambient hate_path).
repo_root = Path(__file__).resolve().parents[1]
monkeypatch.setattr(main, "hate_path", repo_root)
monkeypatch.setattr(main, "hcatHcstat2genBin", "hcstat2gen.bin")
bin_path = repo_root / "hashcat-utils" / "bin" / "hcstat2gen.bin"
if not bin_path.is_file():
pytest.skip(f"hcstat2gen.bin not compiled: {bin_path}")
@@ -39,14 +43,16 @@ class TestMarkovE2E:
assert hcstat2_path.exists(), ".hcstat2 file should be created"
assert hcstat2_path.stat().st_size > 0, ".hcstat2 file should not be empty"
def test_markov_training_gzipped(self, tmp_path: Path) -> None:
def test_markov_training_gzipped(self, tmp_path: Path, monkeypatch) -> None:
"""Test markov training with gzipped wordlist."""
from hate_crack import main
# Setup paths
main.hate_path = Path(__file__).resolve().parents[1]
main.hcatHcstat2genBin = "hcstat2gen.bin"
bin_path = main.hate_path / "hashcat-utils" / "bin" / "hcstat2gen.bin"
# Setup paths (monkeypatch so these session-shared module globals are
# restored after the test — see test_markov_training_plain_text).
repo_root = Path(__file__).resolve().parents[1]
monkeypatch.setattr(main, "hate_path", repo_root)
monkeypatch.setattr(main, "hcatHcstat2genBin", "hcstat2gen.bin")
bin_path = repo_root / "hashcat-utils" / "bin" / "hcstat2gen.bin"
if not bin_path.is_file():
pytest.skip(f"hcstat2gen.bin not compiled: {bin_path}")