mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
104 lines
2.9 KiB
Python
104 lines
2.9 KiB
Python
"""Contract tests for progress_heartbeat.run_with_heartbeat: it advances a
|
|||
|
|
rising progress bar from a background thread while an opaque model call runs,
|
||
|
|
caps below ``end``, returns the call's value, and propagates its exception.
|
||
|
|
Pure (threading), deterministic via events rather than sleep races."""
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import threading
|
||
|
|
import time
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
||
|
|
import progress_heartbeat # noqa: E402
|
||
|
|
|
||
|
|
|
||
|
|
def test_returns_fn_value_without_beats_when_interval_long():
|
||
|
|
emitted = []
|
||
|
|
result = progress_heartbeat.run_with_heartbeat(
|
||
|
|
lambda: 42,
|
||
|
|
lambda pct, stage: emitted.append((pct, stage)),
|
||
|
|
start=0,
|
||
|
|
end=100,
|
||
|
|
stage="work",
|
||
|
|
interval=100,
|
||
|
|
)
|
||
|
|
assert result == 42
|
||
|
|
assert emitted == [] # fn returned long before the first (100s) beat
|
||
|
|
|
||
|
|
|
||
|
|
def test_propagates_fn_exception():
|
||
|
|
emitted = []
|
||
|
|
|
||
|
|
def boom():
|
||
|
|
raise ValueError("nope")
|
||
|
|
|
||
|
|
with pytest.raises(ValueError, match="nope"):
|
||
|
|
progress_heartbeat.run_with_heartbeat(
|
||
|
|
boom,
|
||
|
|
lambda pct, stage: emitted.append((pct, stage)),
|
||
|
|
start=0,
|
||
|
|
end=100,
|
||
|
|
stage="work",
|
||
|
|
interval=100,
|
||
|
|
)
|
||
|
|
assert emitted == []
|
||
|
|
|
||
|
|
|
||
|
|
def test_emits_rising_progress_capped_below_end():
|
||
|
|
emitted = []
|
||
|
|
reached_two = threading.Event()
|
||
|
|
|
||
|
|
def emit(pct, stage):
|
||
|
|
emitted.append((pct, stage))
|
||
|
|
if len(emitted) >= 2:
|
||
|
|
reached_two.set()
|
||
|
|
|
||
|
|
def fn():
|
||
|
|
reached_two.wait(timeout=2.0) # block until two heartbeats fire
|
||
|
|
return "done"
|
||
|
|
|
||
|
|
result = progress_heartbeat.run_with_heartbeat(
|
||
|
|
fn, emit, start=10, end=13, stage="infer", interval=0.01
|
||
|
|
)
|
||
|
|
assert result == "done"
|
||
|
|
pcts = [p for p, _ in emitted]
|
||
|
|
# start+1 .. end-1, monotonically rising, never reaching end.
|
||
|
|
assert pcts == [11, 12]
|
||
|
|
assert all(stage == "infer" for _, stage in emitted)
|
||
|
|
assert max(pcts) <= 13 - 1
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_emit_when_start_at_cap():
|
||
|
|
emitted = []
|
||
|
|
|
||
|
|
def fn():
|
||
|
|
time.sleep(0.05) # give the beat thread room to attempt an emit
|
||
|
|
return "x"
|
||
|
|
|
||
|
|
result = progress_heartbeat.run_with_heartbeat(
|
||
|
|
fn, lambda pct, stage: emitted.append(pct), start=12, end=13, stage="s", interval=0.01
|
||
|
|
)
|
||
|
|
assert result == "x"
|
||
|
|
assert emitted == [] # pct=12 is not < end-1=12, so nothing is emitted
|
||
|
|
|
||
|
|
|
||
|
|
def test_stops_emitting_after_fn_returns():
|
||
|
|
emitted = []
|
||
|
|
reached_one = threading.Event()
|
||
|
|
|
||
|
|
def emit(pct, stage):
|
||
|
|
emitted.append(pct)
|
||
|
|
reached_one.set()
|
||
|
|
|
||
|
|
def fn():
|
||
|
|
reached_one.wait(timeout=2.0)
|
||
|
|
return None
|
||
|
|
|
||
|
|
progress_heartbeat.run_with_heartbeat(
|
||
|
|
fn, emit, start=0, end=100, stage="s", interval=0.01
|
||
|
|
)
|
||
|
|
count = len(emitted)
|
||
|
|
time.sleep(0.05) # a still-alive beat thread would push more emits here
|
||
|
|
assert len(emitted) == count # the heartbeat stopped on return
|