Skip to content

Rust: pure pump state machine with proptest + Kani (#84) - #242

Open
leynos wants to merge 4 commits into
mainfrom
rust-pump-state-machine
Open

Rust: pure pump state machine with proptest + Kani (#84)#242
leynos wants to merge 4 commits into
mainfrom
rust-pump-state-machine

Conversation

@leynos

@leynos leynos commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

pump_stream_files_readwrite drove real descriptor I/O and inlined its own byte-total accounting and writer_open latch, with no seam for scripted verification. This extracts that control flow into a pure, io::Error-free state machine and verifies it with proptest and Kani, as #84 requested.

Design

src/pump_machine.rs (pure — no descriptors, no io::Error):

  • ReadEventChunk / Eof
  • WriteEventComplete { bytes } / Closed { bytes } (the non-fatal broken-pipe latch)
  • PumpState { total_written, writer_open }
  • step(&mut PumpState, ReadEvent, Option<WriteEvent>) -> Flow

pump_stream_files_readwrite now drives the machine: it translates real I/O into events via a new classify_write helper and propagates fatal PumpErrors out of band, so they never reach the machine. The now-redundant io_utils::handle_write_result is removed; its coverage moves to two classify_write tests plus the machine's property tests.

Verification (all four #84 goals)

proptest (cargo nextest) folds random event scripts through step:

  • total bytes are monotonic
  • the writer never reopens once a broken pipe latches it closed
  • a closed writer drains without accruing bytes (reader drain continues after writer close)
  • the loop stops exactly on Eof

Kani (src/pump_machine_kani_proofs.rs, #[cfg(kani)] — outside the commit gate) proves the same invariants over unbounded byte counts and arbitrary starting states, plus fatal-write accounting:

Complete - 6 successfully verified harnesses, 0 failures, 6 total.

Fatal-error propagation (the fourth goal) is realised by the loop's early ? return and covered by classify_write_propagates_a_fatal_error and the existing io_utils write tests.

Behaviour preserved

Every existing pump/consume test is unchanged and still passes — the refactor is behaviour-preserving, only relocating the decision logic behind a verifiable seam.

Validation

Full gates green: make check-fmt, make lint (clippy -D warnings, interrogate 100%, pylint 10.00/10), make test (pytest 755 passed/47 skipped; Rust nextest 59/59), make markdownlint, make nixie. Kani verified separately: 6/6 harnesses SUCCESSFUL.

Closes #84

🤖 Generated with Claude Code

Summary by Sourcery

Extract the read/write pump loop control flow into a pure state machine and drive it from the existing I/O-based pump implementation, with property-based and model-checking verification.

Enhancements:

  • Introduce a pure PumpState-based state machine to model read/write pump control flow independently of descriptor I/O.
  • Refactor pump_stream_files_readwrite to translate I/O results into state-machine events and classify non-fatal vs fatal write outcomes via classify_write.
  • Remove the legacy handle_write_result helper in favour of the new write classification and state machine integration.

Documentation:

Tests:

  • Add proptest-based property tests for the pump state machine invariants and classify_write behaviour.
  • Add Kani proofs (behind #[cfg(kani)]) to formally verify pump state machine invariants over arbitrary states and byte counts.

pump_stream_files_readwrite drove real descriptor I/O and inlined its own
byte-total accounting and writer_open latch, with no seam for scripted
verification (issue #84).

Extract that control flow into a pure, io::Error-free state machine in
src/pump_machine.rs: step advances a PumpState given a ReadEvent
(Chunk/Eof) and, when a write is performed, a WriteEvent (Complete /
Closed). pump_stream_files_readwrite now drives the machine, translating
real I/O into events via a new classify_write helper and propagating
fatal PumpErrors out of band so they never reach the machine. The
now-redundant io_utils::handle_write_result is removed (its coverage
moves to classify_write tests plus the machine's property tests).

Verification:
- proptest folds random event scripts through step, asserting the total
  is monotonic, the writer never reopens once a broken pipe latches it
  closed, a closed writer drains without accruing bytes, and the loop
  stops exactly on Eof.
- Kani proves the same invariants over unbounded byte counts and
  arbitrary starting states (src/pump_machine_kani_proofs.rs, #[cfg(kani)],
  outside the commit gate): 6/6 harnesses VERIFICATION SUCCESSFUL.

Existing pump/consume tests are unchanged and still pass, confirming the
refactor preserves behaviour. Documents the machine in the developers
guide.

Closes #84

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 the read/write pump loop control flow into a pure state machine (pump_machine) and reworks pump_stream_files_readwrite to drive it via classified write outcomes, adding property tests (proptest + Kani) and removing the old handle_write_result helper while preserving behavior.

Sequence diagram for pump_stream_files_readwrite driving the pure pump_machine

sequenceDiagram
    participant PumpLoop as pump_stream_files_readwrite
    participant Reader as Reader
    participant Writer as Writer
    participant IOUtils as io_utils
    participant PumpState as PumpState
    participant Machine as pump_machine

    PumpLoop->>PumpState: start()
    loop each_iteration
        PumpLoop->>IOUtils: read_stream(reader, buffer)
        IOUtils-->>PumpLoop: read_len
        alt read_len == 0
            PumpLoop->>Machine: step(state, ReadEvent::Eof, None)
            Machine-->>PumpLoop: Flow::Stop
            PumpLoop-->>PumpLoop: break loop
        else read_len > 0
            PumpLoop-->>PumpLoop: read = ReadEvent::Chunk
            alt state.writer_open()
                PumpLoop->>Writer: classify_write(writer, chunk)
                Writer->>IOUtils: handle_write(writer, chunk)
                IOUtils-->>Writer: WriteOutcome
                Writer-->>PumpLoop: WriteEvent
                PumpLoop->>Machine: step(state, ReadEvent::Chunk, Some(WriteEvent))
                Machine-->>PumpLoop: Flow::Continue
            else writer_closed
                PumpLoop->>Machine: step(state, ReadEvent::Chunk, None)
                Machine-->>PumpLoop: Flow::Continue
            end
        end
    end
    PumpLoop->>PumpState: total_written()
    PumpState-->>PumpLoop: total_bytes
    PumpLoop-->>PumpLoop: record tracing and return total_bytes
Loading

File-Level Changes

Change Details Files
Refactor pump loop into a pure state machine and wire it into pump_stream_files_readwrite
  • Introduce PumpState, ReadEvent, WriteEvent, Flow and step() as a pure, io::Error-free model of the pump loop
  • Change pump_stream_files_readwrite to maintain PumpState, translate read lengths into ReadEvent, and only perform writes when the writer is open
  • Drive the state machine via step(), stopping on Flow::Stop and recording total_written from PumpState
  • Add internal proptest-based property tests to verify monotonic totals, sticky writer_open latch, drain behavior after closure, and EOF stopping
  • Add Kani proofs over arbitrary starting states and unbounded byte counts to re-assert the same invariants and specific write behaviors
rust/cuprum-rust/src/lib.rs
rust/cuprum-rust/src/pump_machine.rs
rust/cuprum-rust/src/pump_machine_kani_proofs.rs
Introduce classify_write to map real write outcomes into WriteEvent and route fatal vs non-fatal errors
  • Add classify_write helper that calls handle_write and maps WriteOutcome into WriteEvent or propagates PumpError
  • Use classify_write inside pump_stream_files_readwrite to keep fatal errors out of the state machine and to latch non-fatal broken pipes
  • Remove the old handle_write_result helper in io_utils and replace its coverage with classify_write tests
  • Add tests in lib_tests to confirm classify_write reports completed writes and propagates fatal errors
rust/cuprum-rust/src/lib.rs
rust/cuprum-rust/src/io_utils/mod.rs
rust/cuprum-rust/src/lib_tests.rs
Update documentation and tests to describe and validate the new machine-based design
  • Document the pump_machine design, its role in the read/write fallback, and its proptest and Kani verification in the developers guide
  • Delete now-obsolete handle_write_result tests in io_utils/tests.rs
  • Ensure existing pump/consume tests still target pump_stream_files_readwrite via the new state machine and keep behavior unchanged
docs/developers-guide.md
rust/cuprum-rust/src/io_utils/tests.rs
rust/cuprum-rust/src/lib_tests.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#84 Extract a pure pump state machine over read/write events from pump_stream_files_readwrite to enable scripted verification.
#84 Add proptest-based property tests for the pump state machine covering monotonic total bytes, stopping writes after a broken pipe, continued reader drain after writer close, and correct loop termination on EOF/fatal errors.
#84 Add Kani-based verification harnesses for the pump state machine to prove the same invariants over arbitrary states and unbounded byte counts.

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

Walkthrough

The fallback read/write path now uses a pure event-driven state machine. Descriptor outcomes are classified into events, the pump tracks writer closure and bytes written, and proptest, Kani, integration tests, and developer documentation cover the resulting behaviour.

Changes

Read/write fallback pump

Layer / File(s) Summary
Pure pump model and verification
rust/cuprum-rust/src/pump_machine.rs, rust/cuprum-rust/src/pump_machine_kani_proofs.rs
Define read/write events, pump state, flow control, property tests, and Kani proofs for monotonic totals, writer closure, and EOF termination.
Outcome classification and fallback integration
rust/cuprum-rust/src/io_utils/..., rust/cuprum-rust/src/lib.rs, rust/cuprum-rust/src/lib_tests.rs
Classify write outcomes into pump events, drive the fallback loop through step, propagate fatal errors, and test completed and fatal writes.
Developer guide update
docs/developers-guide.md
Document the state-machine flow, event translation, proptest coverage, and Kani verification.

Sequence Diagram(s)

sequenceDiagram
  participant Reader
  participant pump_stream_files_readwrite
  participant classify_write
  participant pump_machine
  Reader->>pump_stream_files_readwrite: read chunk or EOF
  pump_stream_files_readwrite->>classify_write: classify chunk write
  classify_write-->>pump_stream_files_readwrite: WriteEvent or PumpError
  pump_stream_files_readwrite->>pump_machine: step with read and write events
  pump_machine-->>pump_stream_files_readwrite: updated state and Flow
Loading

Possibly related PRs

  • leynos/cuprum#163 — Shares the write-handling plumbing and non-fatal write classification changed here.
  • leynos/cuprum#228 — Modifies the same fallback pumping loop and its byte-recording flow.

Suggested labels: Issue

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

Poem

Guide the bytes through read and write,
Let closed pipes latch tight.
Count each accepted stream,
Prove the state-machine dream,
And stop cleanly at EOF’s light.


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Testing (Overall) ❌ Error Direct tests miss classify_write’s short-write/nonfatal-error branches, and the state-machine proptests mirror production flow rather than exercising the real loop’s broken-pipe path. Add direct classify_write tests for positive short writes and zero-byte nonfatal errors, then add a real pump_stream_files_readwrite broken-pipe/drain integration test.
Developer Documentation ⚠️ Warning The developers' guide was updated, but no design doc or ADR records the new pump-machine seam; docs/cuprum-design.md has no pump or Kani references. Add the pump-machine seam and classify_write decision to the relevant design doc or an ADR, then check off any linked roadmap item if this closes one.
✅ Passed checks (18 passed)
Check name Status Explanation
Title check ✅ Passed Match the title to the pump state-machine refactor, proptest, Kani, and issue #84.
Description check ✅ Passed Keep the description aligned with the state-machine extraction, classify_write, proptest, Kani, and #84.
Linked Issues check ✅ Passed Satisfy #84 by adding the pure pump seam, monotonicity checks, broken-pipe handling, reader drain, and fatal-error coverage.
Out of Scope Changes check ✅ Passed Stay within scope: the diff only touches the pump refactor, verification tests, and related documentation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
User-Facing Documentation ✅ Passed PASS — The PR only refactors internal Rust pump logic and adds verification; no public API or user-visible behaviour changed, and docs/users-guide.md was untouched.
Module-Level Documentation ✅ Passed All 16 Rust modules under src start with module docs; the new modules also explain purpose and relations to the pump/I/O components.
Testing (Unit And Behavioural) ✅ Passed Unit tests cover the new pump-state invariants and write/error paths; behavioural suites hit the real Rust/Python FD boundary with pipes and public stream APIs.
Testing (Property / Proof) ✅ Passed Proptest covers event scripts and Kani proves monotonicity, EOF stop, sticky close, and byte accounting over arbitrary states and u64 bytes.
Testing (Compile-Time / Ui) ✅ Passed PASS: The PR adds runtime state-machine logic plus proptest/Kani proofs, but no new compile-time or UI-adjacent output; the existing trybuild suite already targets separate PyO3 UI cases.
Unit Architecture ✅ Passed PASS: pure pump logic is isolated in pump_machine.rs, fallible I/O stays explicit in io_utils/mod.rs, and the loop only orchestrates the seam.
Domain Architecture ✅ Passed Core pump logic is isolated in pure pump_machine; io_utils/lib stay at the adapter boundary and keep StreamHandle/WriteOutcome out of the model.
Observability ✅ Passed Keep the existing tracing span and I/O boundary logs; the refactor preserves production observability with total_bytes/retry fields and fatal-write/read error events.
Security And Privacy ✅ Passed The diff only refactors pump I/O into a pure state machine with pipe-based tests/proofs; it adds no secrets, auth, permissions, or sensitive-data exposure.
Performance And Resource Use ✅ Passed The hot path stays a single linear read/write loop with bounded buffer reuse; the state machine adds no extra allocations, clones, or unbounded growth.
Concurrency And State ✅ Passed No new shared mutable state or interleaving hazard appears; the pump owns local PumpState, runs sequentially outside the GIL, and existing counters stay thread-local and reset per operation.
Architectural Complexity And Maintainability ✅ Passed PASS: Keep the new pump state machine narrow and crate-private; it isolates the real descriptor seam, with explicit docs and targeted proptest/Kani coverage.
Rust Compiler Lint Integrity ✅ Passed No broad dead-code/unused lint suppressions or artificial anchors were added; the changed Rust files also contain no new clone() calls and remove the stale helper cleanly.
✨ 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 rust-pump-state-machine

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

codescene-access[bot]

This comment was marked as outdated.

sourcery-ai[bot]

This comment was marked as resolved.

@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: 35c23b6c79

ℹ️ 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 rust/cuprum-rust/src/lib.rs Outdated
Comment thread rust/cuprum-rust/src/pump_machine.rs
codescene-access[bot]

This comment was marked as outdated.

CodeScene flagged broken_pipe_latches_closed_and_counts_accepted_bytes
and completed_write_keeps_open_and_counts_bytes as structurally similar.
Fold them into write_counts_bytes_and_latches_by_variant, which drives an
arbitrary WriteEvent and proves both invariants at once: either outcome
counts its accepted bytes, and only a completed write keeps the writer
open. Kani still verifies (5/5 harnesses SUCCESSFUL); code health 10.0.

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.

Two review findings on the pump state machine:

- lib.rs had grown to 402 lines, over the 400-line maximum in AGENTS.md.
  Move classify_write into io_utils, which already owns handle_write,
  WriteOutcome, and the non-fatal error partition it collapses, so the
  write boundary's final step sits with the rest of that boundary. lib.rs
  drops to 388 lines; io_utils/mod.rs (335) and pump_machine.rs (242) stay
  well inside the cap.
- AGENTS.md requires function documentation to demonstrate usage and
  outcome. Add worked examples to pump_machine::step (a completed write,
  then a broken pipe latching closed, draining, and stopping on EOF) and
  to classify_write (complete versus latched-closed after the reader
  hangs up).

Behaviour is unchanged: nextest 59/59 and Kani 5/5 harnesses still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

Repository owner deleted a comment from coderabbitai Bot Jul 28, 2026
@leynos

leynos commented Jul 28, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai Has this now been resolved in the latest commit?

Please use codegraph analysis to determine your answer.

If this comment is now resolved, please mark it as such using the API. Otherwise, please provide an AI agent prompt for the remaining work to be done to address this comment.

## Overall Comments
- The contract that `step` should only receive `Some(write)` when `read == ReadEvent::Chunk` and the writer is open is currently only documented and reimplemented in helpers like `drive`; consider encoding this by narrowing the API (e.g., a separate `step_on_chunk_with_write` or a small wrapper type) so misuse of `step` is harder outside the current call sites.

## Individual Comments

### Comment 1
<location path="rust/cuprum-rust/src/lib_tests.rs" line_range="187-196" />
<code_context>
-    (result, total_written)
-}
-
-#[rstest]
-fn handle_write_result_updates_total_on_success(pipe: (OwnedFd, OwnedFd)) {
-    let (read_end, mut write_end) = pipe;
</code_context>
<issue_to_address>
**issue (testing):** Add tests for non-fatal short write and non-fatal error paths in `classify_write`

These tests only cover the `Complete` and fatal error paths; the two non-fatal branches remain untested:

```rust
Ok(WriteOutcome::NonFatalShortWrite(bytes)) => Ok(WriteEvent::Closed { bytes }),
Err(err) if err.is_nonfatal_write() => Ok(WriteEvent::Closed { bytes: 0 }),
```

Please add:
1. A test where the descriptor produces a non-fatal short write and `classify_write` returns `WriteEvent::Closed { bytes: <accepted> }` with `bytes > 0`.
2. A test where a non-fatal error occurs before any bytes are accepted and `classify_write` returns `WriteEvent::Closed { bytes: 0 }`.

These will ensure the new IO seam matches the previous `handle_write_result` behaviour and that accounting for non-fatal outcomes is covered by regression tests.
</issue_to_address>

### Comment 2
<location path="rust/cuprum-rust/src/lib.rs" line_range="300" />
<code_context>
     loop {
         let read_len = read_stream(reader, &mut buffer)?;
-        if read_len == 0 {
+        let read = if read_len == 0 {
+            ReadEvent::Eof
+        } else {
</code_context>
<issue_to_address>
**issue (complexity):** Consider moving the construction of `ReadEvent`/`WriteEvent` into a helper like `drive_step` in `pump_machine` so the hot loop works directly with `read_len` and `WriteOutcome` again.

You can keep the pure state machine but hide the event-level indirection from the hot loop by pushing the `ReadEvent`/`WriteEvent` mapping into `pump_machine` (or a thin wrapper), and have the loop work with raw outcomes again.

For example, instead of constructing `ReadEvent`, `Option<WriteEvent>`, and calling `step` directly from the loop, add a helper that matches the original loop’s shape:

```rust
// in pump_machine.rs
pub fn drive_step(
    state: &mut PumpState,
    read_len: usize,
    write: Option<WriteOutcome>,
) -> Flow {
    let read = if read_len == 0 {
        ReadEvent::Eof
    } else {
        ReadEvent::Chunk
    };

    let write_event = write.map(|outcome| match outcome {
        WriteOutcome::Complete(bytes) =>
            WriteEvent::Complete { bytes },
        WriteOutcome::NonFatalShortWrite(bytes) =>
            WriteEvent::Closed { bytes },
    });

    step(state, read, write_event)
}
```

Then the hot path becomes much closer to the original “read → maybe write → advance state” logic:

```rust
let mut buffer = vec![0_u8; buffer_size.value()];
let mut state = PumpState::start();

loop {
    let read_len = read_stream(reader, &mut buffer)?;

    let write = if read_len > 0 && state.writer_open() {
        let chunk = buffer
            .get(..read_len)
            .ok_or(PumpError::BufferRangeExceeded)?;
        Some(handle_write(writer, chunk)?)
    } else {
        None
    };

    if drive_step(&mut state, read_len, write) == Flow::Stop {
        break;
    }
}

let total_written = state.total_written();
```

This keeps:

- The pure, testable state machine (`step`, `PumpState`, `Flow`, `ReadEvent`, `WriteEvent`).
- The new `WriteOutcome` / `handle_write` behavior.

But the caller no longer has to reason about domain-specific enums; it just passes `read_len` and an optional `WriteOutcome`, preserving the original, local invariants while still allowing exhaustive property tests on `step`/`drive_step`.
</issue_to_address>

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== PR head and changed files ==='
gh pr view 242 --repo leynos/cuprum --json headRefName,headRefOid,files --jq '{headRefName, headRefOid, files: [.files[].path]}'

echo '=== Relevant Rust file map ==='
fd -t f -e rs . rust/cuprum-rust | sort

echo '=== Symbol outlines ==='
for f in rust/cuprum-rust/src/lib.rs rust/cuprum-rust/src/lib_tests.rs rust/cuprum-rust/src/pump_machine.rs; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    ast-grep outline "$f" --items all || true
  fi
done

echo '=== State-machine and classification references ==='
rg -n -C 5 -P '\b(step|drive|drive_step|classify_write|ReadEvent|WriteEvent|WriteOutcome|PumpState)\b' \
  rust/cuprum-rust/src/lib.rs \
  rust/cuprum-rust/src/lib_tests.rs \
  rust/cuprum-rust/src/pump_machine.rs 2>/dev/null || true

echo '=== CodeRabbit review threads ==='
gh api graphql -F owner=leynos -F name=cuprum -F number=242 -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          id
          isResolved
          path
          line
          comments(first: 20) {
            nodes { author { login } body url }
          }
        }
      }
    }
  }
}' --jq '.data.repository.pullRequest.reviewThreads.nodes[]
 | select(any(.comments.nodes[]?; .author.login == "coderabbitai"))
 | {id,isResolved,path,line,comments:[.comments.nodes[] | {author:.author.login,body,url}]}'

Length of output: 56356


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== io_utils structure and relevant test symbols ==='
ast-grep outline rust/cuprum-rust/src/io_utils/mod.rs --items all
ast-grep outline rust/cuprum-rust/src/io_utils/tests.rs --items all

echo '=== All direct classify_write calls and tests ==='
rg -n -C 8 -P '\bclassify_write\s*\(' rust/cuprum-rust/src

echo '=== Non-fatal write fixtures and assertions ==='
rg -n -C 8 -P '(NonFatalShortWrite|BrokenPipe|ConnectionReset|WriteEvent::Closed|map_short_write_error|write_all_.*_with)' \
  rust/cuprum-rust/src/io_utils rust/cuprum-rust/src/lib_tests.rs

echo '=== Production callers of state transition APIs ==='
rg -n -C 5 -P '\b(step|drive_step|drive)\s*\(' rust/cuprum-rust/src \
  -g '!rust/cuprum-rust/src/pump_machine_kani_proofs.rs'

echo '=== Exact matching CodeRabbit review threads ==='
gh api graphql -F owner=leynos -F name=cuprum -F number=242 -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          id isResolved path line
          comments(first: 30) { nodes { author { login } body url } }
        }
      }
    }
  }
}' --jq '
  .data.repository.pullRequest.reviewThreads.nodes[]
  | select(any(.comments.nodes[]?;
      .author.login == "coderabbitai"
      and (
        (.body | contains("non-fatal short write"))
        or (.body | contains("construction of `ReadEvent`/`WriteEvent`"))
        or (.body | contains("contract that `step` should only receive"))
      )
    ))
  | {id,isResolved,path,line,comments:[.comments.nodes[] | {author:.author.login,body,url}]}'

Length of output: 47595


@leynos, leave this comment unresolved. The latest commit does not address the requested work:

  • classify_write has direct tests only for WriteEvent::Complete and fatal-error propagation in rust/cuprum-rust/src/lib_tests.rs. Lower-level tests cover WriteOutcome::NonFatalShortWrite, but neither non-fatal branch is tested through classify_write.
  • pump_stream_files_readwrite still maps read_len to ReadEvent and passes Option<WriteEvent> directly to step in rust/cuprum-rust/src/lib.rs.
  • step still accepts ReadEvent plus Option<WriteEvent> independently. The only policy-enforcing drive helper is test-only, so invalid combinations remain representable by the production API.
  • The GitHub API returned no matching CodeRabbit review thread for these supplied findings, so no exact thread can be safely resolved through the API.

Use this AI agent prompt:

Update PR `#242` to resolve the remaining pump-machine review feedback.

Scope:
- rust/cuprum-rust/src/pump_machine.rs
- rust/cuprum-rust/src/io_utils/mod.rs
- rust/cuprum-rust/src/io_utils/tests.rs
- rust/cuprum-rust/src/lib.rs
- rust/cuprum-rust/src/lib_tests.rs

Requirements:

1. Encode the `step` write precondition in the production API.
   - Prevent callers from supplying a write event for `ReadEvent::Eof` or after `PumpState::writer_open()` becomes false.
   - Replace the permissive `step(state, ReadEvent, Option<WriteEvent>)` entry point with a narrowed transition API, or add a wrapper type/API that makes valid transitions explicit.
   - Keep the pure state-machine logic and its property/Kani verification intact.
   - Update the property-test driver so it exercises the production transition API rather than reimplementing its policy only inside test code.

2. Remove event-mapping indirection from the I/O hot loop.
   - Add a production helper in `pump_machine.rs` that accepts the raw read length and an optional write outcome/event in the shape appropriate to the narrowed API.
   - Move `read_len == 0` → EOF/chunk translation and write-event conversion into that helper.
   - Refactor `pump_stream_files_readwrite` in `lib.rs` so it retains the simple flow: read bytes → write only if bytes were read and the writer remains open → advance state.
   - Preserve fatal-error propagation outside the pure state machine.

3. Add direct regression tests for both non-fatal `classify_write` mappings.
   - Cover a non-fatal short write after positive accepted progress and assert `WriteEvent::Closed { bytes: accepted }`, where `accepted > 0`.
   - Cover a non-fatal error before progress and assert `WriteEvent::Closed { bytes: 0 }`.
   - Do not rely only on tests for `map_short_write_error` or `write_all_unix_with`; exercise the `classify_write` boundary itself.
   - If direct descriptor setup cannot deterministically force partial progress, extract a narrowly scoped injectable classifier seam so tests can supply `WriteOutcome::NonFatalShortWrite(accepted)` and a `BrokenPipe`/`ConnectionReset` error without weakening production behaviour.

4. Preserve current semantics.
   - Completed writes increase the total and keep the writer open.
   - Non-fatal writes account for accepted bytes once, latch the writer closed, and drain subsequent reads without further writes.
   - EOF stops without applying a write.
   - Fatal write errors propagate as `PumpError`.

Add focused unit/property tests for the narrowed API, run the repository’s Rust formatting/lint/test gates, and report the exact commands and results.

@pandalump

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.

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

@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: 4

🤖 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 `@rust/cuprum-rust/src/io_utils/mod.rs`:
- Line 12: Update write_all_unix_with to remove the unreachable Err(err) if
err.is_nonfatal_write() branch, since these errors are already converted to
WriteOutcome::NonFatalShortWrite. Add coverage for the
Ok(WriteOutcome::NonFatalShortWrite) handling path in both zero-byte and
positive-byte scenarios.

In `@rust/cuprum-rust/src/lib_tests.rs`:
- Around line 187-211: Add direct tests for the non-fatal short-write branch of
classify_write, covering WriteEvent::Closed with both zero and positive accepted
byte counts. Construct a writer or pipe scenario that causes a short write, then
assert the returned Closed event preserves the accepted byte count, while
keeping the existing Complete and fatal PumpError::Io coverage unchanged.

In `@rust/cuprum-rust/src/lib.rs`:
- Around line 296-323: Replace the manual read/write event construction in the
loop with the production drive_step-style helper exported from pump_machine.
Move ownership of the “write only for chunks while the writer is open”
precondition into that helper, and update the test-only drive helper to reuse it
so runtime and property-test behavior share the same path.

In `@rust/cuprum-rust/src/pump_machine.rs`:
- Around line 120-133: Extract a production drive_step API near step that
encodes the valid write-transition precondition, invoking the supplied write
operation only for an open writer receiving ReadEvent::Chunk and rejecting or
preventing writes for EOF or a latched-closed writer. Update
pump_stream_files_readwrite and the test drive helper to use drive_step, so
invariants_hold_across_iterations and writes_stop_after_broken_pipe exercise the
same transition contract instead of reimplementing it.
🪄 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: 7bf48634-692d-4d27-83e0-655f53e188fc

📥 Commits

Reviewing files that changed from the base of the PR and between 302858c and 81d118f.

📒 Files selected for processing (7)
  • docs/developers-guide.md
  • rust/cuprum-rust/src/io_utils/mod.rs
  • rust/cuprum-rust/src/io_utils/tests.rs
  • rust/cuprum-rust/src/lib.rs
  • rust/cuprum-rust/src/lib_tests.rs
  • rust/cuprum-rust/src/pump_machine.rs
  • rust/cuprum-rust/src/pump_machine_kani_proofs.rs
🔗 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)

use std::io;

use crate::errors::PumpError;
use crate::pump_machine::WriteEvent;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -A 15 'fn write_all_unix\b' rust/cuprum-rust/src/io_utils/mod.rs
rg -n -A 10 'fn handle_write\b' rust/cuprum-rust/src/io_utils/mod.rs
rg -n 'is_nonfatal_write|map_short_write_error' rust/cuprum-rust/src/io_utils/mod.rs

Repository: leynos/cuprum

Length of output: 1540


🏁 Script executed:

#!/bin/bash
sed -n '110,210p' rust/cuprum-rust/src/io_utils/mod.rs
printf '\n---\n'
sed -n '235,340p' rust/cuprum-rust/src/io_utils/mod.rs
printf '\n---\n'
rg -n -A 6 -B 6 'WriteEvent::Closed|NonFatalShortWrite|is_nonfatal_write|map_short_write_error' rust/cuprum-rust/src/io_utils/mod.rs

Repository: leynos/cuprum

Length of output: 10447


🏁 Script executed:

#!/bin/bash
rg -n -A 8 -B 8 'fn (write_all_unix_with|write_all_windows|classify_write|handle_write)\b' rust/cuprum-rust/src/io_utils/mod.rs
printf '\n--- TESTS ---\n'
rg -n -A 6 -B 6 'short write|BrokenPipe|ConnectionReset|WriteEvent::Closed|NonFatalShortWrite' rust/cuprum-rust/src/io_utils/mod.rs

Repository: leynos/cuprum

Length of output: 5499


🏁 Script executed:

#!/bin/bash
sed -n '235,335p' rust/cuprum-rust/src/io_utils/mod.rs

Repository: leynos/cuprum

Length of output: 3710


Remove the dead non-fatal Err branch and add NonFatalShortWrite coverage rust/cuprum-rust/src/io_utils/mod.rs:158-162

write_all_unix_with already folds broken-pipe and connection-reset errors into WriteOutcome::NonFatalShortWrite, so Err(err) if err.is_nonfatal_write() never fires here. Delete that branch, and add tests for the Ok(WriteOutcome::NonFatalShortWrite) arm with both zero-byte and positive-byte cases.

🤖 Prompt for 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.

In `@rust/cuprum-rust/src/io_utils/mod.rs` at line 12, Update write_all_unix_with
to remove the unreachable Err(err) if err.is_nonfatal_write() branch, since
these errors are already converted to WriteOutcome::NonFatalShortWrite. Add
coverage for the Ok(WriteOutcome::NonFatalShortWrite) handling path in both
zero-byte and positive-byte scenarios.

Comment on lines +187 to +211
#[rstest]
fn classify_write_reports_a_completed_write() {
let (read_end, mut write_end) = make_pipe();

let event = match classify_write(&mut write_end, b"chunk") {
Ok(event) => event,
Err(err) => panic!("write to an open pipe failed: {err:?}"),
};

assert_eq!(event, WriteEvent::Complete { bytes: 5 });
// Keep the read end open until after the write so the pipe never breaks.
drop(read_end);
}

#[rstest]
fn classify_write_propagates_a_fatal_error() {
// Writing to the read end of a pipe is a fatal `EBADF`, which must
// propagate rather than latch the writer closed.
let (mut read_end, _write_end) = make_pipe();

match classify_write(&mut read_end, b"chunk") {
Ok(event) => panic!("expected a fatal write error, got {event:?}"),
Err(err) => assert!(matches!(err, PumpError::Io(_))),
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Add the missing WriteEvent::Closed coverage for classify_write.

Both new tests are sound, but they only exercise Complete and the fatal Err path — the non-fatal short-write outcome (WriteEvent::Closed, zero and positive accepted bytes) still has no direct test, which is exactly the gap the PR calls out as unresolved. See the io_utils/mod.rs comment on classify_write for the branch these tests should target.

🤖 Prompt for 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.

In `@rust/cuprum-rust/src/lib_tests.rs` around lines 187 - 211, Add direct tests
for the non-fatal short-write branch of classify_write, covering
WriteEvent::Closed with both zero and positive accepted byte counts. Construct a
writer or pipe scenario that causes a short write, then assert the returned
Closed event preserves the accepted byte count, while keeping the existing
Complete and fatal PumpError::Io coverage unchanged.

Comment on lines +296 to +323
let mut state = PumpState::start();

loop {
let read_len = read_stream(reader, &mut buffer)?;
if read_len == 0 {
let read = if read_len == 0 {
ReadEvent::Eof
} else {
ReadEvent::Chunk
};

// Write only when a chunk arrives while the writer is open; a closed
// writer just drains the reader. Fatal writes propagate the real error
// and never reach the pure state machine.
let write = if read == ReadEvent::Chunk && state.writer_open() {
let chunk = buffer
.get(..read_len)
.ok_or(PumpError::BufferRangeExceeded)?;
Some(classify_write(writer, chunk)?)
} else {
None
};

if step(&mut state, read, write) == Flow::Stop {
break;
}
if !writer_open {
continue;
}

let chunk = buffer
.get(..read_len)
.ok_or(PumpError::BufferRangeExceeded)?;

writer_open = handle_write_result(writer, chunk, &mut total_written)?;
}

let total_written = state.total_written();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Loop hand-constructs pump events instead of calling a production driving helper.

This mirrors the gap flagged in pump_machine.rs: the "write iff ReadEvent::Chunk and the writer is open" rule (line 309) is duplicated here and in the pump module's test-only drive helper, rather than owned by one production entry point. Route this loop through a drive_step-style helper exported from pump_machine so the precondition lives in a single place and both runtime code and property tests exercise it identically.

🤖 Prompt for 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.

In `@rust/cuprum-rust/src/lib.rs` around lines 296 - 323, Replace the manual
read/write event construction in the loop with the production drive_step-style
helper exported from pump_machine. Move ownership of the “write only for chunks
while the writer is open” precondition into that helper, and update the
test-only drive helper to reuse it so runtime and property-test behavior share
the same path.

Comment on lines +120 to +133
pub(crate) fn step(state: &mut PumpState, read: ReadEvent, write: Option<WriteEvent>) -> Flow {
match read {
ReadEvent::Eof => Flow::Stop,
ReadEvent::Chunk => {
if state.writer_open {
if let Some(event) = write {
apply_write(state, event);
}
}
// A closed writer drains the reader: the state is left unchanged.
Flow::Continue
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Narrow step's precondition instead of proving around it.

step accepts write: Option<WriteEvent> unconditionally but only honours it when read == ReadEvent::Chunk && state.writer_open; feed it Some(write) alongside ReadEvent::Eof, or while the writer has already latched closed, and it silently drops the value instead of rejecting the call. The Kani proofs (closed_writer_drains_without_writing, loop_stops_exactly_on_eof) only confirm that misuse is currently harmless — they don't stop a future caller from constructing an invalid combination undetected.

The test module compounds this: drive (lines 158-161) re-implements the "write iff an open Chunk" rule as a test-only wrapper rather than routing through a production API that encodes it, so invariants_hold_across_iterations and writes_stop_after_broken_pipe never exercise the real transition contract that pump_stream_files_readwrite relies on.

Extract a production drive_step here that owns the rule (e.g. taking a closure that only runs — and only may run — when a write is warranted) so the fallback loop in rust/cuprum-rust/src/lib.rs and the property tests both drive the machine through the same narrowed entry point.

Also applies to: 158-161

🤖 Prompt for 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.

In `@rust/cuprum-rust/src/pump_machine.rs` around lines 120 - 133, Extract a
production drive_step API near step that encodes the valid write-transition
precondition, invoking the supplied write operation only for an open writer
receiving ReadEvent::Chunk and rejecting or preventing writes for EOF or a
latched-closed writer. Update pump_stream_files_readwrite and the test drive
helper to use drive_step, so invariants_hold_across_iterations and
writes_stop_after_broken_pipe exercise the same transition contract instead of
reimplementing it.

Four review findings, all valid in substance.

classify_write carried an Err(err) if err.is_nonfatal_write() arm that
cannot be reached: map_short_write_error already converts broken-pipe and
connection-reset failures into Ok(WriteOutcome::NonFatalShortWrite), and
every other error escaping handle_write — WriteZero, LengthOverflow,
BufferRangeExceeded, and the fatal partition — reports false from
is_nonfatal_write. Remove it, and split the pure mapping into
classify_write_outcome so the NonFatalShortWrite path can be covered for
both a zero and a positive accepted byte count without contriving a
partial write against a real descriptor. The finding placed this in
write_all_unix_with, which has no such arm; the substance applies to
classify_write.

The write precondition — a chunk read while the writer is still open —
was written out twice, once in the pump loop and once in the property
tests' drive helper, so the properties exercised their own copy rather
than the production path. Add drive_step next to step, which owns that
precondition and invokes the supplied write only when the transition
permits one; both the loop and the tests now go through it.

That extraction needed its own coverage, and finding it took a mutation
check: step independently ignores a write it is handed, so a drive_step
that dropped the precondition leaves the resulting state identical and
differs only in performing a spurious write. The first mutation therefore
survived. The tests now assert whether the write operation ran rather than
only its effect — three focused cases plus an invocation clause folded
into the main property — and both mutations (dropping the writer_open
guard, and writing on EOF) now fail.

Kani still verifies 5/5 harnesses, nextest is 66/66, and the developers
guide records why the invocation is what must be asserted.

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

Copy link
Copy Markdown
Collaborator

All four findings fixed in 5c65637. All were valid in substance; one was mis-anchored, and one turned out to need more than it asked for.

1. The unreachable non-fatal Err arm

Valid, though the comment placed it in write_all_unix_with, which has no is_nonfatal_write branch — the arm is in classify_write.

The unreachability claim holds. I traced every Err that can escape handle_write:

  • map_short_write_error returns Ok(NonFatalShortWrite) for exactly the non-fatal partition and Err otherwise;
  • the zero-progress path yields WriteZero, and record_write_progress yields LengthOverflow / BufferRangeExceeded;
  • is_nonfatal_write matches only Io(BrokenPipe | ConnectionReset), so none of those qualify.

Every Err arriving at classify_write is therefore fatal, and the guard was dead. Removed. (I had flagged it as "defensive parity with the original" when writing it — you are right that dead is dead.)

2. Coverage for both NonFatalShortWrite scenarios

Valid. The zero-byte case is reachable end-to-end with a broken pipe, but a positive accepted count needs a partial write followed by EPIPE, which is not deterministic against a real descriptor.

So I split the pure mapping out as classify_write_outcome, leaving classify_write as the thin I/O wrapper. Both byte counts are now covered deterministically via an rstest case pair, alongside the Complete and fatal mappings. The existing pipe-backed Complete and fatal PumpError::Io tests in lib_tests.rs are unchanged.

3 & 4. drive_step — and the gap it exposed

Valid, and this was the most useful finding. The precondition was written out twice — once in the pump loop, once in the tests' drive helper — so the properties exercised their own copy. drive_step now sits beside step, owns the precondition, and is used by both.

The extraction did not verify itself, and I only found that by mutation-checking it. Removing state.writer_open() from drive_step initially left all 63 tests green. The reason: step independently re-checks writer_open and ignores a write it is handed, so a drive_step without the guard produces identical state — it differs only in having performed a spurious write against a closed writer. Observing state alone cannot distinguish them.

The tests now assert whether the write operation ran, not merely its effect:

  • drive_step_skips_the_write_at_eof
  • drive_step_skips_the_write_once_the_writer_is_closed
  • drive_step_performs_the_write_for_a_chunk_while_open
  • plus an invocation clause folded into invariants_hold_across_iterations, so random scripts cover it too

Both mutations now fail: dropping the writer_open guard (previously survived) and writing on EOF.

Validation

Command Outcome
cargo nextest run 66/66 passed (was 59)
cargo kani (5 harnesses) 5/5 VERIFICATION:- SUCCESSFUL
make check-fmt pass
make lint pass (clippy -D warnings, cargo doc -D warnings, ruff, interrogate, pylint)
make typecheck pass
make test pass — full Python suite plus nextest 66/66
make markdownlint / make nixie pass

lib.rs is 387 lines, still inside the 400-line cap. The developers guide now records why the invocation is the thing to assert, so the next person changing drive_step does not have to rediscover it by mutation.

@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.

Gates Failed
New code is healthy (1 new file with code health below 10.00)
Enforce advisory code health rules (1 file with Code Duplication)

Our agent can fix these. Install it.

Gates Passed
4 Quality Gates Passed

Reason for failure
New code is healthy Violations Code Health Impact
pump_machine.rs 1 rule 9.39 Suppress
Enforce advisory code health rules Violations Code Health Impact
pump_machine.rs 1 advisory rule 9.39 Suppress

See analysis details in CodeScene

Quality Gate Profile: Pay Down Tech Debt
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

Comment on lines +213 to +225
fn drive_step_skips_the_write_at_eof() {
let mut state = PumpState::start();

let (flow, invoked) = drive_counting(
&mut state,
ReadEvent::Eof,
WriteEvent::Complete { bytes: 4 },
);

assert_eq!(flow, Flow::Stop);
assert!(!invoked, "EOF must not attempt a write");
assert_eq!(state.total_written(), 0, "EOF must accrue no bytes");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: tests.drive_step_performs_the_write_for_a_chunk_while_open,tests.drive_step_skips_the_write_at_eof

Suppress

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.

Rust: proptest + Kani state-machine tests for pump_stream_files_readwrite (rust/cuprum-rust/src/lib.rs)

2 participants