from __future__ import annotations from dataclasses import dataclass from synaptopus.runtime import ( StepTrace, merge_execution_records, run_until_acceptance, run_until_acceptance_count, ) @dataclass(frozen=True) class CounterState: value: int = 0 class EvenAcceptanceSystem: def step(self, state: CounterState) -> StepTrace[CounterState, int, dict[str, int]]: next_state = CounterState(state.value + 1) return StepTrace( previous_state=state, next_state=next_state, candidate=next_state.value, accepted=next_state.value % 2 == 0, elapsed_seconds=0.0, metadata={"value": next_state.value}, ) def test_run_until_acceptance_collects_rejected_attempts() -> None: record = run_until_acceptance(EvenAcceptanceSystem(), CounterState(), max_attempts=5) assert record.accepted_count == 1 assert record.attempt_count == 2 assert record.final_state == CounterState(2) assert record.accepted[0].candidate == 2 assert record.attempts[0].accepted is False assert record.attempts[1].accepted is True def test_run_until_acceptance_count_collects_multiple_acceptances() -> None: record = run_until_acceptance_count( EvenAcceptanceSystem(), CounterState(), accepted_count=3, max_attempts_per_accept=5, ) assert record.accepted_count == 3 assert record.attempt_count == 6 assert tuple(step.candidate for step in record.accepted) == (2, 4, 6) assert record.final_state == CounterState(6) def test_merge_execution_records_appends_continuation() -> None: first = run_until_acceptance_count( EvenAcceptanceSystem(), CounterState(), accepted_count=1, max_attempts_per_accept=5, ) second = run_until_acceptance_count( EvenAcceptanceSystem(), first.final_state, accepted_count=2, max_attempts_per_accept=5, ) merged = merge_execution_records(first, second) assert merged.accepted_count == 3 assert merged.attempt_count == 6 assert tuple(step.candidate for step in merged.accepted) == (2, 4, 6) assert merged.final_state == CounterState(6)