"""Integration test: FrameBufferStage in the pipeline.""" import queue from engine.data_sources.sources import ListDataSource, SourceItem from engine.effects.types import EffectConfig from engine.pipeline import Pipeline, PipelineConfig from engine.pipeline.adapters import ( DataSourceStage, DisplayStage, SourceItemsToBufferStage, ) from engine.pipeline.core import PipelineContext from engine.pipeline.stages.framebuffer import FrameBufferStage class QueueDisplay: """Stub display that captures every frame into a queue.""" def __init__(self): self.frames: queue.Queue[list[str]] = queue.Queue() self.width = 80 self.height = 24 self._init_called = False def init(self, width: int, height: int, reuse: bool = False) -> None: self.width = width self.height = height self._init_called = True def show(self, buffer: list[str], border: bool = False) -> None: self.frames.put(list(buffer)) def clear(self) -> None: pass def cleanup(self) -> None: pass def get_dimensions(self) -> tuple[int, int]: return (self.width, self.height) def _build_pipeline( items: list[SourceItem], history_depth: int = 5, width: int = 80, height: int = 24, ) -> tuple[Pipeline, QueueDisplay, PipelineContext]: """Build pipeline: source -> render -> framebuffer -> display.""" display = QueueDisplay() ctx = PipelineContext() ctx.set("items", items) pipeline = Pipeline( config=PipelineConfig(enable_metrics=True), context=ctx, ) # Source source = ListDataSource(items, name="test-source") pipeline.add_stage("source", DataSourceStage(source, name="test-source")) # Render pipeline.add_stage("render", SourceItemsToBufferStage(name="items-to-buffer")) # Framebuffer framebuffer = FrameBufferStage(name="default", history_depth=history_depth) pipeline.add_stage("framebuffer", framebuffer) # Display pipeline.add_stage("display", DisplayStage(display, name="queue")) pipeline.build() pipeline.initialize() return pipeline, display, ctx class TestFrameBufferAcceptance: """Test FrameBufferStage in a full pipeline.""" def test_framebuffer_populates_history(self): """After several frames, framebuffer should have history stored.""" items = [ SourceItem(content="Frame\nBuffer\nTest", source="test", timestamp="0") ] pipeline, display, ctx = _build_pipeline(items, history_depth=5) # Run 3 frames for i in range(3): result = pipeline.execute([]) assert result.success, f"Pipeline failed at frame {i}: {result.error}" # Check framebuffer history in context history = ctx.get("framebuffer.default.history") assert history is not None, "Framebuffer history not found in context" assert len(history) == 3, f"Expected 3 history frames, got {len(history)}" def test_framebuffer_respects_depth(self): """Framebuffer should not exceed configured history depth.""" items = [SourceItem(content="Depth\nTest", source="test", timestamp="0")] pipeline, display, ctx = _build_pipeline(items, history_depth=3) # Run 5 frames for i in range(5): result = pipeline.execute([]) assert result.success history = ctx.get("framebuffer.default.history") assert history is not None assert len(history) == 3, f"Expected depth 3, got {len(history)}" def test_framebuffer_current_intensity(self): """Framebuffer should compute current intensity map.""" items = [SourceItem(content="Intensity\nMap", source="test", timestamp="0")] pipeline, display, ctx = _build_pipeline(items, history_depth=5) # Run at least one frame result = pipeline.execute([]) assert result.success intensity = ctx.get("framebuffer.default.current_intensity") assert intensity is not None, "No intensity map in context" # Intensity should be a list of one value per line? Actually it's a 2D array or list? # Let's just check it's non-empty assert len(intensity) > 0, "Intensity map is empty" def test_framebuffer_get_frame(self): """Should be able to retrieve specific frames from history.""" items = [SourceItem(content="Retrieve\nFrame", source="test", timestamp="0")] pipeline, display, ctx = _build_pipeline(items, history_depth=5) # Run 2 frames for i in range(2): result = pipeline.execute([]) assert result.success # Retrieve frame 0 (most recent) recent = pipeline.get_stage("framebuffer").get_frame(0, ctx) assert recent is not None, "Cannot retrieve recent frame" assert len(recent) > 0, "Recent frame is empty" # Retrieve frame 1 (previous) previous = pipeline.get_stage("framebuffer").get_frame(1, ctx) assert previous is not None, "Cannot retrieve previous frame" def test_framebuffer_with_motionblur_effect(self): """MotionBlurEffect should work when depending on framebuffer.""" from engine.effects.plugins.motionblur import MotionBlurEffect from engine.pipeline.adapters import EffectPluginStage items = [SourceItem(content="Motion\nBlur", source="test", timestamp="0")] display = QueueDisplay() ctx = PipelineContext() ctx.set("items", items) pipeline = Pipeline( config=PipelineConfig(enable_metrics=True), context=ctx, ) source = ListDataSource(items, name="test") pipeline.add_stage("source", DataSourceStage(source, name="test")) pipeline.add_stage("render", SourceItemsToBufferStage(name="render")) framebuffer = FrameBufferStage(name="default", history_depth=3) pipeline.add_stage("framebuffer", framebuffer) motionblur = MotionBlurEffect() motionblur.configure(EffectConfig(enabled=True, intensity=0.5)) pipeline.add_stage( "motionblur", EffectPluginStage( motionblur, name="motionblur", dependencies={"framebuffer.history.default"}, ), ) pipeline.add_stage("display", DisplayStage(display, name="queue")) pipeline.build() pipeline.initialize() # Run a few frames for i in range(5): result = pipeline.execute([]) assert result.success, f"Motion blur pipeline failed at frame {i}" # Check that history exists history = ctx.get("framebuffer.default.history") assert history is not None assert len(history) > 0