Skip to content

Hypothesis state-machine tests for pipeline completion (#73) - #243

Open
leynos wants to merge 3 commits into
mainfrom
python-pipeline-wait-tests
Open

Hypothesis state-machine tests for pipeline completion (#73)#243
leynos wants to merge 3 commits into
mainfrom
python-pipeline-wait-tests

Conversation

@leynos

@leynos leynos commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

Pipeline completion ordering — first-failure latching, timing-slot population, and the fail-fast termination decision — lived inline in _process_completed_task, which mutated _PipelineWaitState, read the clock, and terminated stages in one async method. It was untestable at the state-transition level (#73).

Seam

Extract the pure decision into _PipelineWaitState.record_completion(completed_idx, exit_code, *, ended_at):

  • stamps exit_codes[idx] and ended_at[idx] (the clock is injected as ended_at, so the transition is deterministic),
  • latches the first non-zero exit — in completion order — as failure_index,
  • returns True iff this completion is that first failure and the stage is not the final one — the sole condition under which the caller terminates the remaining downstream stages.

_process_completed_task now reads the clock and performs the async termination side effect around this pure call, so behaviour is unchanged.

Tests

cuprum/unittests/test_pipeline_wait.py:

  • A Hypothesis RuleBasedStateMachine drives randomized completion orders across 1–8 stages, restarting a fresh pipeline once drained, and asserts on every step: the exit code and timestamp land in the right slots, failure_index equals the first-completed non-zero exit and never changes, and the termination flag matches first_failure and idx != last. An invariant checks failure_index always points at a recorded non-zero exit.
  • Example tests pin the boundaries: first-completed failure wins over lowest index, a final-stage failure requests no termination, a lone stage is also final, an all-success run records no failure, and fail-fast fires exactly once.

The transition needs no event loop or subprocess, so the machine exercises completion ordering directly.

Validation

Full gates green: make check-fmt, make lint (ruff, interrogate 100%, pylint 10.00/10), make test (826 passed / 58 skipped incl. the new suite; Rust nextest 57/57). The maturin wheel-manifest snapshot is regenerated for the new test file.

Closes #73

🤖 Generated with Claude Code

Summary by Sourcery

Extract the pipeline completion decision logic into a pure _PipelineWaitState.record_completion transition and adapt the async handler to delegate to it while preserving behavior.

Enhancements:

  • Introduce a deterministic record_completion method on _PipelineWaitState to encapsulate exit-code, timing, and fail-fast bookkeeping separate from async side effects.

Tests:

  • Add Hypothesis state-machine tests and example-based tests for _PipelineWaitState.record_completion to validate completion ordering, first-failure semantics, and termination decisions across varied pipelines.

Pipeline completion ordering lived inline in _process_completed_task,
which mutated _PipelineWaitState, read the clock, and terminated stages
in one async method — untestable at the state-transition level.

Extract the pure decision into _PipelineWaitState.record_completion(
completed_idx, exit_code, *, ended_at): it stamps the exit code and end
time (clock injected), latches the first non-zero exit in completion
order as failure_index, and returns whether the remaining downstream
stages must be terminated. _process_completed_task now calls it and keeps
the clock read and the async termination side effect.

Add cuprum/unittests/test_pipeline_wait.py: a Hypothesis
RuleBasedStateMachine drives random completion orders (restarting fresh
pipelines when drained) and asserts first-failure semantics, timing-slot
population, and the termination decision; example tests pin the
boundaries (first-completed failure wins, final-stage failure requests no
termination, all-success records no failure, fail-fast fires once).

Regenerate the maturin wheel-manifest snapshot for the new test file.

Closes #73

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Extracts pipeline completion decision logic into a pure _PipelineWaitState.record_completion method and adds comprehensive Hypothesis state-machine tests plus example tests to validate completion ordering, failure latching, and fail-fast termination semantics; wires _process_completed_task to use the new method and updates the maturin snapshot for the new test file.

Sequence diagram for pipeline task completion and fail-fast termination

sequenceDiagram
participant Task
participant _process_completed_task
participant _PipelineWaitState
participant _terminate_pipeline_remaining_stages

Task->>_process_completed_task: result()
_process_completed_task->>_PipelineWaitState: record_completion(idx, exit_code, ended_at)
alt [record_completion returns True]
  _process_completed_task->>_terminate_pipeline_remaining_stages: _terminate_pipeline_remaining_stages(processes, wait_tasks, idx, cancel_grace)
end
Loading

File-Level Changes

Change Details Files
Extract pure completion-ordering transition from _process_completed_task into _PipelineWaitState.record_completion and adapt caller.
  • Introduce record_completion(completed_idx, exit_code, *, ended_at) -> bool on _PipelineWaitState to stamp exit codes and end timestamps, latch the first non-zero exit as failure_index, and report whether fail-fast termination should occur.
  • Update _process_completed_task to delegate state mutation and fail-fast decision to record_completion, limiting its responsibilities to reading the clock and performing async termination side effects.
  • Preserve existing behaviour by keeping termination condition semantics identical: only the first non-zero exit on a non-final stage triggers termination of remaining downstream stages.
cuprum/_pipeline_wait.py
Add Hypothesis state-machine tests and example tests for pipeline completion semantics.
  • Introduce _make_wait_state helper to construct a minimal _PipelineWaitState suitable for pure transition testing without event loop or subprocess involvement.
  • Add _PipelineCompletionMachine Hypothesis RuleBasedStateMachine to exercise randomized completion orders, model expected failure index and termination behaviour, and assert invariants about exit-code/timestamp placement and failure-index correctness.
  • Add TestRecordCompletionExamples class with targeted example tests covering boundary scenarios: first-completed failure vs lowest index, final-stage and single-stage failures not requesting termination, all-success runs leaving failure_index unset, and fail-fast firing exactly once.
cuprum/unittests/test_pipeline_wait.py
Update maturin build snapshot to include the new test file.
  • Regenerate test_maturin_build.ambr snapshot so the Rust/maturin wheel manifest accounts for the new test_pipeline_wait.py file.
cuprum/unittests/__snapshots__/test_maturin_build.ambr

Assessment against linked issues

Issue Objective Addressed Explanation
#73 Refactor pipeline completion handling into a pure transition method on _PipelineWaitState (taking state/completed_idx/exit_code and associated data) to isolate the completion-ordering logic from async side effects.
#73 Add Hypothesis state-machine tests that exercise _PipelineWaitState completion ordering, proving first-failure semantics, timing-slot population, and the fail-fast termination decision.
#73 Introduce CrossHair-based verification for the refactored pure transition logic after the refactor. The PR only adds the record_completion method and Hypothesis state-machine tests plus example tests; there is no CrossHair integration or specification/analysis code included.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e1093332-1eda-4ff0-9425-50d84fcffc5a

📥 Commits

Reviewing files that changed from the base of the PR and between 302858c and 152497d.

📒 Files selected for processing (4)
  • cuprum/_pipeline_wait.py
  • cuprum/unittests/__snapshots__/test_maturin_build.ambr
  • cuprum/unittests/test_pipeline_wait.py
  • docs/cuprum-design.md
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/shared-actions (auto-detected)
  • leynos/pylint-pypy-shim (auto-detected)
  • leynos/whitaker (auto-detected)

Summary

  • Extracted pipeline completion bookkeeping into deterministic record_completion(...) and should_terminate_others(...) operations.
  • Preserved completion-order first-failure semantics, timing-slot population, and fail-fast termination behaviour.
  • Added Hypothesis state-machine, boundary, and async integration tests addressing issue #73.
  • Documented the completion-ordering seam and command/query separation in docs/cuprum-design.md.
  • Updated the wheel-manifest snapshot for the new test module.

Walkthrough

Changes

Pipeline completion handling

Layer / File(s) Summary
Centralise completion and fail-fast handling
cuprum/_pipeline_wait.py, docs/cuprum-design.md
record_completion records exit codes and timestamps, latches the first failure by completion order, and lets _process_completed_task decide whether to terminate remaining stages; the documented contract reflects this split.
Validate completion ordering
cuprum/unittests/test_pipeline_wait.py, cuprum/unittests/__snapshots__/test_maturin_build.ambr
State-machine, boundary, and async integration tests cover bookkeeping, failure latching, final-stage behaviour, and termination calls; the wheel snapshot includes the test module.

Sequence Diagram(s)

sequenceDiagram
  participant CompletedTask
  participant _process_completed_task
  participant _PipelineWaitState
  participant _terminate_pipeline_remaining_stages
  CompletedTask->>_process_completed_task: stage exit code and completion time
  _process_completed_task->>_PipelineWaitState: record_completion(idx, exit_code, ended_at)
  _PipelineWaitState-->>_process_completed_task: should_terminate_others(idx)
  _process_completed_task->>_terminate_pipeline_remaining_stages: terminate remaining stages
Loading

Suggested labels: Issue

Suggested reviewers: codescene-delta-analysis, codescene-access

Poem

Stages finish in varied flight,
Their codes and times are set just right.
The first failure sounds the bell,
Later failures know their place as well.
Tests march on in ordered cheer.

🚥 Pre-merge checks | ✅ 18 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Developer Documentation ⚠️ Warning FAIL: docs/cuprum-design.md was updated, but docs/developers-guide.md does not document the new _pipeline_wait command/query seam. Add a short section under 'Pipeline execution helper contracts' covering record_completion and should_terminate_others.
Observability ⚠️ Warning The new fail-fast transition and termination branch are silent: cuprum/_pipeline_wait.py adds no logging, metrics, tracing, or event emission, and ExecPhase has no termination/failure phase. Emit a structured log or ExecEvent/metric at first-failure latching and fail-fast termination, including stage index, exit code, and elapsed time.
✅ Passed checks (18 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the PR’s main change and includes the linked issue reference (#73).
Description check ✅ Passed The description clearly describes the pipeline completion refactor and test additions in this changeset.
Linked Issues check ✅ Passed The PR implements #73 by adding state-machine tests, proving first-failure semantics, timing slots, and fail-fast decisions.
Out of Scope Changes check ✅ Passed No unrelated code changes are evident; the docs and snapshot updates support the new test and refactor work.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Testing (Overall) ✅ Passed The new tests are substantive: a Hypothesis state machine exercises random completion orders, and async boundary tests verify the extracted seam’s wiring and termination decision.
User-Facing Documentation ✅ Passed No user-facing API or behaviour changed; the user guide already documents pipeline fail-fast behaviour, and this PR only adds internal design docs.
Module-Level Documentation ✅ Passed PASS: Both touched Python modules have module docstrings; the new test module explains its role and the pipeline module states its purpose.
Testing (Unit And Behavioural) ✅ Passed PASS: The new suite exercises the local transition with Hypothesis/invariants, and the public pipeline behaviour is already covered by end-to-end scenarios.
Testing (Property / Proof) ✅ Passed The PR adds a Hypothesis RuleBasedStateMachine plus boundary tests for completion-order invariants, and the design doc explicitly recommends Hypothesis for the pure transition.
Testing (Compile-Time / Ui) ✅ Passed PASS: no Rust/TypeScript compile-time seam here; the wheel-build snapshot is normalised, redacts volatile names, and is backed by a generator-pin assertion.
Unit Architecture ✅ Passed The refactor cleanly separates the mutable completion command from the read-only termination query, with the clock and side-effects kept at the async boundary and covered by targeted tests.
Domain Architecture ✅ Passed PASS: _PipelineWaitState now owns pure completion bookkeeping, while clock reads and process termination stay in _process_completed_task and _process_lifecycle.
Security And Privacy ✅ Passed No secrets, credentials, auth changes, or unsafe sinks appear in the touched files; the refactor only moves pipeline bookkeeping and tests use synthetic data.
Performance And Resource Use ✅ Passed PASS: record_completion/should_terminate_others are O(1), and the new Hypothesis suite is bounded (1–8 stages, 60×16 steps) with no new unbounded I/O or buffers.
Concurrency And State ✅ Passed PASS: _PipelineWaitState has a single owner in _wait_for_pipeline, uses an await-free transition/query split, and Hypothesis plus async tests cover completion-order interleavings and fail-fast.
Architectural Complexity And Maintainability ✅ Passed PASS: the command/query split replaces inline bookkeeping with a deterministic seam, has one concrete caller, and is documented/tested; no extra layer or dependency added.
Rust Compiler Lint Integrity ✅ Passed PASS — no Rust files are changed in this PR; the only existing Rust lint expectation is a narrow, issue-linked clippy::missing_const_for_fn in lib.rs.
📋 Issue Planner

Built with CodeRabbit's Coding Plans for faster development and fewer bugs.

View plan used: #73

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch python-pipeline-wait-tests

Comment @coderabbitai help to get the list of available commands.

codescene-access[bot]

This comment was marked as outdated.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@coderabbitai coderabbitai Bot added the Issue label Jul 28, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 87e8aae8a7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread cuprum/_pipeline_wait.py Outdated
Comment thread cuprum/_pipeline_wait.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cuprum/_pipeline_wait.py`:
- Around line 73-76: Update the transition documentation around the completion
condition to state that _terminate_pipeline_remaining_stages terminates every
still-running stage except the failed stage, rather than only remaining
downstream stages. Preserve the existing condition and the description that I/O
remains with the caller.

In `@cuprum/unittests/test_pipeline_wait.py`:
- Line 27: Add substantive async tests for _process_completed_task in
test_pipeline_wait.py, covering completed and termination cases, including
termination invocation and correct clock/index argument forwarding. Exercise the
actual async integration boundary rather than only record_completion, and assert
the expected state/results for each path.
- Around line 88-118: Update the assertions in the stateful and example test
sections, including the flow around record_completion and the additional ranges
123-127 and 147-191, to provide descriptive diagnostic messages instead of using
bare asserts. Cover every assertion, including state initialization,
pending-stage updates, timing and exit-code checks, failure_index, and
termination expectations, with messages that identify the relevant state and
expected values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 36bb32ad-5983-4db4-b3a6-190fc75782ba

📥 Commits

Reviewing files that changed from the base of the PR and between 302858c and 87e8aae.

📒 Files selected for processing (3)
  • cuprum/_pipeline_wait.py
  • cuprum/unittests/__snapshots__/test_maturin_build.ambr
  • cuprum/unittests/test_pipeline_wait.py
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • leynos/shared-actions (auto-detected)
  • leynos/pylint-pypy-shim (auto-detected)
  • leynos/whitaker (auto-detected)

Comment thread cuprum/_pipeline_wait.py Outdated
Comment thread cuprum/unittests/test_pipeline_wait.py
Comment thread cuprum/unittests/test_pipeline_wait.py Outdated
codescene-access[bot]

This comment was marked as outdated.

Review findings on the completion transition:

- record_completion both mutated the wait state and returned the
  termination decision, so a query was inseparable from a command,
  against the command/query segregation rule in AGENTS.md. Split it:
  record_completion is now a command returning None, and the new
  should_terminate_others(completed_idx) query reports the fail-fast
  decision from state without changing it, so it is repeatable and
  order-independent. _process_completed_task calls the command, then the
  query.
- AGENTS.md requires function documentation to demonstrate usage and
  outcome; both methods gained worked examples.
- The docstring described terminating the "remaining downstream stages".
  _terminate_pipeline_remaining_stages stops every still-running stage
  except the failed one, upstream included, so the wording now says
  "every other still-running stage".
- The suite only covered the pure transition, so it could not catch
  inverted or omitted termination, or a mis-forwarded clock or index. Add
  TestProcessCompletedTask, which drives the real async boundary with a
  recording termination double and an injected perf_counter. Verified
  non-vacuous: omitting the termination call and forwarding the wrong
  clock each fail these tests.
- Every bare assertion in the suite gained a diagnostic message.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

The pre-merge Developer Documentation check flagged that the new
record_completion / should_terminate_others seam was described only in
docstrings, with the design doc covering fail-fast behaviour generally.

Add a "Completion ordering seam" subsection to the fail-fast policy in the
design doc: what each half of the command/query split does, that the clock
is injected so the transition is deterministic, that completion order
rather than stage order decides which failure latches, and that
_process_completed_task is the sole caller joining the two.

Also correct the adjacent policy wording, which said fail-fast terminates
"the remaining stages": it terminates every other still-running stage,
upstream included. That is the same inaccuracy already corrected in the
docstrings on this branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@codescene-access codescene-access Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No quality gates enabled for this code.

@buzzybee-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hypothesis state-machine tests for _PipelineWaitState / _process_completed_task (cuprum/_pipeline_wait.py)

3 participants