Skip to content

Commit 9af138a

Browse files
committed
test(hybrid): add unit tests for HybridTap and mode switching
1 parent 39c325f commit 9af138a

1 file changed

Lines changed: 136 additions & 0 deletions

File tree

tests/test_hybrid.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import time
2+
from unittest.mock import patch
3+
4+
import trueentropy
5+
from trueentropy.config import configure, reset_config
6+
from trueentropy.hybrid import HybridTap
7+
from trueentropy.pool import EntropyPool
8+
from trueentropy.tap import EntropyTap
9+
10+
11+
class TestHybridTap:
12+
13+
def test_initial_reseed(self):
14+
"""Test that HybridTap reseeds on initialization."""
15+
pool = EntropyPool()
16+
# Mock extract to verifying it's called
17+
with patch.object(pool, "extract", wraps=pool.extract) as mock_extract:
18+
tap = HybridTap(pool, reseed_on_init=True)
19+
mock_extract.assert_called_with(32)
20+
21+
# Verify tap works
22+
assert 0.0 <= tap.random() < 1.0
23+
24+
def test_manual_reseed(self):
25+
"""Test manual reseeding."""
26+
pool = EntropyPool()
27+
tap = HybridTap(pool)
28+
29+
with patch.object(pool, "extract", wraps=pool.extract) as mock_extract:
30+
tap.reseed()
31+
mock_extract.assert_called_with(32)
32+
33+
def test_auto_reseed_on_time(self):
34+
"""Test that tap reseeds after interval calls."""
35+
pool = EntropyPool()
36+
interval = 0.1
37+
tap = HybridTap(pool, reseed_interval=interval)
38+
39+
# Initial access
40+
tap.random()
41+
last_reseed = tap._last_reseed_time
42+
43+
# Access immediately - should NOT reseed
44+
tap.random()
45+
assert tap._last_reseed_time == last_reseed
46+
47+
# Wait for interval
48+
time.sleep(interval + 0.05)
49+
50+
# Access again - SHOULD reseed
51+
tap.random()
52+
assert tap._last_reseed_time > last_reseed
53+
54+
def test_random_methods(self):
55+
"""Test that all random methods work and delegate to internal PRNG."""
56+
pool = EntropyPool()
57+
tap = HybridTap(pool)
58+
59+
assert isinstance(tap.random(), float)
60+
assert isinstance(tap.randint(1, 10), int)
61+
assert len(tap.randbytes(10)) == 10
62+
assert tap.choice([1, 2, 3]) in [1, 2, 3]
63+
64+
seq = [1, 2, 3]
65+
tap.shuffle(seq)
66+
assert len(seq) == 3
67+
68+
sample = tap.sample([1, 2, 3, 4], 2)
69+
assert len(sample) == 2
70+
assert len(set(sample)) == 2 # Unique
71+
72+
def test_performance_comparison(self):
73+
"""Verify that HybridTap is faster than EntropyTap (sanity check)."""
74+
pool = EntropyPool()
75+
direct_tap = EntropyTap(pool)
76+
hybrid_tap = HybridTap(pool)
77+
78+
# Measure 1000 calls
79+
start = time.perf_counter()
80+
for _ in range(1000):
81+
direct_tap.random()
82+
direct_duration = time.perf_counter() - start
83+
84+
start = time.perf_counter()
85+
for _ in range(1000):
86+
hybrid_tap.random()
87+
hybrid_duration = time.perf_counter() - start
88+
89+
# Hybrid should be significantly faster
90+
# (Though with 1000 calls it might be noise, but typically it's 10x-100x faster)
91+
# We assert simple faster, if it fails due to noise, we might need more samples
92+
# but let's be conservative.
93+
assert (
94+
hybrid_duration < direct_duration
95+
), f"Hybrid ({hybrid_duration:.5f}s) should be faster than Direct ({direct_duration:.5f}s)"
96+
97+
98+
class TestHybridConfig:
99+
100+
def setup_method(self):
101+
"""Reset config before each test."""
102+
reset_config()
103+
# Reset global tap to default
104+
configure(mode="DIRECT")
105+
106+
def teardown_method(self):
107+
reset_config()
108+
configure(mode="DIRECT")
109+
110+
def test_switch_modes(self):
111+
"""Test switching between DIRECT and HYBRID modes."""
112+
# Default is DIRECT
113+
assert isinstance(trueentropy.get_tap(), EntropyTap)
114+
115+
# Switch to HYBRID
116+
trueentropy.configure(mode="HYBRID")
117+
tap = trueentropy.get_tap()
118+
assert isinstance(tap, HybridTap)
119+
# Verify default interval
120+
assert tap._reseed_interval == 60.0
121+
122+
# Switch back to DIRECT
123+
trueentropy.configure(mode="DIRECT")
124+
assert isinstance(trueentropy.get_tap(), EntropyTap)
125+
126+
def test_hybrid_config_params(self):
127+
"""Test configuring hybrid params."""
128+
trueentropy.configure(mode="HYBRID", hybrid_reseed_interval=123.4)
129+
tap = trueentropy.get_tap()
130+
assert isinstance(tap, HybridTap)
131+
assert tap._reseed_interval == 123.4
132+
133+
# Test update interval only
134+
trueentropy.configure(hybrid_reseed_interval=456.7)
135+
tap = trueentropy.get_tap()
136+
assert tap._reseed_interval == 456.7

0 commit comments

Comments
 (0)