Cover the sink reaching a node the controller built
Docs / docs (push) Successful in 30s
Playwright Tests / test-playwright (1, 2) (push) Successful in 3m59s
Playwright Tests / test-playwright (2, 2) (push) Successful in 2m9s
pre-commit / pre-commit (push) Failing after 3m16s
Test Backend / test-backend (push) Successful in 2m48s
Compose Smoke Test / test-compose (push) Successful in 39s
Playwright Tests / merge-reports (push) Successful in 1m12s

The pieces were tested one at a time, so removing the one line in
`_build_node` that wraps a worker-backed function with its sink broke nothing
visible — while losing every generator node's last yield and returning None
in its place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-26 23:01:15 +02:00
co-authored by Claude Opus 5
parent 8f71b638b6
commit 8be7e424ba
+48 -1
View File
@@ -9,10 +9,14 @@ import pytest
from fluksio_worker.worker_main import ARTIFACT_DIR_ENV
from fluksio.flow.artifacts import ArtifactStore
from fluksio.flow.controller import EmitSink
from fluksio.flow.controller import EmitSink, FlowController
from fluksio.flow.messages import DType, MessageSpec
from fluksio.flow.nodes import Node
from fluksio.flow.nodes.base import NodeOutputError
from fluksio.flow.pipeline import Pipeline
from fluksio.flow.schemas import FlowDef, NodeDef
from fluksio.flow.state import MemoryState
from fluksio.flow.store import FlowStore
from fluksio.flow.workers import NodeCancelled, NodeTimeout, PythonWorkerPool
@@ -248,6 +252,49 @@ def test_a_generator_node_publishes_each_yield_and_returns_the_end(pool):
assert {event["call_id"] for event in seen} == {"r1:demo.train"}
def test_a_node_the_controller_built_reclassifies_its_last_yield(pool, tmp_path):
"""The sink only reaches a node if `_build_node` wrapped its function with it.
Without that, a generator node's last yield is lost and its result is None —
which the pieces of this checked one at a time cannot see.
"""
store = FlowStore(tmp_path / "flows")
store.write_flow(
FlowDef(
name="study",
mode="batch",
nodes=[
NodeDef(
id="train",
provides=[MessageSpec(name="loss", dtype=DType.FLOAT, stream=True)],
)
],
)
)
store.write_node_source(
"study",
"train",
"def process():\n"
" yield {'loss': 1.0}\n"
" yield {'loss': 2.0}\n"
" yield {'loss': 3.0}\n",
)
controller = FlowController(store, workers=pool)
nodes, _loaded, _initial, _inputs = controller._build_flows(
[(store.read_flow("study"), False)]
)
emitted: list[dict] = []
pipeline = Pipeline(
nodes=nodes,
state=MemoryState(),
emission_observer=lambda _node, outputs: emitted.append(outputs),
)
pipeline.run()
assert [e["study.loss"] for e in emitted] == [1.0, 2.0]
assert pipeline.state["study.loss"] == 3.0
def test_without_a_return_the_last_yield_is_the_result(pool):
"""The worker sends every yield; the engine is what holds the last one back."""
node, published = _sink_node("count", "out", DType.INT)