desiree commited on
Commit
ec78edd
·
verified ·
1 Parent(s): 615843b

Phase 1: graph + scheduler tests

Browse files
Files changed (1) hide show
  1. tests/unit/test_graph_scheduler.py +80 -0
tests/unit/test_graph_scheduler.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pytest
2
+
3
+ from spi.compiler.production_graph import CycleError, ProductionGraph
4
+ from spi.renderers.base import RendererError, request_hash
5
+ from spi.renderers.mock import MockVideoRenderer
6
+ from spi.scheduler.scheduler import Scheduler
7
+ from spi.schemas.production import GenerationJob
8
+
9
+
10
+ def _job(jid, deps=None, priority=5):
11
+ return GenerationJob(job_id=jid, depends_on=deps or [], priority=priority,
12
+ request={"prompt": jid})
13
+
14
+
15
+ def test_topo_respects_deps_and_priority():
16
+ g = ProductionGraph([_job("A"), _job("B"), _job("C", deps=["A"], priority=1),
17
+ _job("D", deps=["A", "C"])])
18
+ order = g.topo_order()
19
+ assert order.index("A") < order.index("C") < order.index("D")
20
+ assert order.index("A") < order.index("B")
21
+
22
+
23
+ def test_cycle_detected():
24
+ g = ProductionGraph([_job("A"), _job("B"), _job("C")])
25
+ g.add_edge("A", "B")
26
+ g.add_edge("B", "C")
27
+ g.add_edge("C", "A")
28
+ with pytest.raises(CycleError):
29
+ g.topo_order()
30
+ assert g.validate() != []
31
+
32
+
33
+ def test_unknown_dependency_rejected():
34
+ g = ProductionGraph([_job("A")])
35
+ g.jobs["A"].depends_on.append("GHOST")
36
+ with pytest.raises(ValueError):
37
+ g.validate()
38
+
39
+
40
+ def test_ready_jobs_ordering():
41
+ g = ProductionGraph([_job("low", priority=9), _job("high", priority=1)])
42
+ assert g.ready_jobs()[0] == "high"
43
+ g.mark("high", "done")
44
+ assert g.ready_jobs() == ["low"]
45
+
46
+
47
+ def test_request_hash_stable():
48
+ r1 = {"prompt": "x", "seed": 1}
49
+ r2 = {"seed": 1, "prompt": "x"}
50
+ assert request_hash(r1) == request_hash(r2)
51
+
52
+
53
+ def test_mock_renderer_caches_identical_requests():
54
+ r = MockVideoRenderer()
55
+ a1 = r.generate({"prompt": "same", "num_frames": 48, "seed": 7})
56
+ a2 = r.generate({"prompt": "same", "seed": 1, "num_frames": 48}) # same canonical dict
57
+ assert a1["request_hash"] == a2["request_hash"]
58
+ assert a1["cached"] is False and a2["cached"] is True
59
+
60
+
61
+ def test_scheduler_executes_all_and_records():
62
+ g = ProductionGraph([_job("A"), _job("B", deps=["A"])])
63
+ summary = Scheduler(g, MockVideoRenderer()).run()
64
+ assert summary["done"] == 2 and summary["failed"] == 0
65
+ assert all(j.status == "done" for j in g.jobs.values())
66
+ assert all(j.runtime_s is not None for j in g.jobs.values())
67
+ assert g.jobs["A"].artifact_ref.startswith("mock://video/")
68
+
69
+
70
+ def test_scheduler_retries_then_fails():
71
+ class ExplodingRenderer(MockVideoRenderer):
72
+ def generate(self, request):
73
+ raise RendererError("simulated OOM")
74
+
75
+ g = ProductionGraph([_job("A")])
76
+ summary = Scheduler(g, ExplodingRenderer(), max_retries=2).run()
77
+ assert summary["failed"] == 1
78
+ assert g.jobs["A"].status == "failed"
79
+ assert g.jobs["A"].retries == 3 # 2 retries + initial attempt
80
+ assert "OOM" in g.jobs["A"].error