personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

test(supervisor): stop schedule tests from writing fixture journal

The capture-wrapper pattern in tests/test_supervisor_schedule.py called
the real _task_queue.submit, which spawned a daemon that ran
`sol dream -v --day <YYYYMMDD>` against the tracked fixture journal.
That left `*_daily_dream.jsonl` and `daily.updated` files under
tests/fixtures/journal/chronicle/<date>/health/, dirtying git status
and blocking hopper runs.

Replace the wrapper with a single `submit_mock` fixture that binds a
unittest.mock.Mock onto the fresh _task_queue created by the autouse
reset_supervisor_state hook. Switch to one import style (`import
think.supervisor as mod`) and parametrize the chronological/cap and
wrong-tract/wrong-event duplicates. File drops from 347 to 197 lines
while preserving every behavior the original asserted.

Also add chronicle-aware counterparts to the four gitignore patterns
whose single-component `*` glob missed the chronicle/<date>/health/
nesting level (root .gitignore and tests/fixtures/journal/.gitignore)
so the next test of this shape fails loudly via git status instead of
silently leaking.

+132 -277
+1
.gitignore
··· 11 11 tests/fixtures/journal/agents/*/*.jsonl 12 12 tests/fixtures/journal/tokens/*.json 13 13 tests/fixtures/journal/*/health/*_dream.jsonl 14 + tests/fixtures/journal/chronicle/*/health/*_dream.jsonl 14 15 *.sqlite 15 16 *.sqlite-shm 16 17 *.sqlite-wal
+3
tests/fixtures/journal/.gitignore
··· 6 6 7 7 # Dream / supervisor runtime markers and logs 8 8 */health/daily.updated 9 + chronicle/*/health/daily.updated 9 10 */health/dream.log 11 + chronicle/*/health/dream.log 10 12 health/dream.log 11 13 task_log.txt 12 14 13 15 # Health check logs (timestamped + symlinks) from make dev 14 16 */health/*.log 17 + chronicle/*/health/*.log 15 18 health/*.log 16 19 17 20 # Service port files from make dev
+128 -277
tests/test_supervisor_schedule.py
··· 5 5 6 6 import os 7 7 from datetime import date 8 - from unittest.mock import patch 8 + from unittest.mock import MagicMock, Mock, call 9 9 10 + import pytest 10 11 11 - def test_handle_daily_tasks_submits_dream_on_day_change(mock_callosum): 12 - """Test that handle_daily_tasks submits dream via task queue when day changes.""" 13 - import think.supervisor as mod 14 - from think.supervisor import _daily_state, handle_daily_tasks 12 + import think.supervisor as mod 15 13 16 - _daily_state["last_day"] = date(2025, 1, 1) 17 14 18 - submitted = [] 19 - original_submit = mod._task_queue.submit 20 - 21 - def capture_submit(cmd, *args, **kwargs): 22 - submitted.append(cmd) 23 - return original_submit(cmd, *args, **kwargs) 15 + @pytest.fixture 16 + def submit_mock(monkeypatch): 17 + mock = Mock() 18 + monkeypatch.setattr(mod._task_queue, "submit", mock) 19 + return mock 24 20 25 - mod._task_queue.submit = capture_submit 26 21 27 - with ( 28 - patch("think.supervisor.datetime") as mock_datetime, 29 - patch("think.supervisor.updated_days", return_value=["20250101"]), 30 - ): 31 - mock_datetime.now.return_value.date.return_value = date(2025, 1, 2) 32 - handle_daily_tasks() 33 - 34 - assert len(submitted) == 1 35 - assert submitted[0][1] == "dream" 36 - 37 - assert _daily_state["last_day"] == date(2025, 1, 2) 38 - 39 - 40 - def test_handle_daily_tasks_no_spawn_same_day(mock_callosum): 41 - """Test that handle_daily_tasks does not submit dream on same day.""" 42 - import think.supervisor as mod 43 - from think.supervisor import _daily_state, handle_daily_tasks 44 - 45 - today = date(2025, 1, 2) 46 - _daily_state["last_day"] = today 47 - 48 - submitted = [] 49 - original_submit = mod._task_queue.submit 50 - 51 - def capture_submit(cmd, *args, **kwargs): 52 - submitted.append(cmd) 53 - return original_submit(cmd, *args, **kwargs) 22 + @pytest.fixture 23 + def set_today(monkeypatch): 24 + def _set_today(today): 25 + fake_datetime = Mock() 26 + fake_datetime.now.return_value.date.return_value = today 27 + monkeypatch.setattr(mod, "datetime", fake_datetime) 54 28 55 - mod._task_queue.submit = capture_submit 29 + return _set_today 56 30 57 - with patch("think.supervisor.datetime") as mock_datetime: 58 - mock_datetime.now.return_value.date.return_value = today 59 - handle_daily_tasks() 60 31 61 - assert len(submitted) == 0 32 + def daily_complete_message(**overrides): 33 + message = { 34 + "tract": "dream", 35 + "event": "daily_complete", 36 + "day": "20260318", 37 + "success": 3, 38 + "failed": 0, 39 + "duration_ms": 5000, 40 + } 41 + message.update(overrides) 42 + return message 62 43 63 44 64 - def test_handle_daily_tasks_submits_correct_command(mock_callosum): 65 - """Test that handle_daily_tasks submits sol dream without --refresh (dream auto-detects).""" 66 - import think.supervisor as mod 67 - from think.supervisor import _daily_state, handle_daily_tasks 68 - 69 - _daily_state["last_day"] = date(2025, 1, 1) 70 - 71 - submitted = [] 72 - original_submit = mod._task_queue.submit 73 - 74 - def capture_submit(cmd, *args, **kwargs): 75 - submitted.append(cmd) 76 - return original_submit(cmd, *args, **kwargs) 77 - 78 - mod._task_queue.submit = capture_submit 79 - 80 - with ( 81 - patch("think.supervisor.datetime") as mock_datetime, 82 - patch("think.supervisor.updated_days", return_value=["20250101"]), 83 - ): 84 - mock_datetime.now.return_value.date.return_value = date(2025, 1, 2) 85 - handle_daily_tasks() 86 - 87 - assert len(submitted) == 1 88 - cmd = submitted[0] 89 - assert cmd == ["sol", "dream", "-v", "--day", "20250101"] 90 - 91 - 92 - def test_handle_daily_tasks_skipped_in_remote_mode(mock_callosum): 93 - """Test that handle_daily_tasks skips entirely in remote mode.""" 94 - import think.supervisor as mod 95 - from think.supervisor import _daily_state, handle_daily_tasks 96 - 97 - _daily_state["last_day"] = date(2025, 1, 1) 98 - 99 - submitted = [] 100 - original_submit = mod._task_queue.submit 101 - 102 - def capture_submit(cmd, *args, **kwargs): 103 - submitted.append(cmd) 104 - return original_submit(cmd, *args, **kwargs) 105 - 106 - mod._task_queue.submit = capture_submit 107 - 108 - mod._is_remote_mode = True 109 - 110 - with patch("think.supervisor.datetime") as mock_datetime: 111 - mock_datetime.now.return_value.date.return_value = date(2025, 1, 2) 112 - handle_daily_tasks() 113 - 114 - assert len(submitted) == 0 115 - assert _daily_state["last_day"] == date(2025, 1, 1) 116 - 117 - 118 - def test_handle_daily_tasks_multiple_updated_days_chronological(mock_callosum): 119 - """Updated days are submitted oldest-first so yesterday is processed last.""" 120 - import think.supervisor as mod 121 - from think.supervisor import _daily_state, handle_daily_tasks 122 - 123 - _daily_state["last_day"] = date(2025, 1, 5) 124 - 125 - submitted = [] 126 - original_submit = mod._task_queue.submit 127 - 128 - def capture_submit(cmd, *args, **kwargs): 129 - submitted.append(cmd) 130 - return original_submit(cmd, *args, **kwargs) 131 - 132 - mod._task_queue.submit = capture_submit 133 - 134 - with ( 135 - patch("think.supervisor.datetime") as mock_datetime, 136 - patch( 137 - "think.supervisor.updated_days", 138 - return_value=["20250103", "20250104", "20250105"], 45 + @pytest.mark.parametrize( 46 + ("last_day", "today", "updated_days_return", "expected_days"), 47 + [ 48 + pytest.param( 49 + date(2025, 1, 1), 50 + date(2025, 1, 2), 51 + ["20250101"], 52 + ["20250101"], 53 + id="one-updated-day", 54 + ), 55 + pytest.param( 56 + date(2025, 1, 5), 57 + date(2025, 1, 6), 58 + ["20250103", "20250104", "20250105"], 59 + ["20250103", "20250104", "20250105"], 60 + id="multiple-updated-days", 61 + ), 62 + pytest.param( 63 + date(2025, 1, 10), 64 + date(2025, 1, 11), 65 + [ 66 + "20250104", 67 + "20250105", 68 + "20250106", 69 + "20250107", 70 + "20250108", 71 + "20250109", 72 + "20250110", 73 + ], 74 + ["20250107", "20250108", "20250109", "20250110"], 75 + id="max-updated-catchup", 139 76 ), 140 - ): 141 - mock_datetime.now.return_value.date.return_value = date(2025, 1, 6) 142 - handle_daily_tasks() 143 - 144 - assert len(submitted) == 3 145 - days = [cmd[cmd.index("--day") + 1] for cmd in submitted] 146 - assert days == ["20250103", "20250104", "20250105"] 77 + ], 78 + ) 79 + def test_handle_daily_tasks_submits_dreams_on_day_change( 80 + mock_callosum, 81 + monkeypatch, 82 + submit_mock, 83 + set_today, 84 + last_day, 85 + today, 86 + updated_days_return, 87 + expected_days, 88 + ): 89 + mod._daily_state["last_day"] = last_day 90 + set_today(today) 91 + monkeypatch.setattr(mod, "updated_days", lambda **kwargs: updated_days_return) 147 92 93 + mod.handle_daily_tasks() 148 94 149 - def test_handle_daily_tasks_caps_at_max_updated_catchup(mock_callosum): 150 - """Only the newest MAX_UPDATED_CATCHUP days are processed.""" 151 - import think.supervisor as mod 152 - from think.supervisor import _daily_state, handle_daily_tasks 153 - 154 - _daily_state["last_day"] = date(2025, 1, 10) 155 - 156 - submitted = [] 157 - original_submit = mod._task_queue.submit 158 - 159 - def capture_submit(cmd, *args, **kwargs): 160 - submitted.append(cmd) 161 - return original_submit(cmd, *args, **kwargs) 162 - 163 - mod._task_queue.submit = capture_submit 164 - 165 - all_updated = [ 166 - "20250104", 167 - "20250105", 168 - "20250106", 169 - "20250107", 170 - "20250108", 171 - "20250109", 172 - "20250110", 95 + assert submit_mock.call_args_list == [ 96 + call(["sol", "dream", "-v", "--day", day], day=day) for day in expected_days 173 97 ] 98 + assert mod._daily_state["last_day"] == today 174 99 175 - with ( 176 - patch("think.supervisor.datetime") as mock_datetime, 177 - patch("think.supervisor.updated_days", return_value=all_updated), 178 - ): 179 - mock_datetime.now.return_value.date.return_value = date(2025, 1, 11) 180 - handle_daily_tasks() 181 100 182 - # Only newest 4 183 - assert len(submitted) == 4 184 - days = [cmd[cmd.index("--day") + 1] for cmd in submitted] 185 - assert days == ["20250107", "20250108", "20250109", "20250110"] 101 + def test_no_spawn_same_day(mock_callosum, submit_mock, set_today): 102 + today = date(2025, 1, 2) 103 + mod._daily_state["last_day"] = today 104 + set_today(today) 186 105 106 + mod.handle_daily_tasks() 187 107 188 - def test_handle_daily_tasks_no_updated_days(mock_callosum): 189 - """No submissions when there are no updated days.""" 190 - import think.supervisor as mod 191 - from think.supervisor import _daily_state, handle_daily_tasks 108 + submit_mock.assert_not_called() 192 109 193 - _daily_state["last_day"] = date(2025, 1, 1) 194 110 195 - submitted = [] 196 - original_submit = mod._task_queue.submit 111 + def test_skipped_in_remote_mode(mock_callosum, submit_mock, set_today): 112 + mod._daily_state["last_day"] = date(2025, 1, 1) 113 + mod._is_remote_mode = True 114 + set_today(date(2025, 1, 2)) 197 115 198 - def capture_submit(cmd, *args, **kwargs): 199 - submitted.append(cmd) 200 - return original_submit(cmd, *args, **kwargs) 116 + mod.handle_daily_tasks() 201 117 202 - mod._task_queue.submit = capture_submit 118 + submit_mock.assert_not_called() 119 + assert mod._daily_state["last_day"] == date(2025, 1, 1) 203 120 204 - with ( 205 - patch("think.supervisor.datetime") as mock_datetime, 206 - patch("think.supervisor.updated_days", return_value=[]), 207 - ): 208 - mock_datetime.now.return_value.date.return_value = date(2025, 1, 2) 209 - handle_daily_tasks() 210 121 211 - assert len(submitted) == 0 212 - # State still advances even with no updated days 213 - assert _daily_state["last_day"] == date(2025, 1, 2) 214 - 122 + def test_advances_state_with_no_updated_days( 123 + mock_callosum, monkeypatch, submit_mock, set_today 124 + ): 125 + mod._daily_state["last_day"] = date(2025, 1, 1) 126 + set_today(date(2025, 1, 2)) 127 + monkeypatch.setattr(mod, "updated_days", lambda **kwargs: []) 215 128 216 - def test_handle_daily_tasks_excludes_today(mock_callosum): 217 - """Today is excluded from updated_days query.""" 218 - from think.supervisor import _daily_state, handle_daily_tasks 129 + mod.handle_daily_tasks() 219 130 220 - _daily_state["last_day"] = date(2025, 1, 1) 131 + submit_mock.assert_not_called() 132 + assert mod._daily_state["last_day"] == date(2025, 1, 2) 221 133 222 - captured_exclude = {} 223 134 224 - def fake_updated_days(exclude=None): 225 - captured_exclude["value"] = exclude 226 - return ["20250101"] 135 + def test_excludes_today(mock_callosum, monkeypatch, set_today): 136 + mod._daily_state["last_day"] = date(2025, 1, 1) 137 + set_today(date(2025, 1, 2)) 138 + updated_days = MagicMock(return_value=["20250101"]) 139 + monkeypatch.setattr(mod, "updated_days", updated_days) 227 140 228 - with ( 229 - patch("think.supervisor.datetime") as mock_datetime, 230 - patch("think.supervisor.updated_days", side_effect=fake_updated_days), 231 - ): 232 - mock_datetime.now.return_value.date.return_value = date(2025, 1, 2) 233 - handle_daily_tasks() 141 + mod.handle_daily_tasks() 234 142 235 - assert captured_exclude["value"] == {"20250102"} 143 + assert updated_days.call_args.kwargs["exclude"] == {"20250102"} 236 144 237 145 238 146 def test_handle_dream_daily_complete_submits_heartbeat( 239 - mock_callosum, tmp_path, monkeypatch 147 + mock_callosum, tmp_path, monkeypatch, submit_mock 240 148 ): 241 - """_handle_dream_daily_complete submits heartbeat when no PID file exists.""" 242 - import think.supervisor as mod 243 - 244 149 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 245 150 (tmp_path / "health").mkdir(exist_ok=True) 246 151 247 - submitted = [] 248 - original_submit = mod._task_queue.submit 152 + mod._handle_dream_daily_complete(daily_complete_message()) 249 153 250 - def capture_submit(cmd, *args, **kwargs): 251 - submitted.append(cmd) 252 - return original_submit(cmd, *args, **kwargs) 154 + submit_mock.assert_called_once_with(["sol", "heartbeat"]) 253 155 254 - mod._task_queue.submit = capture_submit 255 156 256 - message = { 257 - "tract": "dream", 258 - "event": "daily_complete", 259 - "day": "20260318", 260 - "success": 3, 261 - "failed": 0, 262 - "duration_ms": 5000, 263 - } 264 - mod._handle_dream_daily_complete(message) 265 - 266 - assert len(submitted) == 1 267 - assert submitted[0] == ["sol", "heartbeat"] 268 - 269 - 270 - def test_handle_dream_daily_complete_ignores_wrong_event( 271 - mock_callosum, tmp_path, monkeypatch 157 + @pytest.mark.parametrize( 158 + "message", 159 + [ 160 + pytest.param( 161 + {"tract": "supervisor", "event": "daily_complete"}, id="wrong-tract" 162 + ), 163 + pytest.param({"tract": "dream", "event": "started"}, id="wrong-event"), 164 + pytest.param({}, id="empty-message"), 165 + ], 166 + ) 167 + def test_ignores_non_dream_daily_complete( 168 + mock_callosum, tmp_path, monkeypatch, submit_mock, message 272 169 ): 273 - """_handle_dream_daily_complete ignores messages with wrong tract or event.""" 274 - import think.supervisor as mod 275 - 276 170 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 277 171 (tmp_path / "health").mkdir(exist_ok=True) 278 172 279 - submitted = [] 280 - original_submit = mod._task_queue.submit 173 + mod._handle_dream_daily_complete(message) 281 174 282 - def capture_submit(cmd, *args, **kwargs): 283 - submitted.append(cmd) 284 - return original_submit(cmd, *args, **kwargs) 175 + submit_mock.assert_not_called() 285 176 286 - mod._task_queue.submit = capture_submit 287 177 288 - mod._handle_dream_daily_complete({"tract": "supervisor", "event": "daily_complete"}) 289 - mod._handle_dream_daily_complete({"tract": "dream", "event": "started"}) 290 - mod._handle_dream_daily_complete({}) 291 - 292 - assert len(submitted) == 0 293 - 294 - 295 - def test_handle_dream_daily_complete_skips_when_pid_alive( 296 - mock_callosum, tmp_path, monkeypatch 297 - ): 298 - """_handle_dream_daily_complete does not submit when PID file shows running process.""" 299 - import think.supervisor as mod 300 - 178 + def test_skips_when_pid_alive(mock_callosum, tmp_path, monkeypatch, submit_mock): 301 179 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 302 180 health = tmp_path / "health" 303 181 health.mkdir(exist_ok=True) 304 - 305 182 (health / "heartbeat.pid").write_text(str(os.getpid())) 306 183 307 - submitted = [] 308 - original_submit = mod._task_queue.submit 309 - 310 - def capture_submit(cmd, *args, **kwargs): 311 - submitted.append(cmd) 312 - return original_submit(cmd, *args, **kwargs) 313 - 314 - mod._task_queue.submit = capture_submit 315 - 316 - message = {"tract": "dream", "event": "daily_complete", "day": "20260318"} 317 - mod._handle_dream_daily_complete(message) 184 + mod._handle_dream_daily_complete(daily_complete_message()) 318 185 319 - assert len(submitted) == 0 186 + submit_mock.assert_not_called() 320 187 321 188 322 - def test_handle_dream_daily_complete_proceeds_on_dead_pid( 323 - mock_callosum, tmp_path, monkeypatch 324 - ): 325 - """_handle_dream_daily_complete submits heartbeat when PID file has dead process.""" 326 - import think.supervisor as mod 327 - 189 + def test_proceeds_on_dead_pid(mock_callosum, tmp_path, monkeypatch, submit_mock): 328 190 monkeypatch.setenv("_SOLSTONE_JOURNAL_OVERRIDE", str(tmp_path)) 329 191 health = tmp_path / "health" 330 192 health.mkdir(exist_ok=True) 331 193 (health / "heartbeat.pid").write_text("99999999") 332 194 333 - submitted = [] 334 - original_submit = mod._task_queue.submit 195 + mod._handle_dream_daily_complete(daily_complete_message()) 335 196 336 - def capture_submit(cmd, *args, **kwargs): 337 - submitted.append(cmd) 338 - return original_submit(cmd, *args, **kwargs) 339 - 340 - mod._task_queue.submit = capture_submit 341 - 342 - message = {"tract": "dream", "event": "daily_complete", "day": "20260318"} 343 - mod._handle_dream_daily_complete(message) 344 - 345 - assert len(submitted) == 1 346 - assert submitted[0] == ["sol", "heartbeat"] 197 + submit_mock.assert_called_once_with(["sol", "heartbeat"])