2 files changed
@@ -24,6 +24,7 @@ | |||
| 24 | 24 | """ | |
| 25 | 25 | import fcntl | |
| 26 | 26 | import json | |
| 27 | + import logging | ||
| 27 | 28 | import re | |
| 28 | 29 | import shlex | |
| 29 | 30 | import time | |
@@ -33,6 +34,8 @@ | |||
| 33 | 34 | ||
| 34 | 35 | from sibyl._paths import get_system_state_dir | |
| 35 | 36 | ||
| 37 | + _log = logging.getLogger(__name__) | ||
| 38 | + | ||
| 36 | 39 | ||
| 37 | 40 | @contextmanager | |
| 38 | 41 | def _progress_lock(workspace_root: Path, timeout_sec: float = 30.0): | |
@@ -296,6 +299,11 @@ def topo_sort_layers(tasks: list[dict]) -> list[list[dict]]: | |||
| 296 | 299 | if dep in task_map: | |
| 297 | 300 | in_degree[t["id"]] += 1 | |
| 298 | 301 | children[dep].append(t["id"]) | |
| 302 | + else: | ||
| 303 | + _log.warning( | ||
| 304 | + "Task %s depends on non-existent task %s — treating as satisfied", | ||
| 305 | + t["id"], dep, | ||
| 306 | + ) | ||
| 299 | 307 | ||
| 300 | 308 | layers = [] | |
| 301 | 309 | queue = deque([tid for tid, deg in in_degree.items() if deg == 0]) | |
@@ -1183,6 +1183,39 @@ def test_script_includes_dispatch_logic(self): | |||
| 1183 | 1183 | assert "DISPATCH" in script | |
| 1184 | 1184 | ||
| 1185 | 1185 | ||
| 1186 | + # ══════════════════════════════════════════════ | ||
| 1187 | + # Warn on missing dependencies | ||
| 1188 | + # ══════════════════════════════════════════════ | ||
| 1189 | + | ||
| 1190 | + def test_topo_sort_warns_on_missing_dependency(caplog): | ||
| 1191 | + """Should log a warning when a task depends on a non-existent task.""" | ||
| 1192 | + import logging | ||
| 1193 | + tasks = [ | ||
| 1194 | + {"id": "a", "depends_on": ["ghost"]}, | ||
| 1195 | + {"id": "b", "depends_on": []}, | ||
| 1196 | + ] | ||
| 1197 | + with caplog.at_level(logging.WARNING): | ||
| 1198 | + layers = topo_sort_layers(tasks) | ||
| 1199 | + assert any("ghost" in r.message for r in caplog.records) | ||
| 1200 | + # 'a' should still be included (treat missing dep as satisfied) | ||
| 1201 | + all_ids = {t["id"] for layer in layers for t in layer} | ||
| 1202 | + assert "a" in all_ids | ||
| 1203 | + assert "b" in all_ids | ||
| 1204 | + | ||
| 1205 | + | ||
| 1206 | + def test_topo_sort_no_warning_for_valid_deps(caplog): | ||
| 1207 | + """Should not warn when all dependencies exist.""" | ||
| 1208 | + import logging | ||
| 1209 | + tasks = [ | ||
| 1210 | + {"id": "a", "depends_on": []}, | ||
| 1211 | + {"id": "b", "depends_on": ["a"]}, | ||
| 1212 | + ] | ||
| 1213 | + with caplog.at_level(logging.WARNING): | ||
| 1214 | + layers = topo_sort_layers(tasks) | ||
| 1215 | + assert not any("non-existent" in r.message for r in caplog.records) | ||
| 1216 | + assert len(layers) == 2 | ||
| 1217 | + | ||
| 1218 | + | ||
| 1186 | 1219 | # ══════════════════════════════════════════════ | |
| 1187 | 1220 | # TTL-based stale lease cleanup | |
| 1188 | 1221 | # ══════════════════════════════════════════════ | |
0 commit comments