diff --git a/bandsaunter/simulator.py b/bandsaunter/simulator.py index 9513501..cd371e9 100755 --- a/bandsaunter/simulator.py +++ b/bandsaunter/simulator.py @@ -10,6 +10,7 @@ from __future__ import annotations import math import time +import zlib from dataclasses import dataclass, field import numpy as np @@ -115,7 +116,11 @@ class VirtualTransmitter: _rng: np.random.Generator = field(default=None, init=False, repr=False) def __post_init__(self): - self._seed = abs(hash(self.label or self.frequency)) % 2**31 + # crc32 rather than hash(): the builtin is salted per interpreter, so + # a test's synthetic speech and keying differed on every run and a + # failure could not be reproduced by running it again. + key = str(self.label or self.frequency).encode() + self._seed = zlib.crc32(key) self._rng = np.random.default_rng(self._seed) if not self.label: self.label = f"{self.frequency/1e6:.4f} MHz {self.mode}" diff --git a/tests/test_scanner.py b/tests/test_scanner.py index d5a9945..fe97d6b 100644 --- a/tests/test_scanner.py +++ b/tests/test_scanner.py @@ -52,11 +52,16 @@ def test_record_seconds_is_honoured_exactly(tmp_path): def test_hang_seconds_ends_a_finished_transmission(tmp_path): - """A transmitter that stops must release the scanner after the hang time.""" + """A transmitter that stops must release the scanner after the hang time. + + What is under test is the ending, not the catching, so the burst is a + generous 40% of the cycle over six sweeps: landing on it is meant to be + easy, and only the release afterwards should be able to fail this. + """ tx = [V(146_520_000, "nfm", 0.4, 12_500, "burst", - period_seconds=6.0, on_seconds=1.0)] + period_seconds=5.0, on_seconds=2.0)] s, hits = run_scan(tmp_path, tx, "146.4M-146.6M", - record_seconds=20.0, hang_seconds=0.8, max_cycles=3) + record_seconds=20.0, hang_seconds=0.8, max_cycles=6) assert hits, "nothing recorded" ended_on_silence = [h for h in hits if "quiet" in h.stop_reason] assert ended_on_silence, [h.stop_reason for h in hits] @@ -369,9 +374,12 @@ def test_being_cut_off_mid_transmission_is_reported(tmp_path): def test_no_warning_when_the_signal_really_ended(tmp_path): tx = [V(146_520_000, "nfm", 0.4, 12_500, "burst", - period_seconds=12.0, on_seconds=2.0)] + period_seconds=5.0, on_seconds=2.0)] s, hits = run_scan(tmp_path, tx, "146.4M-146.6M", record_seconds=20.0, - hang_seconds=1.0, max_cycles=3, revisit_seconds=0.1) + hang_seconds=1.0, max_cycles=6, revisit_seconds=0.1) + # Without a recording there is nothing to warn about either way, so the + # count alone would pass whether or not the burst was ever heard. + assert hits, "nothing recorded" assert s.stats.truncated == 0