|
2 | 2 | from __future__ import annotations |
3 | 3 |
|
4 | 4 | import asyncio |
| 5 | +import queue as _queue |
5 | 6 | from typing import Any |
6 | 7 | from unittest.mock import AsyncMock, MagicMock |
7 | 8 |
|
8 | 9 | import pytest |
9 | 10 |
|
10 | 11 | from marimo._runtime.commands import ( |
11 | 12 | ExecuteCellsCommand, |
| 13 | + ModelCommand, |
12 | 14 | StopKernelCommand, |
13 | 15 | UpdateUIElementCommand, |
14 | 16 | ) |
15 | 17 | from marimo._runtime.kernel_lifecycle import ( |
16 | 18 | asyncio_queue_reader, |
| 19 | + drain_stale, |
17 | 20 | listen_messages, |
| 21 | + make_control_enqueuer, |
18 | 22 | ) |
19 | | -from marimo._types.ids import CellId_t, UIElementId |
| 23 | +from marimo._types.ids import CellId_t, UIElementId, WidgetModelId |
20 | 24 |
|
21 | 25 |
|
22 | 26 | @pytest.fixture |
@@ -151,3 +155,76 @@ async def test_listen_messages_merges_ui_updates( |
151 | 155 | dispatched = kernel.handle_message.await_args.args[0] |
152 | 156 | assert isinstance(dispatched, UpdateUIElementCommand) |
153 | 157 | assert dispatched.values == [2] |
| 158 | + |
| 159 | + |
| 160 | +@pytest.mark.parametrize( |
| 161 | + "queue_factory", |
| 162 | + [asyncio.Queue, _queue.Queue], |
| 163 | + ids=["asyncio", "threading"], |
| 164 | +) |
| 165 | +def test_drain_stale_returns_latest_when_queue_empty( |
| 166 | + queue_factory: Any, |
| 167 | +) -> None: |
| 168 | + q = queue_factory() |
| 169 | + latest = _execute("only") |
| 170 | + assert drain_stale(q, latest=latest) is latest |
| 171 | + |
| 172 | + |
| 173 | +@pytest.mark.parametrize( |
| 174 | + "queue_factory", |
| 175 | + [asyncio.Queue, _queue.Queue], |
| 176 | + ids=["asyncio", "threading"], |
| 177 | +) |
| 178 | +def test_drain_stale_returns_newest_pending(queue_factory: Any) -> None: |
| 179 | + q = queue_factory() |
| 180 | + initial = _execute("initial") |
| 181 | + newer = _execute("newer") |
| 182 | + newest = _execute("newest") |
| 183 | + q.put_nowait(newer) |
| 184 | + q.put_nowait(newest) |
| 185 | + |
| 186 | + assert drain_stale(q, latest=initial) is newest |
| 187 | + # Drained: nothing else remains. |
| 188 | + assert q.empty() |
| 189 | + |
| 190 | + |
| 191 | +def test_make_control_enqueuer_routes_plain_command_to_control_only() -> None: |
| 192 | + control: asyncio.Queue[Any] = asyncio.Queue() |
| 193 | + ui: asyncio.Queue[Any] = asyncio.Queue() |
| 194 | + enqueue = make_control_enqueuer(control, ui) |
| 195 | + |
| 196 | + cmd = _execute() |
| 197 | + enqueue(cmd) |
| 198 | + |
| 199 | + assert control.get_nowait() is cmd |
| 200 | + assert ui.empty() |
| 201 | + |
| 202 | + |
| 203 | +def test_make_control_enqueuer_mirrors_ui_element_command() -> None: |
| 204 | + control: asyncio.Queue[Any] = asyncio.Queue() |
| 205 | + ui: asyncio.Queue[Any] = asyncio.Queue() |
| 206 | + enqueue = make_control_enqueuer(control, ui) |
| 207 | + |
| 208 | + cmd = _ui_update("u", 1) |
| 209 | + enqueue(cmd) |
| 210 | + |
| 211 | + assert control.get_nowait() is cmd |
| 212 | + assert ui.get_nowait() is cmd |
| 213 | + |
| 214 | + |
| 215 | +def test_make_control_enqueuer_mirrors_model_command() -> None: |
| 216 | + from marimo._runtime.commands import ModelUpdateMessage |
| 217 | + |
| 218 | + control: asyncio.Queue[Any] = asyncio.Queue() |
| 219 | + ui: asyncio.Queue[Any] = asyncio.Queue() |
| 220 | + enqueue = make_control_enqueuer(control, ui) |
| 221 | + |
| 222 | + cmd = ModelCommand( |
| 223 | + model_id=WidgetModelId("m1"), |
| 224 | + message=ModelUpdateMessage(state={"x": 1}, buffer_paths=[]), |
| 225 | + buffers=[], |
| 226 | + ) |
| 227 | + enqueue(cmd) |
| 228 | + |
| 229 | + assert control.get_nowait() is cmd |
| 230 | + assert ui.get_nowait() is cmd |
0 commit comments