Skip to content

Rust: snapshot tests for consume_stream UTF-8 replacement output (#105) - #241

Merged
leynos merged 9 commits into
mainfrom
rust-consume-stream-snapshots
Aug 2, 2026
Merged

Rust: snapshot tests for consume_stream UTF-8 replacement output (#105)#241
leynos merged 9 commits into
mainfrom
rust-consume-stream-snapshots

Conversation

@leynos

@leynos leynos commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Summary

consume_stream_files (the read-and-decode loop behind the PyO3 rust_consume_stream) had no Rust-level test pinning its observable UTF-8 output. Only the incremental decoder in utf8.rs was property-tested (against String::from_utf8_lossy), and the compile-time UI tests only check PyO3 macro patterns. This adds insta snapshot tests exercising the full loop.

What's added

rust/cuprum-rust/src/consume_snapshot_tests.rs drives real pipe descriptors through consume_stream_files and pins the exact output for the four categories from #105:

Test Category Payload Buffer
pure_ascii_decodes_verbatim Pure ASCII cuprum reads pipes 64
multibyte_sequences_split_across_buffer_boundaries Multi-byte split across boundaries héllo, 世界! ☕ 1
invalid_bytes_become_replacement_characters Invalid bytes → U+FFFD x\xffy\x80z 1
incomplete_trailing_sequence_is_replaced_at_eof Incomplete trailing seq at EOF euro sign: \xe2\x82 1

The multi-byte, invalid, and incomplete cases additionally assert boundary-independence — a one-byte buffer, a three-byte buffer, and a whole-payload buffer all decode to the same text — so a regression in the bounds-checked slicing or final_chunk handling surfaces as a concrete diff.

insta is added as a default-features = false dev-dependency (v1.48.0); the snapshots are inline, so there are no separate .snap files to manage.

Boundary already covered on the Python side

The issue's second bullet (integration through the Python/Rust boundary) is already satisfied by cuprum/unittests/test_rust_streams.py::TestRustConsumeStream, which feeds ASCII, a boundary-split multi-byte scalar (buffer_size=2), invalid bytes, and an incomplete trailing sequence through rust_consume_stream and asserts against payload.decode("utf-8", errors="replace"). These Rust snapshots complement that by pinning the exact decoded text one layer down, at the read loop itself.

Validation

Full gates green: make check-fmt, make lint (clippy -D warnings, cargo doc -D warnings, interrogate 100%, pylint 10.00/10), make test (pytest 755 passed / 47 skipped; Rust nextest 61/61 including the four new cases).

Closes #105

🤖 Generated with Claude Code

Summary by Sourcery

Add Rust snapshot tests to pin the UTF-8 replacement behavior of the consume_stream_files read loop using real pipe descriptors.

Build:

  • Add insta as a dev-dependency for Rust snapshot testing.

Tests:

  • Add Unix-only snapshot tests covering ASCII, multibyte boundary splits, invalid bytes, and incomplete trailing UTF-8 sequences for consume_stream_files, asserting buffer-size-independent decoding.

@sourcery-ai

sourcery-ai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds Unix-only Rust snapshot tests using insta to pin the exact UTF-8 replacement behavior of consume_stream_files, and wires in the insta dev-dependency and test module registration.

Sequence diagram for consume_stream_files UTF-8 snapshot tests

sequenceDiagram
    participant TestRunner
    participant consume_snapshot_tests
    participant consume_stream_files
    participant insta

    TestRunner->>consume_snapshot_tests: pure_ascii_decodes_verbatim
    consume_snapshot_tests->>consume_stream_files: consume_stream_files(read_fd, write_fd, buffer_size)
    consume_stream_files-->>consume_snapshot_tests: decoded_text
    consume_snapshot_tests->>insta: assert_snapshot(decoded_text)

    TestRunner->>consume_snapshot_tests: invalid_bytes_become_replacement_characters
    consume_snapshot_tests->>consume_stream_files: consume_stream_files(read_fd, write_fd, buffer_size)
    consume_stream_files-->>consume_snapshot_tests: decoded_text
    consume_snapshot_tests->>insta: assert_snapshot(decoded_text)

    TestRunner->>consume_snapshot_tests: incomplete_trailing_sequence_is_replaced_at_eof
    consume_snapshot_tests->>consume_stream_files: consume_stream_files(read_fd, write_fd, buffer_size)
    consume_stream_files-->>consume_snapshot_tests: decoded_text
    consume_snapshot_tests->>insta: assert_snapshot(decoded_text)
Loading

File-Level Changes

Change Details Files
Add end-to-end snapshot tests for UTF-8 decoding and replacement behavior of the consume_stream read loop.
  • Introduce a Unix-only test module that exercises consume_stream_files via real OS pipes
  • Implement a helper function that writes a payload into a pipe and runs consume_stream_files with a configurable buffer size
  • Add snapshot tests covering pure ASCII, multibyte sequences across buffer boundaries, invalid bytes mapping to U+FFFD, and incomplete trailing sequences at EOF
  • Assert boundary-independence by comparing outputs across different buffer sizes for multibyte, invalid, and incomplete-sequence cases
rust/cuprum-rust/src/consume_snapshot_tests.rs
rust/cuprum-rust/src/lib.rs
Wire in insta as a dev-dependency for Rust tests.
  • Add insta as a dev-dependency with default-features = false in the crate manifest
  • Update Cargo.lock to capture the new insta dependency graph
rust/cuprum-rust/Cargo.toml
rust/Cargo.lock

Assessment against linked issues

Issue Objective Addressed Explanation
#105 Add Rust-level snapshot tests (e.g., using insta) asserting the exact UTF-8 replacement output of consume_stream/consume_stream_files for: pure ASCII input, multi-byte UTF-8 sequences split across buffer boundaries, invalid byte sequences (U+FFFD replacement), and incomplete trailing sequences at EOF.
#105 Add or update integration tests that exercise the refactored Rust I/O abstraction end-to-end through the Python/Rust boundary (via rust_consume_stream) for the same UTF-8 replacement scenarios. The PR only adds Rust insta snapshot tests in consume_snapshot_tests.rs and wires them into the test module list. It does not add or modify any Python-side integration tests or Rust-PyO3 boundary tests; it merely notes that such tests already exist. Therefore, the PR itself does not implement this integration-testing objective.

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: b95422ff-766a-46b7-b832-9c7a8be12e6b

📥 Commits

Reviewing files that changed from the base of the PR and between 78e53e5 and c1494db.

⛔ Files ignored due to path filters (1)
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • docs/developers-guide.md
  • rust/cuprum-rust/Cargo.toml
  • rust/cuprum-rust/src/consume_snapshot_tests.rs
  • rust/cuprum-rust/src/lib.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)

Summary

  • Add insta v1.48.0 as a development dependency.
  • Add Rust snapshot and property tests for consume_stream_files.
  • Cover ASCII, split multibyte UTF-8, invalid bytes, and incomplete trailing sequences.
  • Verify output against String::from_utf8_lossy and confirm buffer-boundary independence.
  • Document snapshot workflows and distinguish Rust tests from skipped Python/Rust extension tests.
  • Track remaining CI extension builds under #258 and the PyO3 OSError.errno defect under #265.
  • Address issue #105’s Rust test coverage. Keep the end-to-end objective as follow-up work until CI builds the native extension.

Walkthrough

Changes

Add Unix-only end-to-end UTF-8 snapshot and property tests for consume_stream_files. Configure insta, register the test module, and document proptest and inline snapshot workflows.

UTF-8 stream snapshot tests

Layer / File(s) Summary
Test harness and module wiring
rust/cuprum-rust/Cargo.toml, rust/cuprum-rust/src/lib.rs, rust/cuprum-rust/src/consume_snapshot_tests.rs, docs/developers-guide.md
Configure insta, register Unix test coverage, add pipe-backed execution, and document test workflows.
Fixed and property-based UTF-8 cases
rust/cuprum-rust/src/consume_snapshot_tests.rs
Validate ASCII, split multi-byte sequences, invalid bytes, incomplete trailing sequences, String::from_utf8_lossy equivalence, and buffer-size independence.

Possibly related PRs

Suggested labels: Issue

Suggested reviewers: codescene-access

Poem

Pipes carry bytes through the night,
Snapshots mark each replacement right.
Split sequences reunite,
EOF closes the final rite,
Property tests keep boundaries tight.


Important

Pre-merge checks failed

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

❌ Failed checks (2 warnings, 3 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Reject the issue assessment because snapshot coverage is complete, but the requested Python/Rust integration objective remains unexecuted and deferred. Add or execute the required end-to-end Python/Rust integration tests, or update issue #105 to separate the deferred CI and PyO3 work.
Testing (Unit And Behavioural) ⚠️ Warning The new tests call private consume_stream_files directly; rust_consume_stream boundary tests skip when the extension is absent, so executed end-to-end coverage is missing. Build the native extension in CI and run tests through rust_consume_stream; retain the pipe/property tests and add a consume I/O-error assertion.
Unit Architecture ❓ Inconclusive Investigation in progress. Inspect the changed Rust tests and their I/O helpers before deciding.
Security And Privacy ❓ Inconclusive Investigation is still in progress. Inspect the changed Rust tests, dependency configuration, and documentation for secrets or unsafe boundary handling.
Architectural Complexity And Maintainability ❓ Inconclusive Investigation is still in progress; no assessment submitted yet. Gather repository evidence before deciding.
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed Accept the title because it describes the snapshot-test change and includes the linked issue reference (#105).
Description check ✅ Passed Accept the description because it clearly explains the UTF-8 snapshot tests, coverage, validation, and remaining integration-test limitation.
Out of Scope Changes check ✅ Passed Accept the scope because the dependency, Rust tests, module registration, and developer documentation directly support the linked UTF-8 testing objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Testing (Overall) ✅ Passed The new Unix tests drive real pipes through consume_stream_files, assert four exact UTF-8 outcomes, compare multiple buffer sizes, and add broad from_utf8_lossy properties.
User-Facing Documentation ✅ Passed Keep this check passing: the PR adds only test coverage, a dev dependency, and developer guidance; the existing users guide documents rust_consume_stream and UTF-8 replacement semantics.
Developer Documentation ✅ Passed Developer guide documents proptest seed handling, mutation persistence disablement, insta configuration, inline snapshots, and CI boundary limits; roadmap and completed execplan status are consistent.
Module-Level Documentation ✅ Passed Keep the check passing: lib.rs, consume_snapshot_tests.rs, and its properties module all have //! documentation covering purpose, utility, and component relationships.
Testing (Property / Proof) ✅ Passed Accept the change: consume_snapshot_tests.rs uses proptest! over arbitrary byte payloads and buffer sizes, checking from_utf8_lossy equivalence and boundary independence through real pipes.
Testing (Compile-Time / Ui) ✅ Passed Existing trybuild UI tests cover pass and compile-fail cases; this PR changes no compile-time behaviour and adds focused inline snapshots with boundary assertions for stable UTF-8 output.
Domain Architecture ✅ Passed Keep the check passing: the change adds only Unix, test-gated coverage and a dev-only dependency; production logic and public APIs remain unchanged.
Observability ✅ Passed The changes add only a test-only Unix module, a dev-dependency, and documentation; they do not alter production behaviour or create new operational failure modes.
Performance And Resource Use ✅ Passed Keep the change: new work is test-only; property payloads are capped at 511 bytes, buffers at 64 bytes, and pipe descriptors close per case, with no production hot-path change.
Concurrency And State ✅ Passed Keep the change: the PR adds only Unix test wiring and per-call pipe state; no shared mutable state, async tasks, locks, cancellation, or parallel execution is introduced.
Rust Compiler Lint Integrity ✅ Passed The patch adds no broad lint suppressions, artificial anchors, or clone calls; the new helpers are referenced and the snapshot module is correctly gated to Unix tests.
✨ 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-consume-stream-snapshots

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.

codescene-access[bot]

This comment was marked as outdated.

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

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

🤖 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/consume_snapshot_tests.rs`:
- Around line 7-10: Add end-to-end PyO3 integration cases in
TestRustConsumeStream for multibyte UTF-8 sequences split across buffer
boundaries and incomplete trailing bytes at EOF, matching the documented Python
oracle categories. Keep the existing invalid-byte coverage and ensure these
scenarios cross the Python/Rust boundary before retaining the module’s
full-coverage claim.
- Around line 73-77: Extend the equality assertions in the invalid-byte and
incomplete-tail tests around consume_snapshot_tests.rs to include
consume(payload, 3) alongside the existing buffer sizes 1 and 64. Ensure both
replacement-case tests verify identical output across one-byte, three-byte, and
whole-payload reads.
🪄 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: f93ffd86-9684-4256-a4b7-7d86c0dab710

📥 Commits

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

⛔ Files ignored due to path filters (1)
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • rust/cuprum-rust/Cargo.toml
  • rust/cuprum-rust/src/consume_snapshot_tests.rs
  • rust/cuprum-rust/src/lib.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)

Comment thread rust/cuprum-rust/src/consume_snapshot_tests.rs Outdated
Comment thread rust/cuprum-rust/src/consume_snapshot_tests.rs
@pandalump

Copy link
Copy Markdown
Collaborator

Both addressed in 4f6693d — one applied as asked, one that turned out to be right for a different and more serious reason than stated.

2. Three-byte buffer in the replacement tests ✅

Applied. Both the invalid-byte and incomplete-tail tests now assert identical output across one-byte, three-byte, and whole-payload reads, matching the multibyte test. The three-byte case is genuinely the interesting middle ground for the incomplete tail, since it splits the truncated euro sign differently again.

1. The Python integration cases — investigated, and the docstring corrected instead

The literal request was to add multibyte-split and incomplete-trailing cases to TestRustConsumeStream. I checked before writing them, and both already exist:

Category Test Payload / buffer
ASCII test_decodes_payload[ascii_explicit_default] b"rust-consume-stream", 65536
Multibyte split test_decodes_payload[multibyte_split] b"snowman \xe2\x98\x83", buffer_size 2
Invalid bytes test_replaces_invalid_bytes b"valid-\xff\xfe-end", 3
Incomplete tail test_replaces_incomplete_sequence b"trail-\xe2\x98", 2

Each calls rust_consume_stream and asserts against payload.decode("utf-8", errors="replace").

But your underlying instinct was right, and the reason is worse than a missing test. Chasing "ensure these scenarios cross the Python/Rust boundary", I ran them — and all seven TestRustConsumeStream cases skip:

test_decodes_payload[multibyte_split] SKIPPED
test_replaces_incomplete_sequence    SKIPPED
...
7 skipped

The rust_streams fixture skips unless cuprum._rust_backend_native imports, and nothing on the test path builds it:

  • make build is just uv sync --group dev — no maturin step;
  • the CI typecheck-test job runs only make typecheck and make test;
  • maturin develop appears exactly once in ci.yml, inside the benchmark-ratchet job, which runs benchmarks rather than the suite.

I verified this locally: after make build, importing cuprum._rust_backend_native still raises ModuleNotFoundError.

So adding the two requested cases would have added no coverage at all — they would have skipped alongside the existing four, while making the suite look better than it is. That seemed strictly worse than the honest alternative, so I corrected the overstated claim instead. The docstring no longer says these snapshots mirror what the Python oracle "covers"; it records that the Python suite declares the categories, that they are gated on an extension the test path never builds, and that these Rust-side snapshots are consequently the coverage that actually executes.

Happy to add the Python cases anyway if you would rather have them staged for whenever the extension does get built — say the word.

Worth raising separately

That the Rust-backend Python tests never execute in CI is a real gap well beyond this PR: it silently disables every rust_streams-gated test, not just the consume ones. Building the extension in typecheck-test (or having make build do it) would fix it. I have not touched CI here, since that is disproportionate to a snapshot-tests PR — but I am glad to raise it as an issue if useful.

Validation

Command Outcome
cargo nextest run consume_snapshot 4/4 passed
make check-fmt pass
make lint pass (clippy -D warnings, cargo doc, ruff, interrogate, pylint)
make typecheck pass
make test pass — Rust nextest 61/61, full Python suite

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

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

codescene-access[bot]

This comment was marked as outdated.

@pandalump

Copy link
Copy Markdown
Collaborator

@coderabbitai Both warnings addressed — one by implementing it, one by showing the premise is misplaced and raising the real defect.

Testing (Property / Proof) — taken, in 0240976. Two properties now sit beside the four snapshots in consume_snapshot_tests.rs: for arbitrary bytes and buffer sizes, the loop's output equals String::from_utf8_lossy of the input, and two different buffer sizes decode the same payload identically. Payloads stay under 512 bytes so the helper's unbuffered pipe write cannot block, which is the precondition its docs state. The snapshots stay as regressions, exactly as suggested.

Mutation-verified rather than assumed: never marking the final chunk — so an incomplete trailing sequence is dropped — fails the lossy-equivalence property; treating every chunk as final — so multi-byte sequences split at read boundaries — fails both.

Linked Issues — the tests are not missing. TestRustConsumeStream in cuprum/unittests/test_rust_streams.py already declares the same four categories against rust_consume_stream, checked against the payload.decode("utf-8", errors="replace") oracle. Nothing was skipped by omission and nothing needs unskipping. The reason they do not run is CI wiring:

$ grep -rn "maturin develop" .github/workflows/*.yml
.github/workflows/ci.yml:293:  ... uv run maturin develop \

That single occurrence is inside benchmark-ratchet, which runs benchmarks, not tests. The three jobs that do run the suite reach it through make build, which is only uv sync --group dev. So the compiled extension is never present when the tests run, and every extension-gated module — test_rust_streams.py, test_rust_streams_boundary_property.py, test_rust_extension.py, test_rust_splice.py, and the gated cases in test_backend.py — skips silently on every run.

Adding more Python-side boundary tests to this PR would not change that; they would skip alongside the existing ones and make CI look better covered while testing nothing. I have raised #258 for the actual fix — build the extension in the test jobs, and add a CUPRUM_REQUIRE_RUST_EXTENSION guard so the skip becomes a failure rather than silence. Fixing CI wiring is out of scope for a snapshot-test PR and belongs on its own change where it can be verified.

The general rule I would like applied in future: when a gated test suite appears absent from a diff, check whether the gate ever opens in CI before asking for more tests behind the same gate. The module docstring in consume_snapshot_tests.rs documents this gap, which is why the Rust-side snapshots exist as the coverage that actually executes.

codescene-access[bot]

This comment was marked as outdated.

@coderabbitai

coderabbitai Bot commented Jul 30, 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 '=== Rust test module outline ==='
ast-grep outline rust/cuprum-rust/src/consume_snapshot_tests.rs --items all || true

echo '=== Rust property/snapshot assertions and helper constraints ==='
rg -n -C 5 'proptest!|from_utf8_lossy|buffer_size|512|assert_snapshot|consume\(' \
  rust/cuprum-rust/src/consume_snapshot_tests.rs

echo '=== Make build target ==='
rg -n -A 8 -B 2 '^build:' Makefile

echo '=== Workflow maturin/test-job contexts ==='
rg -n -A 14 -B 14 'maturin develop|make build|make test|typecheck-test|benchmark-ratchet' \
  .github/workflows/ci.yml

echo '=== Python Rust-stream gate contexts ==='
rg -n -A 8 -B 5 '_rust_backend_native|rust_streams|rust_consume_stream|pytest\.skip' \
  cuprum/unittests/test_rust_streams.py \
  cuprum/unittests/test_rust_streams_boundary_property.py \
  cuprum/unittests/test_rust_extension.py \
  cuprum/unittests/test_rust_splice.py \
  cuprum/unittests/test_backend.py

echo '=== Issue `#258` ==='
gh issue view 258 --repo leynos/cuprum --json number,title,state,url,body

Length of output: 74531


@pandalump Mark the property-testing finding as addressed. The two properties correctly establish lossy-decoding equivalence and buffer-boundary independence, while the 512-byte cap documents and respects the pipe helper’s non-blocking precondition.

Withdraw the Python integration-test finding. TestRustConsumeStream already declares and oracle-checks all four requested categories. The evidence confirms that make build only synchronizes dependencies and that maturin develop runs solely in benchmark-ratchet; therefore #258 correctly tracks the actual CI coverage defect and its proposed fail-loud guard.

Keep checking whether a gated suite actually executes in CI before requesting additional tests behind that gate.

🐇 ✅


✏️ Learnings added
Learnt from: pandalump
URL: https://github.com/leynos/cuprum/pull/241

Timestamp: 2026-07-30T13:32:43.354Z
Learning: When reviewing Python/Rust boundary coverage in cuprum, first verify that CI builds and imports `cuprum._rust_backend_native`. `make build` only runs `uv sync --group dev`, while `.github/workflows/ci.yml` currently invokes `maturin develop` only in the `benchmark-ratchet` job; extension-gated Python tests can otherwise exist but skip without executing. GitHub issue `#258` tracks building the extension in test jobs and making its absence fail loudly.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

codescene-access[bot]

This comment was marked as outdated.

@buzzybee-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 30, 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 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: 1

🤖 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/consume_snapshot_tests.rs`:
- Around line 154-162: Update the property test function
decoding_is_independent_of_the_buffer_size to reject cases where first and
second are equal by adding the appropriate property assumption before the
assertion, while preserving the existing buffer-size bounds and comparison.
🪄 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: e53399db-1703-40f3-b957-02903a612093

📥 Commits

Reviewing files that changed from the base of the PR and between faabf07 and 81433c9.

📒 Files selected for processing (2)
  • rust/cuprum-rust/proptest-regressions/consume_snapshot_tests.txt
  • rust/cuprum-rust/src/consume_snapshot_tests.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)

Comment thread rust/cuprum-rust/src/consume_snapshot_tests.rs
codescene-access[bot]

This comment was marked as outdated.

codescene-access[bot]

This comment was marked as outdated.

@lodyai
lodyai Bot force-pushed the rust-consume-stream-snapshots branch from c65eef8 to 78e53e5 Compare July 30, 2026 22:56
codescene-access[bot]

This comment was marked as outdated.

leynos and others added 7 commits July 31, 2026 21:58
consume_stream_files' read-and-decode loop had no Rust-level test pinning
its observable UTF-8 output; only the incremental decoder in utf8.rs was
property-tested, and the PyO3 boundary is covered on the Python side
(test_rust_streams.py::TestRustConsumeStream).

Add src/consume_snapshot_tests.rs with insta inline-snapshot tests that
drive the full loop through a real pipe for the four categories the
Python oracle suite also covers:

- pure ASCII decodes verbatim
- multi-byte sequences split across buffer boundaries (buffer_size 1)
  decode identically to a whole-payload buffer
- invalid bytes become U+FFFD
- an incomplete trailing sequence resolves to U+FFFD at EOF

The boundary-independence assertions pin that the output does not depend
on where reads split a multi-byte sequence. insta is added as a
default-features-off dev-dependency.

Closes #105

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Both replacement tests now assert identical output across one-byte,
three-byte, and whole-payload reads, matching the multibyte test. The
three-byte case is the interesting middle ground for the incomplete tail,
since it splits the truncated euro sign differently again.

The module docstring claimed these snapshots covered "the four categories
the Python oracle suite also covers", which overstated what is verified.
TestRustConsumeStream does declare all four against rust_consume_stream —
ASCII, a boundary-split snowman at buffer_size 2, invalid bytes, and a
truncated trailing sequence — each checked against
payload.decode("utf-8", errors="replace"). But those cases are gated on
the compiled extension being importable, and nothing on the test path
builds it: make build only runs uv sync, and the CI typecheck-test job
runs make typecheck and make test without maturin develop, which appears
only in the benchmark-ratchet job. They therefore skip, locally and in CI.

Adding the two further Python cases the review asked for would have added
no coverage for the same reason, so the docstring now states the position
accurately instead: the Python suite declares the categories, these
snapshots are what actually executes for them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pre-merge Developer Documentation check noted that the guide covers
the Rust test stack and syrupy but never mentions insta, which this branch
adds as a dev-dependency.

Add a paragraph to "Rust property testing and verification" stating when
to reach for insta over a property test — when the valuable assertion is
the exact output text rather than a property of it — and why the snapshots
here are inline rather than .snap files: the expected text stays beside
the case producing it, there are no snapshot files to review or prune, and
accepting a deliberate change means editing the literal rather than
running cargo insta. Records the default-features = false pin too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The four snapshots pin what the loop produces for hand-picked payload
categories, but they cannot show the categories are exhaustive — a payload
whose multi-byte sequence straddles a read boundary in some way none of the
four happens to hit would go unnoticed.

Add two properties over arbitrary bytes and buffer sizes: the loop's output
equals `String::from_utf8_lossy` of the input, and two different buffer
sizes decode the same payload identically. Payloads stay under 512 bytes so
the helper's unbuffered pipe write cannot block, which is the precondition
it documents.

Verified by mutation rather than assumed: never marking the final chunk
(so an incomplete trailing sequence is dropped) fails one property, and
treating every chunk as final (so multi-byte sequences split at read
boundaries) fails both.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Whitaker lint requires a module to document itself from the inside
rather than from the item above it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`first` and `second` were drawn independently, so roughly one case in
sixty-four compared a payload against itself at a single buffer size.
Those cases hold for any implementation, correct or not, and so assert
nothing about boundary independence.

Reject equal pairs with `prop_assume!`. Name the shared bound
`MAX_BUFFER` rather than repeating the literal, so the two properties
cannot drift apart.

An offset-derived second size would avoid the rejection entirely, but it
needs `%`, which the workspace denies via
`clippy::integer-division-remainder-used`. At a 1-in-64 rejection rate
the assumption costs nothing measurable and states the intent directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The committed seeds record cases from mutation testing, not real
failures: `payload = [194]` is an incomplete two-byte UTF-8 lead, which
only failed against a deliberately-broken decoder that never marked the
final chunk. Checking them in re-runs seeds that have never failed
against correct code, and implies to a reader that they once did.

The cases they pin are already covered by name — the incomplete-tail
snapshot test asserts exactly that input.

Ignore the directory so the file cannot return. It has appeared three
times across these branches, each time from a verification run rather
than a genuine shrink.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lodyai
lodyai Bot force-pushed the rust-consume-stream-snapshots branch from 15eafac to a8b7c24 Compare July 31, 2026 20:00
codescene-access[bot]

This comment was marked as outdated.

The snapshot guidance did not say that the same four input categories are
also covered at the Python/Rust boundary, nor why those cases do not run.
A reader could reasonably conclude either that the Rust cases duplicate
existing coverage, or that they discharge the boundary entirely. Neither
is true.

Name `TestRustConsumeStream` and what it asserts, state that `make build`
only synchronizes dependencies so the extension is never built for the
test jobs, and point at the two issues that track the fix: `#258` for
building the extension with a fail-loud guard, and `#265` for the PyO3
`errno` defect that currently fails one extension-enabled test.

Say plainly what the snapshots and properties are: the coverage of the
read-and-decode loop that executes on every commit, and not a substitute
for executed boundary coverage.

Documentation only.

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[bot]

This comment was marked as outdated.

@buzzybee-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 1, 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 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: 1

🤖 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 @.gitignore:
- Around line 212-215: Remove the rust/cuprum-rust/proptest-regressions/ entry
from .gitignore so genuine Proptest regression seeds remain trackable and can be
committed for regression coverage.
🪄 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: 7acd14a8-caac-463f-ac59-6dcb15d2fb0e

📥 Commits

Reviewing files that changed from the base of the PR and between 78e53e5 and ef417a2.

⛔ Files ignored due to path filters (1)
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • .gitignore
  • docs/developers-guide.md
  • rust/cuprum-rust/Cargo.toml
  • rust/cuprum-rust/src/consume_snapshot_tests.rs
  • rust/cuprum-rust/src/lib.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)

Comment thread .gitignore Outdated
Ignoring `rust/cuprum-rust/proptest-regressions/` stopped mutant-derived
seeds returning, but it also foreclosed the case the directory exists
for: a genuine shrink that should be replayed ahead of the generated
cases. A silently-dropped seed is the worse failure of the two, because
nothing signals the loss.

Remove the ignore and address the original problem where it starts.
Proptest reads `PROPTEST_DISABLE_FAILURE_PERSISTENCE`, which sets
`failure_persistence` to `None`, so a verification run against
deliberately-broken code can be told not to write a seed at all rather
than relying on someone noticing the stray file afterwards.

Document both halves: commit a seed from a real failure, and disable
persistence when proving a property is non-vacuous.

Co-Authored-By: Claude Opus 5 (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.

@buzzybee-df12

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 2, 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.

@leynos

leynos commented Aug 2, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai please create a new GitHub issue for the following:

Testing (Unit And Behavioural) ⚠️ Warning The new tests call private consume_stream_files directly; rust_consume_stream boundary tests skip when the extension is absent, so executed end-to-end coverage is missing. Build the native extension in CI and run tests through rust_consume_stream; retain the pipe/property tests and add a consume I/O-error assertion.

@coderabbitai

coderabbitai Bot commented Aug 2, 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

for number in 258 265; do
  echo "=== Issue #${number} ==="
  gh issue view "$number" \
    --repo leynos/cuprum \
    --json number,title,state,url,body,assignees
done

Length of output: 7828


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="leynos/cuprum"
pr_url="https://github.com/leynos/cuprum/pull/241"

comment_url="$(
  gh api "repos/${repo}/issues/241/comments" --paginate \
    --jq '.[] | select(.user.login == "leynos" and (.body | contains("please create a new GitHub issue for the following"))) | .html_url' \
    | tail -n1
)"

if [ -z "$comment_url" ]; then
  echo "Could not locate the requesting comment URL." >&2
  exit 1
fi

body=$(cat <<EOF
## Summary

Add an executed Python/Rust boundary regression test for I/O errors from \`rust_consume_stream\`.

The tests in \`rust/cuprum-rust/src/consume_snapshot_tests.rs\` correctly exercise the private \`consume_stream_files\` read-and-decode loop. They do not exercise error conversion through the public Python/Rust boundary.

## Required work

1. After the CI extension build from `#258` is available, add a focused test that calls \`rust_consume_stream\` with an invalid or closed file descriptor.
2. Assert that the Python-facing call raises the expected \`OSError\`.
3. Assert a usable OS error number. Accept the platform-specific values already used by existing tests: \`errno.EBADF\` or \`errno.EINVAL\`.
4. Retain the existing pipe, snapshot, and property tests for \`consume_stream_files\`. Do not replace them with the boundary test.
5. Run the new test in CI with the native extension enabled.

## Rationale

The current Rust tests validate decoding behaviour below the PyO3 boundary. An executed \`rust_consume_stream\` I/O-error assertion validates descriptor failure handling and error translation at the public Python/Rust boundary.

`#258` must first build the native extension in test CI and fail loudly when it is unavailable. `#265` must preserve \`OSError.errno\` across PyO3 before this assertion can pass consistently.

## Affected areas

- \`cuprum/unittests/test_rust_streams.py\`
- \`rust/cuprum-rust/src/lib.rs\` and the \`rust_consume_stream\` error-conversion path, if `#265` requires a fix
- \`.github/workflows/ci.yml\` and test setup from `#258`

## Acceptance criteria

- CI builds and imports \`cuprum._rust_backend_native\` before it runs the extension-gated test suite.
- A test calls \`rust_consume_stream\` with an I/O failure and observes \`OSError\`.
- The test verifies \`.errno\` is \`errno.EBADF\` or \`errno.EINVAL\`.
- The extension-gated test suite executes in CI instead of skipping.
- The existing \`consume_stream_files\` snapshots and properties remain.

## Related work

- `#258` — build the native extension in test CI and make a missing extension fail loudly.
- `#265` — preserve \`OSError.errno\` across the PyO3 boundary.

## Backlinks

- Pull request: ${pr_url}
- Requesting comment: ${comment_url}
- Requested by: `@leynos`
EOF
)

issue_url=$(gh issue create \
  --repo "$repo" \
  --title "Add executed rust_consume_stream I/O-error boundary coverage" \
  --body "$body" \
  --assignee "leynos")

echo "$issue_url"

Length of output: 3100


@leynos Created and assigned #276 — Add executed rust_consume_stream I/O-error boundary coverage.

The issue links to PR #241 and the requesting comment. It keeps #258 for CI extension builds and #265 for OSError.errno preservation. It requires a public rust_consume_stream I/O-error assertion after those prerequisites complete.

You are interacting with an AI system.

@leynos
leynos merged commit ec1ae64 into main Aug 2, 2026
20 checks passed
@leynos
leynos deleted the rust-consume-stream-snapshots branch August 2, 2026 11:14
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: snapshot and integration tests for consume_stream UTF-8 replacement output

3 participants