"""Message history: the series a node panel draws as a sparkline.""" from fluksio.flow.messages import MessageSpec from fluksio.flow.nodes import Node from fluksio.flow.pipeline import Pipeline from fluksio.flow.state import HISTORY_LIMIT, MemoryState def emitter(values) -> Node: """A node publishing the given values, one per execution.""" remaining = list(values) def process(params): return {"out": remaining.pop(0)} node = Node(f=process, requires=[], provides=[MessageSpec(name="out")], name="src") node.assign_flow("f", "src") return node def test_the_series_follows_the_order_values_were_published(): node = emitter([1.0, 2.0, 3.0]) pipeline = Pipeline(nodes=[node]) # An injecting node publishes directly, an executed one goes through the # executor; both paths land in the same series. node.inject() pipeline.run() pipeline.run() points = pipeline.state.history("f.out") assert [value for _, value in points] == [1.0, 2.0, 3.0] assert [ts for ts, _ in points] == sorted(ts for ts, _ in points) def test_the_series_is_capped(): state = MemoryState() for i in range(HISTORY_LIMIT + 20): state.append_history({"f.out": float(i)}, ts=float(i)) points = state.history("f.out") assert len(points) == HISTORY_LIMIT # The oldest twenty fell off the end. assert points[0] == (20.0, 20.0) assert points[-1] == (139.0, 139.0) def test_only_numbers_are_recorded(): state = MemoryState() state.append_history({"f.text": "warm", "f.flag": True, "f.temp": 21}, ts=1.0) assert state.history("f.text") == [] assert state.history("f.flag") == [] assert state.history("f.temp") == [(1.0, 21.0)] def test_a_message_without_history_comes_back_empty(): assert MemoryState().history("f.never") == []