forked from genewildish/Mainline
feat: Complete pipeline hot-rebuild implementation with acceptance tests
- Implements pipeline hot-rebuild with state preservation (issue #43) - Adds auto-injection of MVP stages for missing capabilities - Adds radial camera mode for polar coordinate scanning - Adds afterimage and motionblur effects using framebuffer history - Adds comprehensive acceptance tests for camera modes and pipeline rebuild - Updates presets.toml with new effect configurations Related to: #35 (Pipeline Mutation API epic) Closes: #43, #44, #45
This commit is contained in:
@@ -129,7 +129,7 @@ class TestPipeline:
|
||||
|
||||
pipeline.add_stage("source", mock_source)
|
||||
pipeline.add_stage("display", mock_display)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
assert pipeline._initialized is True
|
||||
assert "source" in pipeline.execution_order
|
||||
@@ -182,7 +182,7 @@ class TestPipeline:
|
||||
pipeline.add_stage("source", mock_source)
|
||||
pipeline.add_stage("effect", mock_effect)
|
||||
pipeline.add_stage("display", mock_display)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
result = pipeline.execute(None)
|
||||
|
||||
@@ -218,7 +218,7 @@ class TestPipeline:
|
||||
|
||||
pipeline.add_stage("source", mock_source)
|
||||
pipeline.add_stage("failing", mock_failing)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
result = pipeline.execute(None)
|
||||
|
||||
@@ -254,7 +254,7 @@ class TestPipeline:
|
||||
|
||||
pipeline.add_stage("source", mock_source)
|
||||
pipeline.add_stage("optional", mock_optional)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
result = pipeline.execute(None)
|
||||
|
||||
@@ -302,7 +302,7 @@ class TestCapabilityBasedDependencies:
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_stage("headlines", SourceStage())
|
||||
pipeline.add_stage("render", RenderStage())
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
assert "headlines" in pipeline.execution_order
|
||||
assert "render" in pipeline.execution_order
|
||||
@@ -334,7 +334,7 @@ class TestCapabilityBasedDependencies:
|
||||
pipeline.add_stage("render", RenderStage())
|
||||
|
||||
try:
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
raise AssertionError("Should have raised StageError")
|
||||
except StageError as e:
|
||||
assert "Missing capabilities" in e.message
|
||||
@@ -394,7 +394,7 @@ class TestCapabilityBasedDependencies:
|
||||
pipeline.add_stage("headlines", SourceA())
|
||||
pipeline.add_stage("poetry", SourceB())
|
||||
pipeline.add_stage("display", DisplayStage())
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
assert pipeline.execution_order[0] == "headlines"
|
||||
|
||||
@@ -791,7 +791,7 @@ class TestFullPipeline:
|
||||
pipeline.add_stage("b", StageB())
|
||||
|
||||
try:
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
raise AssertionError("Should detect circular dependency")
|
||||
except Exception:
|
||||
pass
|
||||
@@ -815,7 +815,7 @@ class TestPipelineMetrics:
|
||||
config = PipelineConfig(enable_metrics=True)
|
||||
pipeline = Pipeline(config=config)
|
||||
pipeline.add_stage("dummy", DummyStage())
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
pipeline.execute("test_data")
|
||||
|
||||
@@ -838,7 +838,7 @@ class TestPipelineMetrics:
|
||||
config = PipelineConfig(enable_metrics=False)
|
||||
pipeline = Pipeline(config=config)
|
||||
pipeline.add_stage("dummy", DummyStage())
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
pipeline.execute("test_data")
|
||||
|
||||
@@ -860,7 +860,7 @@ class TestPipelineMetrics:
|
||||
config = PipelineConfig(enable_metrics=True)
|
||||
pipeline = Pipeline(config=config)
|
||||
pipeline.add_stage("dummy", DummyStage())
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
pipeline.execute("test1")
|
||||
pipeline.execute("test2")
|
||||
@@ -964,7 +964,7 @@ class TestOverlayStages:
|
||||
pipeline.add_stage("overlay_a", OverlayStageA())
|
||||
pipeline.add_stage("overlay_b", OverlayStageB())
|
||||
pipeline.add_stage("regular", RegularStage())
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
overlays = pipeline.get_overlay_stages()
|
||||
assert len(overlays) == 2
|
||||
@@ -1006,7 +1006,7 @@ class TestOverlayStages:
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_stage("regular", RegularStage())
|
||||
pipeline.add_stage("overlay", OverlayStage())
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
pipeline.execute("data")
|
||||
|
||||
@@ -1070,7 +1070,7 @@ class TestOverlayStages:
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_stage("test", TestStage())
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
assert pipeline.get_stage_type("test") == "overlay"
|
||||
|
||||
@@ -1092,7 +1092,7 @@ class TestOverlayStages:
|
||||
|
||||
pipeline = Pipeline()
|
||||
pipeline.add_stage("test", TestStage())
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
assert pipeline.get_render_order("test") == 42
|
||||
|
||||
@@ -1142,7 +1142,7 @@ class TestInletOutletTypeValidation:
|
||||
pipeline.add_stage("consumer", ConsumerStage())
|
||||
|
||||
with pytest.raises(StageError) as exc_info:
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
assert "Type mismatch" in str(exc_info.value)
|
||||
assert "TEXT_BUFFER" in str(exc_info.value)
|
||||
@@ -1190,7 +1190,7 @@ class TestInletOutletTypeValidation:
|
||||
pipeline.add_stage("consumer", ConsumerStage())
|
||||
|
||||
# Should not raise
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
def test_any_type_accepts_everything(self):
|
||||
"""DataType.ANY accepts any upstream type."""
|
||||
@@ -1234,7 +1234,7 @@ class TestInletOutletTypeValidation:
|
||||
pipeline.add_stage("consumer", ConsumerStage())
|
||||
|
||||
# Should not raise because consumer accepts ANY
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
def test_multiple_compatible_types(self):
|
||||
"""Stage can declare multiple inlet types."""
|
||||
@@ -1278,7 +1278,7 @@ class TestInletOutletTypeValidation:
|
||||
pipeline.add_stage("consumer", ConsumerStage())
|
||||
|
||||
# Should not raise because consumer accepts SOURCE_ITEMS
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
def test_display_must_accept_text_buffer(self):
|
||||
"""Display stages must accept TEXT_BUFFER type."""
|
||||
@@ -1302,7 +1302,7 @@ class TestInletOutletTypeValidation:
|
||||
pipeline.add_stage("display", BadDisplayStage())
|
||||
|
||||
with pytest.raises(StageError) as exc_info:
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
assert "display" in str(exc_info.value).lower()
|
||||
|
||||
@@ -1349,7 +1349,7 @@ class TestPipelineMutation:
|
||||
"""add_stage() initializes stage when pipeline already initialized."""
|
||||
pipeline = Pipeline()
|
||||
mock_stage = self._create_mock_stage("test")
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
pipeline._initialized = True
|
||||
|
||||
pipeline.add_stage("test", mock_stage, initialize=True)
|
||||
@@ -1478,7 +1478,7 @@ class TestPipelineMutation:
|
||||
pipeline.add_stage("a", stage_a, initialize=False)
|
||||
pipeline.add_stage("b", stage_b, initialize=False)
|
||||
pipeline.add_stage("c", stage_c, initialize=False)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
result = pipeline.move_stage("a", after="c")
|
||||
|
||||
@@ -1497,7 +1497,7 @@ class TestPipelineMutation:
|
||||
pipeline.add_stage("a", stage_a, initialize=False)
|
||||
pipeline.add_stage("b", stage_b, initialize=False)
|
||||
pipeline.add_stage("c", stage_c, initialize=False)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
result = pipeline.move_stage("c", before="a")
|
||||
|
||||
@@ -1512,7 +1512,7 @@ class TestPipelineMutation:
|
||||
stage = self._create_mock_stage("test")
|
||||
|
||||
pipeline.add_stage("test", stage, initialize=False)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
result = pipeline.move_stage("nonexistent", after="test")
|
||||
|
||||
@@ -1613,7 +1613,7 @@ class TestPipelineMutation:
|
||||
|
||||
pipeline.add_stage("s1", stage1, initialize=False)
|
||||
pipeline.add_stage("s2", stage2, initialize=False)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
info = pipeline.get_pipeline_info()
|
||||
|
||||
@@ -1640,7 +1640,7 @@ class TestPipelineMutation:
|
||||
pipeline.add_stage("source", source, initialize=False)
|
||||
pipeline.add_stage("effect", effect, initialize=False)
|
||||
pipeline.add_stage("display", display, initialize=False)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
assert pipeline.execution_order == ["source", "effect", "display"]
|
||||
|
||||
@@ -1664,7 +1664,7 @@ class TestPipelineMutation:
|
||||
|
||||
pipeline.add_stage("source", source, initialize=False)
|
||||
pipeline.add_stage("display", display, initialize=False)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
|
||||
new_stage = self._create_mock_stage(
|
||||
"effect", "effect", capabilities={"effect"}, dependencies={"source"}
|
||||
@@ -1757,7 +1757,7 @@ class TestPipelineMutation:
|
||||
pipeline.add_stage("source", TestSource(), initialize=False)
|
||||
pipeline.add_stage("effect", TestEffect(), initialize=False)
|
||||
pipeline.add_stage("display", TestDisplay(), initialize=False)
|
||||
pipeline.build()
|
||||
pipeline.build(auto_inject=False)
|
||||
pipeline.initialize()
|
||||
|
||||
result = pipeline.execute(None)
|
||||
|
||||
Reference in New Issue
Block a user