/ test / test_bench.py
test_bench.py
  1  """Pytest harness for keccak_bench.
  2  
  3  Validates:
  4    - static test/vectors.json against hashlib (catches drift in the file)
  5    - `--validate-hex` path against hashlib (catches miner-side bugs)
  6    - a short hot-path run (real beat records)
  7  
  8  Run with:  pytest -xvs test/test_bench.py
  9  
 10  Or from the repo root:  make test
 11  """
 12  from __future__ import annotations
 13  
 14  import hashlib
 15  import json
 16  import pathlib
 17  import subprocess
 18  import sys
 19  
 20  HERE = pathlib.Path(__file__).resolve().parent
 21  REPO = HERE.parent
 22  VECTORS_PATH = HERE / "vectors.json"
 23  MINER_LTO = REPO / "src" / "keccak_bench_lto"
 24  MINER_PLAIN = REPO / "src" / "keccak_bench"
 25  
 26  
 27  def _load_vectors() -> dict:
 28      return json.loads(VECTORS_PATH.read_text())
 29  
 30  
 31  def test_vectors_file_self_consistent() -> None:
 32      data = _load_vectors()
 33      prefix = data["prefix"].encode()
 34      for v in data["vectors"]:
 35          x = bytes.fromhex(v["x_hex"])
 36          expected = hashlib.sha3_256(prefix + x).hexdigest()
 37          assert v["expected_hash_hex"] == expected, v
 38  
 39  
 40  def _run_validate_hex(miner: pathlib.Path, x_hex: str) -> str:
 41      out = subprocess.check_output([str(miner), "--validate-hex", x_hex],
 42                                    text=True, timeout=10)
 43      rec = json.loads(out.strip().splitlines()[-1])
 44      assert rec["kind"] == "validate"
 45      return rec["hash_hex"]
 46  
 47  
 48  def _test_vectors_for(miner: pathlib.Path) -> None:
 49      if not miner.exists():
 50          import pytest
 51          pytest.skip(f"miner missing: {miner} — run `make build`")
 52      data = _load_vectors()
 53      for v in data["vectors"]:
 54          x_hex = v["x_hex"]
 55          if not x_hex:
 56              # --validate-hex with empty string is handled as 0-length input.
 57              pass
 58          got = _run_validate_hex(miner, x_hex)
 59          assert got == v["expected_hash_hex"], f"{x_hex}: got={got} expected={v['expected_hash_hex']}"
 60  
 61  
 62  def test_vectors_via_miner_lto() -> None:
 63      _test_vectors_for(MINER_LTO)
 64  
 65  
 66  def test_vectors_via_miner_plain() -> None:
 67      _test_vectors_for(MINER_PLAIN)
 68  
 69  
 70  def test_hotpath_short() -> None:
 71      """Spawn the miner for a few seconds, verify emitted 'best' records
 72      re-hash with hashlib. Short run so the test suite stays fast."""
 73      if not MINER_LTO.exists():
 74          import pytest
 75          pytest.skip(f"miner missing: {MINER_LTO}")
 76      data = _load_vectors()
 77      prefix = data["prefix"].encode()
 78      p = subprocess.Popen(
 79          [str(MINER_LTO), "--workers", "1", "--salt-hex", "0102030405", "--source", "pytest"],
 80          stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1,
 81      )
 82      assert p.stdout is not None
 83      import time
 84      t0 = time.time()
 85      best_records: list[dict] = []
 86      canary_fail = False
 87      try:
 88          for line in p.stdout:
 89              if time.time() - t0 > 3.0:
 90                  break
 91              line = line.strip()
 92              if not line.startswith("{"):
 93                  continue
 94              try:
 95                  r = json.loads(line)
 96              except Exception:
 97                  continue
 98              if r.get("kind") == "canary_fail":
 99                  canary_fail = True
100                  break
101              if r.get("kind") == "best":
102                  best_records.append(r)
103      finally:
104          try:
105              p.terminate()
106              p.wait(timeout=5)
107          except Exception:
108              p.kill()
109      assert not canary_fail, "miner emitted canary_fail"
110      assert len(best_records) >= 5, f"expected >= 5 best records, got {len(best_records)}"
111      for r in best_records:
112          x = bytes.fromhex(r["input_hex"])
113          expected = hashlib.sha3_256(prefix + x).hexdigest()
114          assert r["hash_hex"] == expected, r
115  
116  
117  if __name__ == "__main__":
118      # Allow running as a script too: `python test/test_bench.py`
119      for fn in [test_vectors_file_self_consistent,
120                 test_vectors_via_miner_lto,
121                 test_vectors_via_miner_plain,
122                 test_hotpath_short]:
123          try:
124              fn()
125              print(f"OK  {fn.__name__}")
126          except Exception as e:  # noqa: BLE001
127              print(f"FAIL {fn.__name__}: {e}")
128              sys.exit(1)
129      print("all tests passed")