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
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:
@@ -9,10 +9,14 @@ import pytest
|
|||||||
from fluksio_worker.worker_main import ARTIFACT_DIR_ENV
|
from fluksio_worker.worker_main import ARTIFACT_DIR_ENV
|
||||||
|
|
||||||
from fluksio.flow.artifacts import ArtifactStore
|
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.messages import DType, MessageSpec
|
||||||
from fluksio.flow.nodes import Node
|
from fluksio.flow.nodes import Node
|
||||||
from fluksio.flow.nodes.base import NodeOutputError
|
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
|
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"}
|
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):
|
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."""
|
"""The worker sends every yield; the engine is what holds the last one back."""
|
||||||
node, published = _sink_node("count", "out", DType.INT)
|
node, published = _sink_node("count", "out", DType.INT)
|
||||||
|
|||||||
Reference in New Issue
Block a user