haystack/test/core/pipeline/test_pipeline.py
mathislucka eec91824bc
fix: pipeline run bugs in cyclic and acyclic pipelines (#8707)
* add component checks

* pipeline should run deterministically

* add FIFOQueue

* add agent tests

* add order dependent tests

* run new tests

* remove code that is not needed

* test: intermediate from cycle outputs are available outside cycle

* add tests for component checks (Claude)

* adapt tests for component checks (o1 review)

* chore: format

* remove tests that aren't needed anymore

* add _calculate_priority tests

* revert accidental change in pyproject.toml

* test format conversion

* adapt to naming convention

* chore: proper docstrings and type hints for PQ

* format

* add more unit tests

* rm unneeded comments

* test input consumption

* lint

* fix: docstrings

* lint

* format

* format

* fix license header

* fix license header

* add component run tests

* fix: pass correct input format to tracing

* fix types

* format

* format

* types

* add defaults from Socket instead of signature

- otherwise components with dynamic inputs would fail

* fix test names

* still wait for optional inputs on greedy variadic sockets

- mirrors previous behavior

* fix format

* wip: warn for ambiguous running order

* wip: alternative warning

* fix license header

* make code more readable

Co-authored-by: Amna Mubashar <amnahkhan.ak@gmail.com>

* Introduce content tracing to a behavioral test

* Fixing linting

* Remove debug print statements

* Fix tracer tests

* remove print

* test: test for component inputs

* test: remove testing for run order

* chore: update component checks from experimental

* chore: update pipeline and base from experimental

* refactor: remove unused method

* refactor: remove unused method

* refactor: outdated comment

* refactor: inputs state is updated as side effect

- to prepare for AsyncPipeline implementation

* format

* test: add file conversion test

* format

* fix: original implementation deepcopies outputs

* lint

* fix: from_dict was updated

* fix: format

* fix: test

* test: add test for thread safety

* remove unused imports

* format

* test: FIFOPriorityQueue

* chore: add release note

* fix: resolve merge conflict with mermaid changes

* fix: format

* fix: remove unused import

* refactor: rename to avoid accidental conflicts

* chore: remove unused inputs, add missing license header

* chore: extend release notes

* Update releasenotes/notes/fix-pipeline-run-2fefeafc705a6d91.yaml

Co-authored-by: Amna Mubashar <amnahkhan.ak@gmail.com>

* fix: format

* fix: format

* Update release note

---------

Co-authored-by: Amna Mubashar <amnahkhan.ak@gmail.com>
Co-authored-by: David S. Batista <dsbatista@gmail.com>
2025-02-06 14:19:47 +00:00

86 lines
3.0 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
from concurrent.futures import ThreadPoolExecutor
import pytest
from haystack.components.joiners import BranchJoiner
from haystack.core.component import component
from haystack.core.errors import PipelineRuntimeError
from haystack.core.pipeline import Pipeline
class TestPipeline:
"""
This class contains only unit tests for the Pipeline class.
It doesn't test Pipeline.run(), that is done separately in a different way.
"""
def test_pipeline_thread_safety(self, waiting_component, spying_tracer):
# Initialize pipeline with synchronous components
pp = Pipeline()
pp.add_component("wait", waiting_component())
run_data = [{"wait_for": 1}, {"wait_for": 2}]
# Use ThreadPoolExecutor to run pipeline calls in parallel
with ThreadPoolExecutor(max_workers=len(run_data)) as executor:
# Submit pipeline runs to the executor
futures = [executor.submit(pp.run, data) for data in run_data]
# Wait for all futures to complete
for future in futures:
future.result()
# Verify component visits using tracer
component_spans = [sp for sp in spying_tracer.spans if sp.operation_name == "haystack.component.run"]
for span in component_spans:
assert span.tags["haystack.component.visits"] == 1
def test__run_component_success(self):
"""Test successful component execution"""
joiner_1 = BranchJoiner(type_=str)
joiner_2 = BranchJoiner(type_=str)
pp = Pipeline()
pp.add_component("joiner_1", joiner_1)
pp.add_component("joiner_2", joiner_2)
pp.connect("joiner_1", "joiner_2")
inputs = {"joiner_1": {"value": [{"sender": None, "value": "test_value"}]}}
outputs = pp._run_component(
component=pp._get_component_with_graph_metadata_and_visits("joiner_1", 0),
inputs=inputs,
component_visits={"joiner_1": 0, "joiner_2": 0},
)
assert outputs == {"value": "test_value"}
# We remove input in greedy variadic sockets, even if they are from the user
assert "value" not in inputs["joiner_1"]
def test__run_component_fail(self):
"""Test error when component doesn't return a dictionary"""
@component
class WrongOutput:
@component.output_types(output=str)
def run(self, value: str):
return "not_a_dict"
wrong = WrongOutput()
pp = Pipeline()
pp.add_component("wrong", wrong)
inputs = {"wrong": {"value": [{"sender": None, "value": "test_value"}]}}
with pytest.raises(PipelineRuntimeError) as exc_info:
pp._run_component(
component=pp._get_component_with_graph_metadata_and_visits("wrong", 0),
inputs=inputs,
component_visits={"wrong": 0},
)
assert "didn't return a dictionary" in str(exc_info.value)