Skip to content

Commit 493a3d6

Browse files
committed
Improve CLI coverage for Redis 6.2 runs
1 parent e78395d commit 493a3d6

File tree

3 files changed

+207
-6
lines changed

3 files changed

+207
-6
lines changed

src/docket/worker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ async def get_new_deliveries(redis: Redis) -> RedisReadGroupResponse:
298298
else int(self.minimum_check_interval.total_seconds() * 1000),
299299
count=available_slots,
300300
)
301-
if is_memory and not result:
301+
if is_memory and not result: # pragma: no branch - memory backend throttle
302302
await asyncio.sleep(self.minimum_check_interval.total_seconds())
303303
return result
304304

tests/cli/test_snapshot.py

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import asyncio
22
from datetime import datetime, timedelta, timezone
3+
from types import TracebackType
34

45
import pytest
56
from pytest import MonkeyPatch
7+
from rich.table import Table
68
from typer.testing import CliRunner
79

810
from docket import tasks
9-
from docket.cli import app, relative_time
10-
from docket.docket import Docket
11+
from docket.cli import app, relative_time, snapshot as snapshot_command
12+
from docket.docket import Docket, DocketSnapshot, RunningExecution, WorkerInfo
13+
from docket.execution import Execution
1114
from docket.worker import Worker
1215

1316

@@ -249,6 +252,97 @@ async def test_snapshot_with_stats_flag_mixed_tasks(docket: Docket, runner: CliR
249252
await worker_running
250253

251254

255+
def test_snapshot_cli_renders_stats_without_backend(monkeypatch: MonkeyPatch):
256+
"""snapshot CLI should render tables even when the docket interaction is stubbed."""
257+
now = datetime(2023, 1, 1, 12, 0, tzinfo=timezone.utc)
258+
259+
running_base = Execution(
260+
function=tasks.sleep,
261+
args=(1,),
262+
kwargs={},
263+
when=now,
264+
key="running-task",
265+
attempt=1,
266+
)
267+
running = RunningExecution(
268+
execution=running_base,
269+
worker="worker-1",
270+
started=now - timedelta(seconds=5),
271+
)
272+
273+
future_execution = Execution(
274+
function=tasks.trace,
275+
args=("hello!",),
276+
kwargs={},
277+
when=now + timedelta(seconds=10),
278+
key="future-task",
279+
attempt=1,
280+
)
281+
282+
snapshot_obj = DocketSnapshot(
283+
taken=now,
284+
total_tasks=2,
285+
future=[future_execution],
286+
running=[running],
287+
workers=[
288+
WorkerInfo(
289+
name="worker-1",
290+
last_seen=now - timedelta(seconds=2),
291+
tasks={"sleep", "trace"},
292+
)
293+
],
294+
)
295+
296+
registered: list[str] = []
297+
298+
class FakeDocket:
299+
def __init__(self, name: str, url: str) -> None:
300+
self.name = name
301+
self.url = url
302+
303+
async def __aenter__(self) -> "FakeDocket":
304+
return self
305+
306+
async def __aexit__(
307+
self,
308+
exc_type: type[BaseException] | None,
309+
exc: BaseException | None,
310+
tb: TracebackType | None,
311+
) -> bool:
312+
return False
313+
314+
def register_collection(self, task_path: str) -> None:
315+
registered.append(task_path)
316+
317+
async def snapshot(self) -> DocketSnapshot:
318+
return snapshot_obj
319+
320+
monkeypatch.setattr("docket.cli.Docket", FakeDocket)
321+
322+
class RecordingConsole:
323+
def __init__(self) -> None:
324+
self.calls: list[tuple[tuple[object, ...], dict[str, object]]] = []
325+
326+
def print(self, *args: object, **kwargs: object) -> None:
327+
self.calls.append((args, kwargs))
328+
329+
recorder = RecordingConsole()
330+
monkeypatch.setattr("docket.cli.Console", lambda: recorder)
331+
332+
snapshot_command(stats=True)
333+
334+
# The default task collection should be registered and both tables should be printed.
335+
assert registered == ["docket.tasks:standard_tasks"]
336+
tables = [args[0] for args, _ in recorder.calls if args]
337+
assert any(
338+
isinstance(obj, Table) and "Docket:" in (obj.title or "") for obj in tables
339+
)
340+
assert any(
341+
isinstance(obj, Table) and obj.title == "Task Count Statistics by Function"
342+
for obj in tables
343+
)
344+
345+
252346
async def test_snapshot_with_stats_shows_timestamp_columns(
253347
docket: Docket, runner: CliRunner
254348
):

tests/cli/test_workers.py

Lines changed: 110 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import asyncio
2-
from datetime import timedelta
2+
from datetime import datetime, timedelta, timezone
3+
from types import TracebackType
34

5+
from pytest import MonkeyPatch
46
from typer.testing import CliRunner
57

6-
from docket.cli import app
7-
from docket.docket import Docket
8+
from docket.cli import app, list_workers, print_workers, workers_for_task
9+
from docket.docket import Docket, WorkerInfo
810
from docket.worker import Worker
911

1012

@@ -63,3 +65,108 @@ async def test_list_workers_for_task(docket: Docket, runner: CliRunner):
6365

6466
assert "worker-1" in result.output
6567
assert "worker-2" in result.output
68+
69+
70+
def test_print_workers_highlight(monkeypatch: MonkeyPatch):
71+
"""print_workers should render a table for the supplied workers."""
72+
now = datetime.now(timezone.utc)
73+
workers = [
74+
WorkerInfo(name="worker-1", last_seen=now, tasks={"trace", "sleep"}),
75+
]
76+
77+
class RecordingConsole:
78+
def __init__(self) -> None:
79+
self.objects: list[object] = []
80+
81+
def print(self, obj: object) -> None:
82+
self.objects.append(obj)
83+
84+
recorder = RecordingConsole()
85+
monkeypatch.setattr("docket.cli.Console", lambda: recorder)
86+
87+
print_workers("demo-docket", workers, highlight_task="sleep")
88+
89+
assert recorder.objects, "Console.print should be invoked"
90+
table = recorder.objects[0]
91+
assert getattr(table, "title", "") == "Workers in Docket: demo-docket"
92+
93+
94+
def test_list_workers_delegates_to_print(monkeypatch: MonkeyPatch):
95+
"""list_workers should fetch workers and delegate to print_workers."""
96+
now = datetime.now(timezone.utc)
97+
workers = [WorkerInfo(name="worker-1", last_seen=now, tasks={"trace"})]
98+
captured: list[tuple[str, list[WorkerInfo], str | None]] = []
99+
100+
def fake_print(
101+
docket_name: str,
102+
worker_list: list[WorkerInfo],
103+
highlight_task: str | None = None,
104+
) -> None:
105+
captured.append((docket_name, worker_list, highlight_task))
106+
107+
class FakeDocket:
108+
def __init__(self, name: str, url: str) -> None:
109+
self.name = name
110+
self.url = url
111+
112+
async def __aenter__(self) -> "FakeDocket":
113+
return self
114+
115+
async def __aexit__(
116+
self,
117+
exc_type: type[BaseException] | None,
118+
exc: BaseException | None,
119+
tb: TracebackType | None,
120+
) -> bool:
121+
return False
122+
123+
async def workers(self) -> list[WorkerInfo]:
124+
return workers
125+
126+
monkeypatch.setattr("docket.cli.print_workers", fake_print)
127+
monkeypatch.setattr("docket.cli.Docket", FakeDocket)
128+
129+
list_workers(docket_="demo", url="redis://localhost:6379/0")
130+
131+
assert captured == [("demo", workers, None)]
132+
133+
134+
def test_workers_for_task_delegates_to_print(monkeypatch: MonkeyPatch):
135+
"""workers_for_task should fetch filtered workers and highlight the task."""
136+
now = datetime.now(timezone.utc)
137+
workers = [WorkerInfo(name="worker-2", last_seen=now, tasks={"trace"})]
138+
captured: list[tuple[str, list[WorkerInfo], str | None]] = []
139+
140+
def fake_print(
141+
docket_name: str,
142+
worker_list: list[WorkerInfo],
143+
highlight_task: str | None = None,
144+
) -> None:
145+
captured.append((docket_name, worker_list, highlight_task))
146+
147+
class FakeDocket:
148+
def __init__(self, name: str, url: str) -> None:
149+
self.name = name
150+
self.url = url
151+
152+
async def __aenter__(self) -> "FakeDocket":
153+
return self
154+
155+
async def __aexit__(
156+
self,
157+
exc_type: type[BaseException] | None,
158+
exc: BaseException | None,
159+
tb: TracebackType | None,
160+
) -> bool:
161+
return False
162+
163+
async def task_workers(self, task: str) -> list[WorkerInfo]:
164+
assert task == "trace"
165+
return workers
166+
167+
monkeypatch.setattr("docket.cli.print_workers", fake_print)
168+
monkeypatch.setattr("docket.cli.Docket", FakeDocket)
169+
170+
workers_for_task("trace", docket_="demo", url="redis://localhost:6379/0")
171+
172+
assert captured == [("demo", workers, "trace")]

0 commit comments

Comments
 (0)