Files
harness-tools/services/dictate/tests/test_server_core.py
T

917 lines
29 KiB
Python

from concurrent.futures import Future
from collections.abc import Sequence
from dataclasses import dataclass
import errno
import selectors
import subprocess
import sys
import unittest
from unittest.mock import patch
from pi_dictate_service.protocol import (
MAX_FRAME_BYTES,
MAX_TRANSCRIPT_BYTES,
PROTOCOL_VERSION,
encode_frame,
parse_request,
)
from pi_dictate_service.recorder import Capture, RecorderError
from pi_dictate_service.server import DictateServiceCore, Outbound, serve
class Clock:
def __init__(self) -> None:
self.now = 0.0
def __call__(self) -> float:
return self.now
def advance(self, seconds: float) -> None:
self.now += seconds
@dataclass
class PendingWork:
future: Future[object]
function: object
args: tuple[object, ...]
class ControlledExecutor:
def __init__(self) -> None:
self.pending: list[PendingWork] = []
def submit(self, function: object, *args: object) -> Future[object]:
future: Future[object] = Future()
self.pending.append(PendingWork(future, function, args))
return future
def run_next(self) -> None:
work = self.pending.pop(0)
try:
result = work.function(*work.args) # type: ignore[operator]
except BaseException as error:
work.future.set_exception(error)
else:
work.future.set_result(result)
def shutdown(self, wait: bool = True, cancel_futures: bool = False) -> None:
return None
class FakeModel:
def __init__(self) -> None:
self.loaded = False
self.transcript = "dictated text"
self.load_error: BaseException | None = None
self.transcribe_error: BaseException | None = None
self.samples: Sequence[float] | None = None
def load(self) -> None:
if self.load_error:
raise self.load_error
self.loaded = True
def transcribe(self, samples: Sequence[float]) -> str:
self.samples = samples
if self.transcribe_error:
raise self.transcribe_error
return self.transcript
class FakeRecorder:
def __init__(self) -> None:
self.started = False
self.cancelled = False
self.capture = Capture(b"\x00\x00\x00\x40", -15, 15, b"")
self.audio_level = 0.0
self.stop_error: RecorderError | None = None
self.unexpected: RecorderError | None = None
def start(self) -> None:
self.started = True
def stop(self) -> Capture:
if self.stop_error:
raise self.stop_error
return self.capture
def cancel(self) -> None:
self.cancelled = True
def poll_unexpected(self) -> RecorderError | None:
failure, self.unexpected = self.unexpected, None
return failure
class SpawnFailRecorder(FakeRecorder):
cleanup_complete = True
def start(self) -> None:
raise RecorderError("recorder_spawn", "could not start pw-record")
class SpawnFailRecorderFactory:
def __call__(self) -> SpawnFailRecorder:
return SpawnFailRecorder()
class DelayedCleanupRecorder(FakeRecorder):
def __init__(self) -> None:
super().__init__()
self.cleanup_complete = False
self.cleanup_attempts = 0
self.cleanup_started = False
def stop(self) -> Capture:
self.cleanup_started = True
raise RecorderError("recorder_timeout", "pw-record cleanup did not complete")
def poll_unexpected(self) -> RecorderError | None:
if not self.cleanup_started:
return None
self.cleanup_attempts += 1
if self.cleanup_attempts >= 3:
self.cleanup_complete = True
return None
class DelayedCleanupRecorderFactory:
def __init__(self) -> None:
self.instance = DelayedCleanupRecorder()
def __call__(self) -> DelayedCleanupRecorder:
return self.instance
class DelayedCloseTurn:
sample_rate_hz = 16_000
retain_pcm = True
def __init__(self) -> None:
self.close_calls = 0
@property
def cleanup_complete(self) -> bool:
return self.close_calls >= 3
def enqueue_pcm(self, chunk: bytes) -> None:
return None
def finish(self, capture: Capture | None = None) -> str:
return "cloud result"
def poll_error(self) -> None:
return None
def cancel(self) -> None:
return None
def close(self) -> None:
self.close_calls += 1
class StreamingTurn:
sample_rate_hz = 24_000
retain_pcm = False
def __init__(self, events: list[str]) -> None:
self.events = events
self.closed = False
@property
def cleanup_complete(self) -> bool:
return self.closed
def enqueue_pcm(self, chunk: bytes) -> None:
self.events.append(f"audio-{chunk.decode('ascii')}")
def finish(self, capture: Capture | None = None) -> str:
self.events.append("finish")
self.assert_capture = capture
return "cloud result"
def poll_error(self) -> None:
return None
def cancel(self) -> None:
self.events.append("cancel")
def close(self) -> None:
self.closed = True
class StreamingRecorder(FakeRecorder):
def __init__(self, sink: StreamingTurn, events: list[str]) -> None:
super().__init__()
self.sink = sink
self.events = events
self.capture = Capture(b"", -15, 15, b"")
def start(self) -> None:
super().start()
self.sink.enqueue_pcm(b"a")
self.sink.enqueue_pcm(b"b")
def stop(self) -> Capture:
self.events.append("recorder-stop")
return super().stop()
class StreamingRecorderFactory:
def __init__(self, events: list[str]) -> None:
self.events = events
self.sample_rate_hz: int | None = None
self.instance: StreamingRecorder | None = None
def __call__(
self,
*,
sample_rate_hz: int,
pcm_sink: StreamingTurn,
retain_pcm: bool,
) -> StreamingRecorder:
self.sample_rate_hz = sample_rate_hz
if retain_pcm:
raise AssertionError("cloud capture must not retain PCM")
self.instance = StreamingRecorder(pcm_sink, self.events)
return self.instance
class FakeBackend:
sample_rate_hz = 16_000
descriptor = type("Descriptor", (), {"backend": "fake", "model": "fake-model"})()
def __init__(self, turn: DelayedCloseTurn | StreamingTurn) -> None:
self.turn = turn
def start_turn(self) -> DelayedCloseTurn:
return self.turn
def cancel_prepare(self) -> None:
return None
class RecorderFactory:
def __init__(self) -> None:
self.instances: list[FakeRecorder] = []
def __call__(self) -> FakeRecorder:
recorder = FakeRecorder()
self.instances.append(recorder)
return recorder
class CoreTestCase(unittest.TestCase):
def setUp(self) -> None:
self.clock = Clock()
self.executor = ControlledExecutor()
self.model = FakeModel()
self.recorders = RecorderFactory()
self.core = DictateServiceCore(
model=self.model,
recorder_factory=self.recorders,
executor=self.executor,
clock=self.clock,
idle_seconds=900,
)
def request(
self,
client: str,
request_type: str,
request_id: str,
generation_id: str | None = None,
) -> list[dict[str, object]]:
frame: dict[str, object] = {
"version": PROTOCOL_VERSION,
"type": request_type,
"requestId": request_id,
}
if generation_id is not None:
frame["generationId"] = generation_id
self.core.handle_request(client, parse_request(frame))
return self.frames(self.core.drain_events(), client)
@staticmethod
def frames(events: list[Outbound], client: str | None = None) -> list[dict[str, object]]:
return [event.frame for event in events if client is None or event.client_id == client]
def hello(self, client: str = "a") -> None:
events = self.request(client, "hello", f"hello-{client}")
self.assertEqual(events[0]["type"], "pong")
def load_to_listening(self, client: str = "a", generation: str = "g1") -> FakeRecorder:
self.hello(client)
self.request(client, "start", "start", generation)
self.executor.run_next()
self.core.poll()
events = self.frames(self.core.drain_events(), client)
self.assertEqual(events[-1]["state"], "listening")
self.assertEqual(events[-1]["requestId"], "start")
return self.recorders.instances[-1]
class HandshakeAndOwnershipTests(CoreTestCase):
def test_hello_is_required_and_valid_errors_keep_session_open(self) -> None:
events = self.request("a", "start", "s1", "g1")
self.assertEqual(events[0]["code"], "hello_required")
self.hello("a")
self.request("a", "start", "s2", "g1")
self.assertEqual(len(self.executor.pending), 1)
def test_one_generation_is_globally_owned(self) -> None:
self.load_to_listening()
self.hello("b")
busy = self.request("b", "start", "s2", "g2")
self.assertEqual(busy[0]["code"], "busy")
not_owner = self.request("b", "stop", "x", "g1")
self.assertEqual(not_owner[0]["code"], "not_owner")
stale = self.request("a", "stop", "x2", "old")
self.assertEqual(stale[0]["code"], "generation_mismatch")
stopping = self.request("a", "stop", "x3", "g1")
self.assertEqual(stopping[0]["state"], "stopping")
class CancellationTests(CoreTestCase):
def test_cancel_during_load_is_immediate_and_blocks_until_worker_finishes(self) -> None:
self.hello("a")
self.request("a", "start", "start", "g1")
cancelled = self.request("a", "cancel", "cancel", "g1")
self.assertEqual(cancelled[0]["type"], "cancelled")
self.hello("b")
self.assertEqual(self.request("b", "start", "start-b", "g2")[0]["code"], "busy")
self.executor.run_next()
self.core.poll()
self.assertEqual(self.recorders.instances, [])
self.assertNotIn("result", [event["type"] for event in self.frames(self.core.drain_events())])
def test_cancel_during_inference_suppresses_later_result(self) -> None:
self.load_to_listening()
self.request("a", "stop", "stop", "g1")
self.executor.run_next()
self.core.poll()
transcribing = self.frames(self.core.drain_events(), "a")
self.assertEqual(transcribing[0]["state"], "transcribing")
cancelled = self.request("a", "cancel", "cancel", "g1")
self.assertEqual(cancelled[0]["type"], "cancelled")
self.executor.run_next()
self.core.poll()
self.assertNotIn("result", [event["type"] for event in self.frames(self.core.drain_events())])
def test_disconnect_cancels_capture_and_tombstones_workers(self) -> None:
recorder = self.load_to_listening()
self.core.disconnect("a")
self.assertEqual(len(self.executor.pending), 1)
self.executor.run_next()
self.core.poll()
self.assertTrue(recorder.cancelled)
self.assertEqual(self.core.active_generation_id, None)
class AudioLevelTests(CoreTestCase):
def test_emits_owned_normalized_audio_levels_at_no_more_than_ten_hertz(self) -> None:
recorder = self.load_to_listening()
recorder.audio_level = 0.625
events: list[Outbound] = []
for _ in range(40):
self.clock.advance(0.025)
self.core.poll()
events.extend(self.core.drain_events())
self.assertGreaterEqual(len(events), 9)
self.assertLessEqual(len(events), 10)
self.assertEqual({event.client_id for event in events}, {"a"})
self.assertEqual({event.frame["type"] for event in events}, {"level"})
self.assertEqual({event.frame["level"] for event in events}, {0.625})
def test_clamps_invalid_levels_and_stops_emitting_after_listening(self) -> None:
recorder = self.load_to_listening()
for raw_level, expected in ((2.0, 1.0), (-1.0, 0.0), (float("nan"), 0.0)):
recorder.audio_level = raw_level
self.clock.advance(0.1)
self.core.poll()
level = self.frames(self.core.drain_events(), "a")
self.assertEqual(level[0]["level"], expected)
self.request("a", "stop", "stop", "g1")
self.clock.advance(0.2)
self.core.poll()
self.assertEqual(self.core.drain_events(), [])
class StopAndInferenceTests(CoreTestCase):
def test_stop_drains_then_converts_before_inference_and_emits_result(self) -> None:
self.load_to_listening()
stopping = self.request("a", "stop", "stop", "g1")
self.assertEqual(stopping[0]["state"], "stopping")
self.assertEqual(stopping[0]["requestId"], "stop")
self.executor.run_next()
self.core.poll()
transcribing = self.frames(self.core.drain_events(), "a")
self.assertEqual(transcribing[0]["state"], "transcribing")
self.executor.run_next()
self.core.poll()
completed = self.frames(self.core.drain_events(), "a")
self.assertEqual(list(self.model.samples or []), [0.0, 0.5])
self.assertEqual(completed[0]["type"], "result")
self.assertEqual(completed[0]["transcript"], "dictated text")
self.assertEqual(completed[1]["state"], "idle")
def test_realtime_backend_streams_ordered_pcm_and_finalizes_after_recorder_stop(self) -> None:
events: list[str] = []
turn = StreamingTurn(events)
recorders = StreamingRecorderFactory(events)
core = DictateServiceCore(
backend=FakeBackend(turn),
recorder_factory=recorders,
executor=self.executor,
clock=self.clock,
idle_seconds=900,
)
core.accepted("a")
core.handle_request("a", parse_request({"version": 2, "type": "hello", "requestId": "hello"}))
core.drain_events()
core.handle_request("a", parse_request({
"version": 2,
"type": "start",
"requestId": "start",
"generationId": "g1",
}))
self.executor.run_next()
core.poll()
self.assertEqual(recorders.sample_rate_hz, 24_000)
self.assertEqual(events, ["audio-a", "audio-b"])
core.drain_events()
core.handle_request("a", parse_request({
"version": 2,
"type": "stop",
"requestId": "stop",
"generationId": "g1",
}))
self.executor.run_next()
core.poll()
self.executor.run_next()
core.poll()
self.assertEqual(events, ["audio-a", "audio-b", "recorder-stop", "finish"])
result = [event.frame for event in core.drain_events() if event.frame["type"] == "result"]
self.assertEqual(result[0]["transcript"], "cloud result")
self.assertIsNone(core.active_generation_id)
def test_cancel_after_prepare_completion_tracks_turn_cleanup_before_release(self) -> None:
events: list[str] = []
turn = StreamingTurn(events)
core = DictateServiceCore(
backend=FakeBackend(turn),
recorder_factory=StreamingRecorderFactory(events),
executor=self.executor,
clock=self.clock,
idle_seconds=900,
)
core.accepted("a")
core.handle_request("a", parse_request({"version": 2, "type": "hello", "requestId": "hello"}))
core.drain_events()
core.handle_request("a", parse_request({
"version": 2,
"type": "start",
"requestId": "start",
"generationId": "g1",
}))
self.executor.run_next()
# The future has completed, but the socket loop has not yet applied it.
core.handle_request("a", parse_request({
"version": 2,
"type": "cancel",
"requestId": "cancel",
"generationId": "g1",
}))
core.poll()
self.assertIn("cancel", events)
self.assertTrue(turn.cleanup_complete)
self.assertIsNone(core.active_generation_id)
def test_completed_turn_retries_bounded_cleanup_before_releasing_generation(self) -> None:
turn = DelayedCloseTurn()
core = DictateServiceCore(
backend=FakeBackend(turn),
recorder_factory=self.recorders,
executor=self.executor,
clock=self.clock,
idle_seconds=900,
)
core.accepted("a")
core.handle_request("a", parse_request({"version": 2, "type": "hello", "requestId": "hello"}))
core.drain_events()
core.handle_request("a", parse_request({
"version": 2,
"type": "start",
"requestId": "start",
"generationId": "g1",
}))
self.executor.run_next()
core.poll()
core.drain_events()
core.handle_request("a", parse_request({
"version": 2,
"type": "stop",
"requestId": "stop",
"generationId": "g1",
}))
self.executor.run_next()
core.poll()
self.executor.run_next()
core.poll()
self.assertEqual(core.active_generation_id, "g1")
self.assertEqual(turn.close_calls, 2)
core.poll()
self.assertIsNone(core.active_generation_id)
self.assertEqual(turn.close_calls, 3)
def test_empty_transcript_is_a_result(self) -> None:
self.model.transcript = ""
self.load_to_listening()
self.request("a", "stop", "stop", "g1")
self.executor.run_next()
self.core.poll()
self.core.drain_events()
self.executor.run_next()
self.core.poll()
completed = self.frames(self.core.drain_events(), "a")
self.assertEqual(completed[0]["type"], "result")
self.assertEqual(completed[0]["transcript"], "")
def test_model_and_transcript_failures_are_bounded_terminal_errors(self) -> None:
self.model.load_error = RuntimeError("CUDA unavailable")
self.hello()
self.request("a", "start", "start", "g1")
self.executor.run_next()
self.core.poll()
failed = self.frames(self.core.drain_events(), "a")
self.assertEqual(failed[0]["code"], "model_failure")
self.assertEqual(failed[-1]["state"], "idle")
model = FakeModel()
model.loaded = True
model.transcript = "x" * (MAX_TRANSCRIPT_BYTES + 1)
core = DictateServiceCore(
model=model,
recorder_factory=self.recorders,
executor=self.executor,
clock=self.clock,
)
core.handle_request("a", parse_request({"version": PROTOCOL_VERSION, "type": "hello", "requestId": "h"}))
core.drain_events()
core.handle_request("a", parse_request({"version": PROTOCOL_VERSION, "type": "start", "requestId": "s", "generationId": "g"}))
core.drain_events()
core.handle_request("a", parse_request({"version": PROTOCOL_VERSION, "type": "stop", "requestId": "t", "generationId": "g"}))
self.executor.run_next()
core.poll()
core.drain_events()
self.executor.run_next()
core.poll()
failed = self.frames(core.drain_events(), "a")
self.assertEqual(failed[0]["code"], "transcript_too_large")
def test_unexpected_recorder_exit_is_terminal(self) -> None:
recorder = self.load_to_listening()
recorder.unexpected = RecorderError("recorder_exit", "pw-record exited with code 3")
self.core.poll()
self.executor.run_next()
self.core.poll()
failed = self.frames(self.core.drain_events(), "a")
self.assertEqual(failed[0]["code"], "recorder_exit")
self.assertEqual(failed[-1]["state"], "idle")
def test_stop_does_not_reclassify_an_already_exited_recorder_as_intentional(self) -> None:
recorder = self.load_to_listening()
recorder.unexpected = RecorderError("recorder_exit", "pw-record exited with code 0")
failed = self.request("a", "stop", "stop", "g1")
self.executor.run_next()
self.core.poll()
failed.extend(self.frames(self.core.drain_events(), "a"))
self.assertEqual(failed[0]["code"], "recorder_exit")
self.assertEqual(failed[-1]["state"], "idle")
self.assertEqual(self.executor.pending, [])
def test_recorder_spawn_failure_releases_generation_without_cleanup_loop(self) -> None:
core = DictateServiceCore(
model=self.model,
recorder_factory=SpawnFailRecorderFactory(),
executor=self.executor,
clock=self.clock,
idle_seconds=900,
)
core.accepted("a")
core.handle_request("a", parse_request({
"version": 2, "type": "hello", "requestId": "hello"
}))
core.drain_events()
core.handle_request("a", parse_request({
"version": 2,
"type": "start",
"requestId": "start",
"generationId": "g1",
}))
self.executor.run_next()
core.poll()
events = self.frames(core.drain_events(), "a")
self.assertEqual(events[-2]["code"], "recorder_spawn")
self.assertEqual(events[-1]["state"], "idle")
self.assertIsNone(core.active_generation_id)
self.assertEqual(self.executor.pending, [])
def test_unreaped_recorder_keeps_generation_busy_until_cleanup_completes(self) -> None:
recorders = DelayedCleanupRecorderFactory()
core = DictateServiceCore(
model=self.model,
recorder_factory=recorders,
executor=self.executor,
clock=self.clock,
idle_seconds=900,
)
core.accepted("a")
core.handle_request("a", parse_request({
"version": 2, "type": "hello", "requestId": "hello"
}))
core.drain_events()
core.handle_request("a", parse_request({
"version": 2,
"type": "start",
"requestId": "start",
"generationId": "g1",
}))
self.executor.run_next()
core.poll()
core.drain_events()
core.handle_request("a", parse_request({
"version": 2,
"type": "stop",
"requestId": "stop",
"generationId": "g1",
}))
self.executor.run_next()
core.poll()
self.assertEqual(core.active_generation_id, "g1")
# First reap the cancellation worker scheduled by the terminal stop.
self.executor.run_next()
core.poll()
for attempt in range(1, 4):
self.executor.run_next()
core.poll()
if attempt < 3:
self.assertEqual(core.active_generation_id, "g1")
self.assertIsNone(core.active_generation_id)
self.assertEqual(recorders.instance.cleanup_attempts, 3)
class SocketLoopRegressionTests(unittest.TestCase):
def test_output_overflow_does_not_use_the_closed_socket_from_same_selector_event(self) -> None:
frame = {"padding": "x" * (MAX_FRAME_BYTES - len(encode_frame({"padding": ""})))}
self.assertEqual(len(encode_frame(frame)), MAX_FRAME_BYTES)
client = _FakeClientSocket()
listener = _FakeListener(client)
selector = _FakeSelector(client)
core = _FakeLoopCore(
Outbound("client-1", frame),
)
with patch("pi_dictate_service.server.selectors.DefaultSelector", return_value=selector):
serve(listener, core)
self.assertEqual(client.send_calls, 0)
self.assertTrue(client.closed)
self.assertTrue(listener.closed)
def test_close_does_not_wait_for_owned_executor_workers(self) -> None:
executor = _ShutdownRecordingExecutor()
with patch("pi_dictate_service.server._DaemonExecutor", return_value=executor):
core = DictateServiceCore(model=FakeModel())
core.accepted("a")
core.handle_request(
"a", parse_request({"version": PROTOCOL_VERSION, "type": "hello", "requestId": "h"})
)
core.drain_events()
core.handle_request(
"a",
parse_request(
{
"version": PROTOCOL_VERSION,
"type": "start",
"requestId": "s",
"generationId": "g",
}
),
)
core.close()
self.assertEqual(executor.shutdown_args, (False, True))
def test_real_owned_executor_does_not_block_interpreter_exit(self) -> None:
program = """
import threading
import time
from pi_dictate_service.protocol import parse_request
from pi_dictate_service.server import DictateServiceCore
class StuckModel:
loaded = False
def load(self):
threading.Event().wait()
def transcribe(self, _samples):
return ''
core = DictateServiceCore(model=StuckModel())
core.accepted('a')
core.handle_request('a', parse_request({'version': 2, 'type': 'hello', 'requestId': 'h'}))
core.drain_events()
core.handle_request('a', parse_request({
'version': 2, 'type': 'start', 'requestId': 's', 'generationId': 'g'
}))
time.sleep(0.05)
core.close()
print('closed')
"""
result = subprocess.run(
[sys.executable, "-c", program],
text=True,
capture_output=True,
timeout=2,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), "closed")
@dataclass
class _FakeSelectorKey:
data: str | None
class _FakeSelector:
def __init__(self, client: "_FakeClientSocket") -> None:
self._events = [
[(_FakeSelectorKey(None), selectors.EVENT_READ)],
[(_FakeSelectorKey("client-1"), selectors.EVENT_READ | selectors.EVENT_WRITE)],
]
self.client = client
self.unregistered: list[object] = []
self.closed = False
def register(self, fileobj: object, _events: int, _data: object = None) -> None:
return None
def unregister(self, fileobj: object) -> None:
self.unregistered.append(fileobj)
def modify(self, fileobj: object, _events: int, _data: object = None) -> None:
if fileobj is self.client and self.client.closed:
raise OSError(errno.EBADF, "bad file descriptor")
def select(self, timeout: float | None = None) -> list[tuple[_FakeSelectorKey, int]]:
return self._events.pop(0)
def close(self) -> None:
self.closed = True
class _FakeClientSocket:
def __init__(self) -> None:
self.closed = False
self.send_calls = 0
self._read = True
def setblocking(self, _value: bool) -> None:
return None
def recv(self, _size: int) -> bytes:
if self._read:
self._read = False
return b"{broken}\n"
return b""
def send(self, _data: bytearray) -> int:
self.send_calls += 1
raise OSError(errno.EBADF, "bad file descriptor")
def close(self) -> None:
self.closed = True
class _FakeListener:
def __init__(self, client: _FakeClientSocket) -> None:
self.client = client
self.closed = False
self._accepted = False
def setblocking(self, _value: bool) -> None:
return None
def accept(self) -> tuple[_FakeClientSocket, object]:
if self._accepted:
raise BlockingIOError
self._accepted = True
return self.client, None
def close(self) -> None:
self.closed = True
class _FakeLoopCore:
def __init__(self, outbound: Outbound) -> None:
self.outbound = outbound
self.accepted_client: str | None = None
self._outbound_sent = False
self._exit_checks = 0
def should_exit(self) -> bool:
self._exit_checks += 1
return self._exit_checks >= 3
def poll(self) -> None:
return None
def drain_events(self) -> list[Outbound]:
if self.accepted_client is not None and not self._outbound_sent:
self._outbound_sent = True
return [self.outbound]
return []
def accepted(self, client_id: str) -> None:
self.accepted_client = client_id
def disconnect(self, _client_id: str) -> None:
return None
def close(self) -> None:
return None
class _ShutdownRecordingExecutor:
def __init__(self) -> None:
self.shutdown_args: tuple[bool, bool] | None = None
def submit(self, _function: object, *_args: object) -> Future[object]:
return Future()
def shutdown(self, *, wait: bool = True, cancel_futures: bool = False) -> None:
self.shutdown_args = (wait, cancel_futures)
if wait:
raise AssertionError("shutdown waited for a hung worker")
class IdleTests(CoreTestCase):
def test_only_valid_requests_reset_idle_and_workers_suspend_then_restart_timer(self) -> None:
self.core.accepted("a")
self.clock.advance(899)
self.assertFalse(self.core.should_exit())
self.request("a", "hello", "h")
self.clock.advance(899)
self.assertFalse(self.core.should_exit())
self.request("a", "start", "s", "g")
self.clock.advance(901)
self.assertFalse(self.core.should_exit())
self.request("a", "cancel", "c", "g")
self.executor.run_next()
self.core.poll()
self.core.drain_events()
self.assertFalse(self.core.should_exit())
self.clock.advance(899)
self.assertFalse(self.core.should_exit())
self.clock.advance(1)
self.assertTrue(self.core.should_exit())
if __name__ == "__main__":
unittest.main()