|
3 | 3 |
|
4 | 4 | import asyncio |
5 | 5 | import os |
6 | | -import sys |
| 6 | +import shutil |
7 | 7 | from pathlib import Path |
8 | 8 | from tempfile import NamedTemporaryFile |
9 | 9 |
|
10 | 10 | import pytest |
11 | 11 |
|
| 12 | +from marimo._dependencies.dependencies import DependencyManager |
12 | 13 | from marimo._utils.file_watcher import FileWatcherManager, PollingFileWatcher |
13 | 14 |
|
14 | 15 |
|
@@ -45,10 +46,6 @@ async def test_callback(path: Path): |
45 | 46 | assert callback_calls[0] == tmp_path |
46 | 47 |
|
47 | 48 |
|
48 | | -@pytest.mark.skipif( |
49 | | - sys.version_info < (3, 10), |
50 | | - reason="File watcher tests require Python 3.10+", |
51 | | -) |
52 | 49 | async def test_file_watcher_manager() -> None: |
53 | 50 | # Create two temporary files |
54 | 51 | with ( |
@@ -151,3 +148,60 @@ async def callback3(path: Path) -> None: |
151 | 148 | manager.stop_all() |
152 | 149 | os.remove(tmp_path1) |
153 | 150 | os.remove(tmp_path2) |
| 151 | + |
| 152 | + |
| 153 | +# This test is not working and watchdog makes CI hang in other areas |
| 154 | +# So we test this manually with `uv run --with=watchdog marimo edit nb.py --watch` |
| 155 | +@pytest.mark.xfail(reason="Test not working") |
| 156 | +@pytest.mark.skipif( |
| 157 | + not DependencyManager.watchdog.has(), |
| 158 | + reason="watchdog not installed", |
| 159 | +) |
| 160 | +async def test_watchdog_file_moved() -> None: |
| 161 | + """Test that watchdog detects when a temp file is moved to the target path. |
| 162 | +
|
| 163 | + This simulates editors like Claude Code that save by creating a temp file |
| 164 | + and then moving it to the target location. |
| 165 | + """ |
| 166 | + from marimo._utils.file_watcher import _create_watchdog |
| 167 | + |
| 168 | + with NamedTemporaryFile(delete=False) as tmp_file: |
| 169 | + tmp_path = Path(tmp_file.name) |
| 170 | + tmp_file.write(b"initial content") |
| 171 | + |
| 172 | + callback_calls: list[Path] = [] |
| 173 | + |
| 174 | + async def test_callback(path: Path) -> None: |
| 175 | + callback_calls.append(path) |
| 176 | + |
| 177 | + try: |
| 178 | + # Create watcher |
| 179 | + loop = asyncio.get_event_loop() |
| 180 | + watcher = _create_watchdog(tmp_path, test_callback, loop) |
| 181 | + watcher.start() |
| 182 | + |
| 183 | + # Wait for watcher to be ready |
| 184 | + await asyncio.sleep(0.2) |
| 185 | + |
| 186 | + # Simulate Claude Code save pattern: create temp file and move it |
| 187 | + temp_save_path = tmp_path.parent / f"{tmp_path.name}.tmp.12345" |
| 188 | + with open(temp_save_path, "w") as f: # noqa: ASYNC230 |
| 189 | + f.write("modified content") |
| 190 | + |
| 191 | + # Move temp file to target (simulating atomic save) |
| 192 | + shutil.move(str(temp_save_path), str(tmp_path)) |
| 193 | + |
| 194 | + # Wait for the watcher to detect the move |
| 195 | + await asyncio.sleep(0.3) |
| 196 | + |
| 197 | + # Stop watcher |
| 198 | + watcher.stop() |
| 199 | + |
| 200 | + # Assert that the callback was called |
| 201 | + assert len(callback_calls) >= 1 |
| 202 | + assert callback_calls[0] == tmp_path |
| 203 | + |
| 204 | + finally: |
| 205 | + # Cleanup |
| 206 | + if tmp_path.exists(): |
| 207 | + os.remove(tmp_path) |
0 commit comments