Spaces:
Sleeping
Sleeping
| """Unit tests for StepProtocol and SampleResult.""" | |
| from __future__ import annotations | |
| import pytest | |
| from pipeline import SampleResult, StepContext, StepProtocol | |
| # --------------------------------------------------------------------------- | |
| # Helper objects | |
| # --------------------------------------------------------------------------- | |
| class ValidStep: | |
| requires = frozenset({"a"}) | |
| provides = frozenset({"b"}) | |
| def __call__(self, ctx: StepContext) -> StepContext: | |
| return ctx | |
| class ValidStepWithPlainSets: | |
| requires = {"a"} # plain set, not frozenset | |
| provides = {"b"} | |
| def __call__(self, ctx: StepContext) -> StepContext: | |
| return ctx | |
| class MissingRequires: | |
| provides = frozenset({"b"}) | |
| def __call__(self, ctx: StepContext) -> StepContext: | |
| return ctx | |
| class MissingProvides: | |
| requires = frozenset({"a"}) | |
| def __call__(self, ctx: StepContext) -> StepContext: | |
| return ctx | |
| class MissingCall: | |
| requires = frozenset({"a"}) | |
| provides = frozenset({"b"}) | |
| class EmptyStep: | |
| """Valid step with empty requires/provides (e.g. a pure side-effect step).""" | |
| requires = frozenset() | |
| provides = frozenset() | |
| def __call__(self, ctx: StepContext) -> StepContext: | |
| return ctx | |
| # --------------------------------------------------------------------------- | |
| # StepProtocol | |
| # --------------------------------------------------------------------------- | |
| class TestStepProtocol: | |
| def test_valid_step_passes_isinstance(self): | |
| assert isinstance(ValidStep(), StepProtocol) | |
| def test_valid_step_with_plain_sets_passes_isinstance(self): | |
| # AbstractSet[str] accepts both set and frozenset | |
| assert isinstance(ValidStepWithPlainSets(), StepProtocol) | |
| def test_empty_requires_provides_is_valid(self): | |
| assert isinstance(EmptyStep(), StepProtocol) | |
| def test_missing_requires_fails_isinstance(self): | |
| assert not isinstance(MissingRequires(), StepProtocol) | |
| def test_missing_provides_fails_isinstance(self): | |
| assert not isinstance(MissingProvides(), StepProtocol) | |
| def test_missing_call_fails_isinstance(self): | |
| assert not isinstance(MissingCall(), StepProtocol) | |
| def test_plain_object_fails_isinstance(self): | |
| assert not isinstance(object(), StepProtocol) | |
| def test_none_fails_isinstance(self): | |
| assert not isinstance(None, StepProtocol) | |
| # --------------------------------------------------------------------------- | |
| # SampleResult | |
| # --------------------------------------------------------------------------- | |
| class TestSampleResult: | |
| def test_basic_construction(self): | |
| ctx = StepContext(sample="s") | |
| r = SampleResult(sample="s", output=ctx, error=None, failed_at=None) | |
| assert r.sample == "s" | |
| assert r.output is ctx | |
| assert r.error is None | |
| assert r.failed_at is None | |
| def test_cause_defaults_to_none(self): | |
| r = SampleResult(sample="s", output=None, error=None, failed_at=None) | |
| assert r.cause is None | |
| def test_is_mutable(self): | |
| r = SampleResult(sample="s", output=None, error=None, failed_at=None) | |
| exc = RuntimeError("oops") | |
| r.error = exc | |
| r.failed_at = "SomeStep" | |
| assert r.error is exc | |
| assert r.failed_at == "SomeStep" | |
| def test_failure_result(self): | |
| exc = RuntimeError("boom") | |
| r = SampleResult(sample="x", output=None, error=exc, failed_at="BoomStep") | |
| assert r.output is None | |
| assert r.error is exc | |
| assert r.failed_at == "BoomStep" | |
| def test_branch_failure_has_cause(self): | |
| inner = RuntimeError("inner") | |
| from pipeline import BranchError | |
| outer = BranchError([inner]) | |
| r = SampleResult( | |
| sample="x", output=None, error=outer, failed_at="Branch", cause=inner | |
| ) | |
| assert r.cause is inner | |
| assert r.failed_at == "Branch" | |