import io import threading from pathlib import Path import pytest from nisse_core.oplog import OpLog from nisse_core.state.store import StateStore from nisse_core.state.worker import InlineWorker, SerialWorker @pytest.fixture def worker() -> SerialWorker: w = SerialWorker() w.start() yield w w.stop(timeout=5) def test_jobs_run_in_order_on_the_worker_thread(worker: SerialWorker) -> None: seen: list[tuple[int, str]] = [] for n in range(3): worker.submit(lambda n=n: seen.append((n, threading.current_thread().name))) worker.call(lambda: None) # drains everything queued before it assert [n for n, _ in seen] == [0, 1, 2] assert {name for _, name in seen} == {"nisse-worker"} def test_call_returns_the_result_and_raises_the_error(worker: SerialWorker) -> None: assert worker.call(lambda: 42) == 42 def boom() -> None: raise ValueError("bad job") with pytest.raises(ValueError, match="bad job"): worker.call(boom) def test_a_failed_job_is_reported_and_the_worker_carries_on() -> None: errors: list[BaseException] = [] worker = SerialWorker(on_error=errors.append) worker.start() def boom() -> None: raise RuntimeError("lost job") worker.submit(boom) assert worker.call(lambda: "still here") == "still here" assert [str(e) for e in errors] == ["lost job"] worker.stop(timeout=5) def test_call_from_the_worker_itself_runs_inline(worker: SerialWorker) -> None: # Queueing behind itself would deadlock. assert worker.call(lambda: worker.call(lambda: "nested")) == "nested" def test_the_store_is_usable_only_from_the_thread_that_owns_it( worker: SerialWorker, tmp_path: Path ) -> None: store = worker.call( lambda: StateStore(tmp_path / "state.db", oplog=OpLog("t", io.StringIO())) ) assert worker.call(store.active_item) is None # The tripwire the worker design keeps on: any other thread fails loudly. with pytest.raises(Exception, match="same thread"): store.active_item() worker.call(store.close) def test_inline_worker_defers_a_job_submitted_from_inside_a_job() -> None: worker = InlineWorker() order: list[str] = [] def outer() -> None: order.append("outer start") worker.submit(lambda: order.append("inner")) order.append("outer end") worker.submit(outer) assert order == ["outer start", "outer end", "inner"] assert not worker.busy