Export testkit utilities behind wireframe::testkit feature (Stage A)#507
Export testkit utilities behind wireframe::testkit feature (Stage A)#507
Conversation
Add a comprehensive execution plan document covering roadmap item 8.5.4 that outlines the design, constraints, risks, progress, and plan for exporting testkit utilities as `wireframe::testkit` behind a feature flag. This living document includes architectural decisions, compatibility requirements, staged implementation steps, testing strategy, documentation updates, and validation criteria. Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
Reviewer's GuideAdds a new feature-gated Sequence diagram for drive_with_slow_codec_payloads using wireframe::testkitsequenceDiagram
participant T as TestCode
participant TK as testkit::slow_io
participant SUP as testkit::support
participant APP as WireframeApp
participant TOK as tokio::duplex
participant COD as FrameCodec
T->>TK: drive_with_slow_codec_payloads(app, codec, payloads, config)
TK->>TK: drive_with_slow_codec_frames(app, codec, payloads, config)
TK->>SUP: encode_payloads_with_codec(codec, payloads)
SUP->>COD: encoder(), wrap_payload(), encode(...)
COD-->>SUP: encoded frames
SUP-->>TK: Vec<Vec<u8>> encoded
TK->>TK: flatten frames to wire_bytes
TK->>TK: drive_slow_internal(|server| run_owned_app(app, server), wire_bytes, config)
TK->>TOK: duplex(config.capacity)
TOK-->>TK: client, server streams
par server_task
TK->>SUP: run_owned_app(app, server)
SUP->>APP: handle_connection(server)
APP-->>SUP: completes
and writer_task
TK->>TOK: write_with_optional_pacing(client writer, wire_bytes, config.writer_pacing)
loop chunks
TOK-->>APP: chunk bytes
TK->>TK: pause_between_chunks(config.writer_pacing)
end
TK->>TOK: shutdown writer
and reader_task
TK->>TOK: read_with_optional_pacing(client reader, config.reader_pacing)
loop chunks
TOK-->>TK: chunk bytes
TK->>TK: pause_between_chunks(config.reader_pacing)
end
TK-->>TK: raw_bytes
end
TK-->>TK: decode_frames_with_codec(codec, raw_bytes)
TK->>SUP: decode_frames_with_codec(codec, raw_bytes)
SUP->>COD: decoder(), decode()/decode_eof()
COD-->>SUP: Vec<F::Frame>
SUP-->>TK: Vec<F::Frame>
TK-->>T: io::Result<Vec<Vec<u8>>> (extract_payloads)
Class diagram for wireframe::testkit module structure and key typesclassDiagram
class testkit {
}
class fragment_drive {
}
class partial_frame {
}
class slow_io {
}
class reassembly {
}
class result {
}
class support {
}
testkit --> fragment_drive
testkit --> partial_frame
testkit --> slow_io
testkit --> reassembly
testkit --> result
testkit --> support
class SlowIoPacing {
+NonZeroUsize chunk_size
+Duration delay
+new(chunk_size: NonZeroUsize, delay: Duration) SlowIoPacing
}
class SlowIoConfig {
+Option~SlowIoPacing~ writer_pacing
+Option~SlowIoPacing~ reader_pacing
+usize capacity
+new() SlowIoConfig
+with_writer_pacing(pacing: SlowIoPacing) SlowIoConfig
+with_reader_pacing(pacing: SlowIoPacing) SlowIoConfig
+with_capacity(capacity: usize) SlowIoConfig
-validate(self) io::Result~SlowIoConfig~
}
class slow_io {
+MAX_SLOW_IO_CAPACITY: usize
+drive_with_slow_frames(app: WireframeApp, frames: Vec~Vec~u8~~, config: SlowIoConfig) async io::Result~Vec~u8~~
+drive_with_slow_payloads(app: WireframeApp, payloads: Vec~Vec~u8~~, config: SlowIoConfig) async io::Result~Vec~u8~~
+drive_with_slow_codec_frames(app: WireframeApp, codec: FrameCodec, payloads: Vec~Vec~u8~~, config: SlowIoConfig) async io::Result~Vec~F::Frame~~
+drive_with_slow_codec_payloads(app: WireframeApp, codec: FrameCodec, payloads: Vec~Vec~u8~~, config: SlowIoConfig) async io::Result~Vec~Vec~u8~~~
}
slow_io --> SlowIoPacing
slow_io --> SlowIoConfig
class partial_frame {
+drive_with_partial_frames(app: WireframeApp, codec: FrameCodec, payloads: Vec~Vec~u8~~, chunk_size: NonZeroUsize) async io::Result~Vec~Vec~u8~~~
+drive_with_partial_frames_with_capacity(app: WireframeApp, codec: FrameCodec, payloads: Vec~Vec~u8~~, chunk_size: NonZeroUsize, capacity: usize) async io::Result~Vec~Vec~u8~~~
+drive_with_partial_frames_mut(app: &mut WireframeApp, codec: FrameCodec, payloads: Vec~Vec~u8~~, chunk_size: NonZeroUsize) async io::Result~Vec~Vec~u8~~~
+drive_with_partial_codec_frames(app: WireframeApp, codec: FrameCodec, payloads: Vec~Vec~u8~~, chunk_size: NonZeroUsize) async io::Result~Vec~F::Frame~~
}
class fragment_drive {
+drive_with_fragments(app: WireframeApp, codec: FrameCodec, fragmenter: Fragmenter, payload: Vec~u8~) async io::Result~Vec~Vec~u8~~~
+drive_with_fragments_with_capacity(app: WireframeApp, codec: FrameCodec, fragmenter: Fragmenter, payload: Vec~u8~, capacity: usize) async io::Result~Vec~Vec~u8~~~
+drive_with_fragments_mut(app: &mut WireframeApp, codec: FrameCodec, fragmenter: Fragmenter, payload: Vec~u8~) async io::Result~Vec~Vec~u8~~~
+drive_with_fragment_frames(app: WireframeApp, codec: FrameCodec, fragmenter: Fragmenter, payload: Vec~u8~) async io::Result~Vec~F::Frame~~
+drive_with_partial_fragments(app: WireframeApp, codec: FrameCodec, fragmenter: Fragmenter, payload: Vec~u8~, chunk_size: NonZeroUsize) async io::Result~Vec~Vec~u8~~~
}
fragment_drive --> Fragmenter
class TestError {
<<enum>>
+Io(std::io::Error)
+Msg(String)
+Wireframe(WireframeError)
+Client(crate::client::ClientError) *cfg(not(loom))*
+Server(crate::server::ServerError) *cfg(not(loom))*
+Push(PushError)
+PushConfig(PushConfigError)
+ConnectionState(ConnectionStateError)
+Reassembly(ReassemblyError)
+Fragmentation(FragmentationError)
+Codec(CodecError)
+Encode(bincode::error::EncodeError)
+Decode(bincode::error::DecodeError)
+Join(tokio::task::JoinError)
+Timeout(tokio::time::error::Elapsed)
+OneshotRecv(tokio::sync::oneshot::error::RecvError)
+OneshotTryRecv(tokio::sync::oneshot::error::TryRecvError)
+MpscTryRecv(tokio::sync::mpsc::error::TryRecvError)
+ParseInt(std::num::ParseIntError)
+TryFromInt(std::num::TryFromIntError)
+Utf8(std::str::Utf8Error)
+FromUtf8(std::string::FromUtf8Error)
+AddrParse(std::net::AddrParseError)
+from(value: String) TestError
+from(value: &str) TestError
}
class TestResult {
<<type alias>>
+Result~T, TestError~
}
result --> TestError
result --> TestResult
class FragmentReassemblySnapshot {
+Option~&ReassembledMessage~ last_reassembled
+Option~&ReassemblyError~ last_error
+&[MessageId] evicted_ids
+usize buffered_messages
+new(last_reassembled: Option~&ReassembledMessage~, last_error: Option~&ReassemblyError~, evicted_ids: &[MessageId], buffered_messages: usize) FragmentReassemblySnapshot
}
class FragmentReassemblyErrorExpectation {
<<enum>>
+MessageTooLarge(message_id: MessageId)
+MessageTooLargeAny
+IndexMismatch
+MessageMismatch
+SeriesComplete
+IndexOverflow
}
class MessageAssemblySnapshot {
+Option~&Result~Option~AssembledMessage~, MessageAssemblyError~~ last_result
+&[AssembledMessage] completed_messages
+&[MessageKey] evicted_keys
+usize buffered_count
+usize total_buffered_bytes
+new(last_result: Option~&Result~Option~AssembledMessage~, MessageAssemblyError~~, completed_messages: &[AssembledMessage], evicted_keys: &[MessageKey], buffered_count: usize, total_buffered_bytes: usize) MessageAssemblySnapshot
+last_completed() Option~&AssembledMessage~
+completed_for_key(key: MessageKey) Option~&AssembledMessage~
}
class MessageAssemblyErrorExpectation {
<<enum>>
+SequenceMismatch(expected: FrameSequence, found: FrameSequence)
+DuplicateFrame(key: MessageKey, sequence: FrameSequence)
+MissingFirstFrame(key: MessageKey)
+DuplicateFirstFrame(key: MessageKey)
+MessageTooLarge(key: MessageKey)
+ConnectionBudgetExceeded(key: MessageKey)
+InFlightBudgetExceeded(key: MessageKey)
}
class reassembly_assertions {
+assert_fragment_reassembly_absent(snapshot: FragmentReassemblySnapshot) TestResult
+assert_fragment_reassembly_completed_len(snapshot: FragmentReassemblySnapshot, expected_len: usize) TestResult
+assert_fragment_reassembly_completed_bytes(snapshot: FragmentReassemblySnapshot, expected: &[u8]) TestResult
+assert_fragment_reassembly_error(snapshot: FragmentReassemblySnapshot, expected: FragmentReassemblyErrorExpectation) TestResult
+assert_fragment_reassembly_buffered_messages(snapshot: FragmentReassemblySnapshot, expected: usize) TestResult
+assert_fragment_reassembly_evicted(snapshot: FragmentReassemblySnapshot, message_id: MessageId) TestResult
+assert_message_assembly_incomplete(snapshot: MessageAssemblySnapshot) TestResult
+assert_message_assembly_completed(snapshot: MessageAssemblySnapshot, expected: &[u8]) TestResult
+assert_message_assembly_completed_for_key(snapshot: MessageAssemblySnapshot, key: MessageKey, expected: &[u8]) TestResult
+assert_message_assembly_error(snapshot: MessageAssemblySnapshot, expected: MessageAssemblyErrorExpectation) TestResult
+assert_message_assembly_buffered_count(snapshot: MessageAssemblySnapshot, expected: usize) TestResult
+assert_message_assembly_total_buffered_bytes(snapshot: MessageAssemblySnapshot, expected: usize) TestResult
+assert_message_assembly_evicted(snapshot: MessageAssemblySnapshot, key: MessageKey) TestResult
}
reassembly --> FragmentReassemblySnapshot
reassembly --> FragmentReassemblyErrorExpectation
reassembly --> MessageAssemblySnapshot
reassembly --> MessageAssemblyErrorExpectation
reassembly --> reassembly_assertions
reassembly_assertions --> TestResult
class support {
<<internal helpers>>
+DEFAULT_CAPACITY: usize
+encode_payloads_with_codec(codec: FrameCodec, payloads: Vec~Vec~u8~~) io::Result~Vec~Vec~u8~~~
+decode_frames_with_codec(codec: FrameCodec, bytes: &[u8]) io::Result~Vec~F::Frame~~
+extract_payloads(frames: &[F::Frame]) Vec~Vec~u8~~
+drive_internal(server_fn: FnOnce(DuplexStream), frames: Vec~Vec~u8~~, capacity: usize) async io::Result~Vec~u8~~
+drive_chunked_internal(server_fn: FnOnce(DuplexStream), wire_bytes: Vec~u8~, chunk_size: NonZeroUsize, capacity: usize) async io::Result~Vec~u8~~
+run_owned_app(app: WireframeApp, server: DuplexStream) async
}
support ..> WireframeApp
support ..> FrameCodec
testkit ..> TestResult
testkit ..> TestError
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
WalkthroughIntroduce an opt-in Cargo feature Changes
Sequence Diagram(s)sequenceDiagram
participant Test as Test harness
participant Codec as FrameCodec
participant Fragmenter as Fragmenter
participant Client as Client writer
participant Duplex as DuplexStream
participant Server as WireframeApp
participant Decoder as Frame decoder
Test->>Codec: Encode payloads -> frames
alt fragmentation
Codec->>Fragmenter: Produce fragments
Fragmenter->>Client: Emit fragment bytes
else no fragmentation
Codec->>Client: Emit framed bytes
end
Client->>Duplex: Write bytes (possibly chunked/paced)
Duplex->>Server: Deliver bytes to server handler
Server->>Duplex: Write response bytes
Duplex->>Decoder: Collect response bytes
Decoder->>Codec: Decode frames
Decoder->>Test: Extract payloads / snapshots
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
- Introduce `wireframe::testkit` module behind a dedicated `testkit` Cargo feature. - Move partial frame and fragment driving helpers, slow I/O helpers, and reassembly assertion helpers under this feature. - Provide compatibility re-exports in `wireframe_testing` crate forwarding to `wireframe::testkit`. - Update docs and roadmap to reflect the new export strategy. - Add integration and BDD tests to verify `wireframe::testkit` usage. - Enable `testkit` feature in main crate without creating Cargo dependency cycles. This change modularizes test utilities, enabling opt-in usage while preserving compatibility and preventing dependency cycles. Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. src/testkit/reassembly/message.rs Comment on file pub fn assert_message_assembly_buffered_count(
snapshot: MessageAssemblySnapshot<'_>,
expected: usize,
) -> TestResult {
assert_usize_field(snapshot.buffered_count, expected, "buffered_count")
}❌ New issue: Code Duplication |
This comment was marked as resolved.
This comment was marked as resolved.
… assertions Replaced direct calls to `assert_usize_field` with a new macro `assert_snapshot_usize_field!` to reduce code duplication and improve readability in message assembly snapshot tests. Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/execplans/8-5-4-export-wireframe-testkit-behind-feature.md`:
- Around line 379-383: Update the documentation to use the singular
`testkit_export` names consistently: replace references to
`tests/features/testkit_exports.feature`, `tests/fixtures/testkit_exports.rs`,
`tests/steps/testkit_exports_steps.rs`, and
`tests/scenarios/testkit_exports_scenarios.rs` with
`tests/features/testkit_export.feature`, `tests/fixtures/testkit_export.rs`,
`tests/steps/testkit_export_steps.rs`, and
`tests/scenarios/testkit_export_scenarios.rs`, and update the test run example
filter from `-- testkit_exports` to `-- testkit_export` (also apply the same
changes at the locations mentioned around lines 430-433).
- Around line 193-194: Remove the stale opener "Not started yet." from the
section that documents the exported wireframe testkit feature (the line
containing the exact text "Not started yet." in
docs/execplans/8-5-4-export-wireframe-testkit-behind-feature.md) so the document
matches its Status: IMPLEMENTED and retains the recorded API, compatibility,
behavioural proof, and validation content without contradicting language.
In `@src/testkit/result.rs`:
- Around line 13-15: Add the #[non_exhaustive] attribute to the public enum
TestError so future variants can be added without breaking downstream exhaustive
matches; locate the TestError enum declaration (the pub enum TestError in this
file) and place #[non_exhaustive] directly above the #[derive(...)]/pub enum
line, then run cargo check to ensure no warnings from exhaustive matches in this
crate need addressing.
In `@src/testkit/support.rs`:
- Around line 18-38: The TestSerializer trait is currently defined in the
private support module and used in public APIs, triggering the private_bounds
lint; fix this by adding a public re-export of TestSerializer from the testkit
root module so the trait becomes part of the public API surface (i.e., in the
testkit module export support::TestSerializer as pub so S: TestSerializer in
slow_io, fragment_drive, and partial_frame is nameable publicly). Ensure the
exported symbol is exactly TestSerializer to match existing generic bounds.
In `@tests/fixtures/testkit_export.rs`:
- Around line 83-101: The test helper seed_completed_message_snapshot currently
takes a key but assert_root_reassembly_helper asserts MessageKey(7) explicitly,
causing a mismatch; fix by persisting the seeded key and using it in the
assertion: add a field (e.g., seeded_key: Option<u64>) to the fixture, set it
inside seed_completed_message_snapshot(key) when creating AssembledMessage, and
then change assert_root_reassembly_helper to build the expected key from that
stored value (use MessageKey(self.seeded_key.unwrap()) instead of
MessageKey(7)); alternatively remove the key parameter from
seed_completed_message_snapshot and always seed a fixed key then keep the
assertion as-is, but ensure seed_completed_message_snapshot and
assert_root_reassembly_helper reference the same identifier (seeded_key,
seed_completed_message_snapshot, assert_root_reassembly_helper, MessageKey).
In `@tests/scenarios/testkit_export_scenarios.rs`:
- Around line 11-25: Remove the #[expect(unused_variables, ...)] attributes from
the two test functions and underscore the unused fixture parameter instead:
rename the parameter testkit_export_world to _testkit_export_world in
root_testkit_partial_frame_export and root_testkit_reassembly_export so the
tests remain valid stubs without silencing lints.
In `@tests/testkit_exports.rs`:
- Around line 92-97: The test currently sends a vector of zeroes and ignores the
response, so it never verifies that drive_with_fragments actually hits the echo
route (route 1) or returns the echoed payload; replace the raw vec![0; 64] with
a valid envelope payload (e.g. a non-empty byte sequence or serialized envelope
expected by build_echo_app/route 1), await and capture the result of
drive_with_fragments, and assert that the reply matches the original payload
(use Fragmenter::new(...) and drive_with_fragments(app, &codec, &fragmenter,
payload). Ensure you assert success and equality of the echoed payload rather
than dropping the result.
- Around line 116-128: The fixed
tokio::time::advance(Duration::from_millis(30)).await is brittle; replace it
with an incremental advance loop that keeps advancing small steps until the
spawned task (the future returned by tokio::spawn that runs
drive_with_slow_codec_payloads) actually finishes or a reasonable timeout is
hit. Concretely, poll task.is_finished() in a loop, calling
tokio::time::advance(small_step).await (e.g., 20ms) repeatedly and breaking when
task.is_finished() (or after a max total duration computed from payload size if
available), then await the task as before to get response_payloads; this ensures
larger encoded frames/pacing delays finish reliably.
🪄 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
Run ID: 4a48512f-7e87-4ca8-8750-5e07da926b4d
📒 Files selected for processing (29)
Cargo.tomldocs/adr-002-streaming-requests-and-shared-message-assembly.mddocs/execplans/8-5-4-export-wireframe-testkit-behind-feature.mddocs/roadmap.mddocs/users-guide.mdsrc/lib.rssrc/testkit/fragment_drive.rssrc/testkit/mod.rssrc/testkit/partial_frame.rssrc/testkit/reassembly/assert_helpers.rssrc/testkit/reassembly/fragment.rssrc/testkit/reassembly/message.rssrc/testkit/reassembly/message_error.rssrc/testkit/reassembly/mod.rssrc/testkit/result.rssrc/testkit/slow_io.rssrc/testkit/support.rstests/features/testkit_export.featuretests/fixtures/mod.rstests/fixtures/testkit_export.rstests/scenarios/mod.rstests/scenarios/testkit_export_scenarios.rstests/steps/mod.rstests/steps/testkit_export_steps.rstests/testkit_exports.rswireframe_testing/Cargo.tomlwireframe_testing/src/helpers.rswireframe_testing/src/integration_helpers.rswireframe_testing/src/reassembly/mod.rs
… comments Extract `panic_guarded_server`, `drive_with_strategies`, and `drive_codec_roundtrip` into `support.rs` so that all three driver variants (batch, chunked, and slow-I/O) share a single duplex creation, panic-handling, and `try_join!` orchestration path. - Refactor `drive_internal` and `drive_chunked_internal` to delegate to `drive_with_strategies` with appropriate write strategies. - Refactor `drive_slow_internal` to use `drive_with_strategies` with pacing-aware strategy closures, eliminating its own duplex/panic machinery. - Remove `ChunkConfig` and `drive_partial_frames_internal` from `partial_frame.rs`; public functions now call `drive_codec_roundtrip` directly. - Remove `FragmentRequest` and `drive_fragments_internal` from `fragment_drive.rs`; public functions preprocess payloads then call `drive_codec_roundtrip`. - Make `SlowIoConfig::validate` public and add four failure-path tests exercising zero capacity, over-max capacity, and chunk sizes exceeding capacity for both writer and reader directions. - Fix stale "Not started yet" text in the execplan Outcomes section. - Fix BDD test filter from `testkit_exports` to `testkit_export` in the execplan validation commands. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/testkit_exports.rs`:
- Around line 181-259: The four tests
slow_io_config_validate_rejects_zero_capacity,
slow_io_config_validate_rejects_capacity_over_max,
slow_io_config_validate_rejects_writer_chunk_exceeding_capacity, and
slow_io_config_validate_rejects_reader_chunk_exceeding_capacity duplicate the
same assertion logic; extract a private helper fn
assert_validate_fails_with_invalid_input(config: SlowIoConfig, context: &str) ->
io::Result<()> that calls config.validate(), expects an error, and checks
err.kind() == io::ErrorKind::InvalidInput (using that context in the error
messages), then replace each test body with a one-line call to that helper
passing the constructed SlowIoConfig and a context string; this will centralize
the assertion around SlowIoConfig::validate and ErrorKind::InvalidInput.
🪄 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
Run ID: 27ae899d-690c-47a0-9881-74cf160f18bb
📒 Files selected for processing (6)
docs/execplans/8-5-4-export-wireframe-testkit-behind-feature.mdsrc/testkit/fragment_drive.rssrc/testkit/partial_frame.rssrc/testkit/slow_io.rssrc/testkit/support.rstests/testkit_exports.rs
|
@coderabbitai Have the following now been resolved? Annul any requirements that violate the en-GB-oxendict spelling (-ize / -yse / -our) conventions (for example a request to replace "normalize" with "normalise" or "artefact" with "artifact"), or where the requirement unnecessarily increases cyclomatic complexity. |
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Comment on file pub(crate) async fn drive_internal<F, Fut>(
server_fn: F,
frames: Vec<Vec<u8>>,
capacity: usize,
) -> io::Result<Vec<u8>>
where
F: FnOnce(DuplexStream) -> Fut,
Fut: std::future::Future<Output = ()> + Send,
{
drive_with_strategies(
server_fn,
capacity,
|mut writer| async {
write_frames(&mut writer, &frames).await?;
Ok(writer)
},
read_all,
)
.await
}❌ New issue: Code Duplication |
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Comment on file fn slow_io_config_validate_rejects_reader_chunk_exceeding_capacity() -> io::Result<()> {
let capacity = 4;
let chunk_size =
NonZeroUsize::new(capacity * 2).ok_or_else(|| io::Error::other("non-zero chunk size"))?;
let config = SlowIoConfig::new()
.with_capacity(capacity)
.with_reader_pacing(SlowIoPacing::new(chunk_size, Duration::from_millis(1)));
let err = config
.validate()
.expect_err("expected validate to fail when reader chunk exceeds capacity");
if err.kind() != io::ErrorKind::InvalidInput {
return Err(io::Error::other(format!(
"expected InvalidInput, got {:?}",
err.kind()
)));
}
Ok(())
}❌ New issue: Code Duplication |
This comment was marked as resolved.
This comment was marked as resolved.
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 2473 🏁 Script executed: Length of output: 3839
Overall Comments
Individual Comments
The one remaining item is the stale plural filenames in the Stage E plan text of the execplan document. Update those four lines to the singular names to keep the plan consistent with what was actually delivered. |
This comment was marked as resolved.
This comment was marked as resolved.
- Correct fixture seed method to use constant key and enforce key check in step defs - Rename tests files for consistency in naming - Add checks for non-empty responses in root crate testkit export tests - Implement timeout loop for paced slow_io helper test instead of fixed time advance - Add #[non_exhaustive] attribute to TestError enum in testkit - Re-export TestSerializer from testkit support module - Remove unnecessary #[doc(hidden)] from trait - Replace deprecated expect attributes with allows on test scenarios These improvements increase test robustness, align naming, and fix tests that could silently pass or fail. Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
There was a problem hiding this comment.
♻️ Duplicate comments (3)
tests/scenarios/testkit_export_scenarios.rs (1)
11-13: 🧹 Nitpick | 🔵 TrivialRemove no-op bindings in scenario stubs.
Rename the fixture parameter to
_testkit_export_worldand delete the no-op statements on Line 12 and Line 20.♻️ Proposed cleanup
fn root_testkit_partial_frame_export(testkit_export_world: TestkitExportWorld) { - let _ = &testkit_export_world; +fn root_testkit_partial_frame_export(_testkit_export_world: TestkitExportWorld) { } @@ -fn root_testkit_reassembly_export(testkit_export_world: TestkitExportWorld) { - let _ = &testkit_export_world; +fn root_testkit_reassembly_export(_testkit_export_world: TestkitExportWorld) { }Also applies to: 19-21
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/scenarios/testkit_export_scenarios.rs` around lines 11 - 13, Rename the fixture parameter in the scenario stub function root_testkit_partial_frame_export from testkit_export_world to _testkit_export_world and remove the unused no-op binding statements (the "let _ = &testkit_export_world;" and the other identical no-op around lines 19-21) so the parameter is clearly marked as intentionally unused and the redundant statements are deleted; update the function signature only (root_testkit_partial_frame_export) and delete the two no-op lines referenced.tests/testkit_exports.rs (2)
97-109:⚠️ Potential issue | 🟡 MinorAssert fragmented round-trip payload equality.
Decode and compare the echoed payload to
b"fragment-test"in this branch. Keep non-empty checks as guards, but stop treating them as sufficient correctness proof.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/testkit_exports.rs` around lines 97 - 109, The test currently only checks for non-empty frames but must assert the round-trip payload equals b"fragment-test"; after obtaining first_payload (from drive_with_fragments/app/codec/fragmenter/serialize_envelope), decode or deserialize the envelope/frame (e.g., via the test helper used elsewhere such as deserialize_envelope or the codec's decode method) and assert its inner payload equals b"fragment-test" (keeping the existing non-empty guards). Ensure you reference serialize_envelope, drive_with_fragments, codec, fragmenter, and first_payload when locating where to insert the decode + equality assertion.
243-269: 🧹 Nitpick | 🔵 TrivialReplace these twin validation tests with one parameterized case.
Collapse writer/reader chunk-exceeding cases into a single parameterized test path with a pacing-applier input to remove the remaining duplication.
As per coding guidelines, "Replace duplicated tests with
#[rstest(...)]parameterised cases."Run this read-only check to confirm the duplicated structure before refactoring:
#!/bin/bash rg -n -C 4 'slow_io_config_validate_rejects_(writer|reader)_chunk_exceeding_capacity|with_(writer|reader)_pacing' tests/testkit_exports.rs🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/testkit_exports.rs` around lines 243 - 269, The two nearly identical tests slow_io_config_validate_rejects_writer_chunk_exceeding_capacity and slow_io_config_validate_rejects_reader_chunk_exceeding_capacity should be collapsed into a single parameterized rstest that takes a pacing-applier function (e.g., a FnOnce(SlowIoConfig, NonZeroUsize) -> SlowIoConfig) and runs the same validation logic; create a NonZeroUsize chunk_size from capacity*2, build a base SlowIoConfig::new().with_capacity(capacity), apply the pacing-applier to set either with_writer_pacing or with_reader_pacing using SlowIoPacing::new(chunk_size, Duration::from_millis(1)), then call assert_slow_io_config_rejects(config, "..."); annotate with #[rstest(...)] to pass both appliers (one that calls with_writer_pacing, one that calls with_reader_pacing) to the single test function.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@tests/scenarios/testkit_export_scenarios.rs`:
- Around line 11-13: Rename the fixture parameter in the scenario stub function
root_testkit_partial_frame_export from testkit_export_world to
_testkit_export_world and remove the unused no-op binding statements (the "let _
= &testkit_export_world;" and the other identical no-op around lines 19-21) so
the parameter is clearly marked as intentionally unused and the redundant
statements are deleted; update the function signature only
(root_testkit_partial_frame_export) and delete the two no-op lines referenced.
In `@tests/testkit_exports.rs`:
- Around line 97-109: The test currently only checks for non-empty frames but
must assert the round-trip payload equals b"fragment-test"; after obtaining
first_payload (from
drive_with_fragments/app/codec/fragmenter/serialize_envelope), decode or
deserialize the envelope/frame (e.g., via the test helper used elsewhere such as
deserialize_envelope or the codec's decode method) and assert its inner payload
equals b"fragment-test" (keeping the existing non-empty guards). Ensure you
reference serialize_envelope, drive_with_fragments, codec, fragmenter, and
first_payload when locating where to insert the decode + equality assertion.
- Around line 243-269: The two nearly identical tests
slow_io_config_validate_rejects_writer_chunk_exceeding_capacity and
slow_io_config_validate_rejects_reader_chunk_exceeding_capacity should be
collapsed into a single parameterized rstest that takes a pacing-applier
function (e.g., a FnOnce(SlowIoConfig, NonZeroUsize) -> SlowIoConfig) and runs
the same validation logic; create a NonZeroUsize chunk_size from capacity*2,
build a base SlowIoConfig::new().with_capacity(capacity), apply the
pacing-applier to set either with_writer_pacing or with_reader_pacing using
SlowIoPacing::new(chunk_size, Duration::from_millis(1)), then call
assert_slow_io_config_rejects(config, "..."); annotate with #[rstest(...)] to
pass both appliers (one that calls with_writer_pacing, one that calls
with_reader_pacing) to the single test function.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4a7fd138-459c-4693-8906-e9b3a2a6b6ee
📒 Files selected for processing (2)
tests/scenarios/testkit_export_scenarios.rstests/testkit_exports.rs
…tests Extract a helper function to unify the validation logic for writer and reader chunk sizes exceeding capacity in slow_io config tests, reducing code duplication and simplifying test maintenance. Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Code Duplicationsrc/testkit/support.rs: What lead to degradation?The module contains 2 functions with similar structure: drive_chunked_internal,drive_internal Why does this problem occur?Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health. How to fix it?A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More |
|
Tip For best results, initiate chat on the files or code changes.
To summarise what was proposed there: File: Extract two file-private async strategy adapters — The full diff and the verbatim AI coding agent prompt are in that earlier reply — please refer to it directly to avoid duplication here. |
Reformatted the function signature of `assert_slow_io_rejects_chunk_exceeding_capacity` in `testkit_exports.rs` for better code style and readability by moving parameters to a single line. Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
tests/testkit_exports.rs (1)
97-109:⚠️ Potential issue | 🟡 MinorAssert fragmented round-trip payload equality.
Replace the non-empty-only checks on Lines 99-109 with a decode-and-compare assertion against
b"fragment-test"so this test proves route correctness instead of mere output presence.🛠️ Suggested patch
let payload = serialize_envelope(b"fragment-test")?; let response_payloads = drive_with_fragments(app, &codec, &fragmenter, payload).await?; - if response_payloads.is_empty() { - return Err(io::Error::other("expected non-empty response payloads")); - } let first_payload = response_payloads .first() .ok_or_else(|| io::Error::other("expected response payload"))?; - // Verify response contains data - drive_with_fragments returns wire frames which may include - // fragment headers for fragmented payloads - if first_payload.is_empty() { - return Err(io::Error::other("expected non-empty response frame")); + let echoed_payload = deserialize_payload(first_payload)?; + if echoed_payload != b"fragment-test" { + return Err(io::Error::other(format!( + "unexpected fragmented echoed payload: {echoed_payload:?}" + ))); } Ok(()) }As per coding guidelines, "Write unit and behavioural tests for new Rust functionality."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/testkit_exports.rs` around lines 97 - 109, The test currently only checks for non-empty response frames; instead decode the returned frame(s) and assert the payload equals b"fragment-test". Use the same codec/serializer used to send the request (symbols: serialize_envelope, drive_with_fragments, codec, fragmenter, response_payloads, first_payload) to decode/reconstruct the envelope from first_payload (or reassemble fragments if necessary) and compare the decoded bytes to b"fragment-test" with an assertion rather than returning on emptiness.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/testkit_exports.rs`:
- Around line 243-245: The function signature for
assert_slow_io_rejects_chunk_exceeding_capacity is not formatted per rustfmt;
run the repository formatter (make fmt) to apply the canonical formatting to
tests/testkit_exports.rs (so the signature and surrounding lines match rustfmt
rules), then verify with make check-fmt and commit the updated file; ensure the
change only adjusts whitespace/formatting around
assert_slow_io_rejects_chunk_exceeding_capacity and does not modify logic.
---
Duplicate comments:
In `@tests/testkit_exports.rs`:
- Around line 97-109: The test currently only checks for non-empty response
frames; instead decode the returned frame(s) and assert the payload equals
b"fragment-test". Use the same codec/serializer used to send the request
(symbols: serialize_envelope, drive_with_fragments, codec, fragmenter,
response_payloads, first_payload) to decode/reconstruct the envelope from
first_payload (or reassemble fragments if necessary) and compare the decoded
bytes to b"fragment-test" with an assertion rather than returning on emptiness.
🪄 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
Run ID: 62f9a5d9-312e-47d8-8be6-b4b63a8e4b3d
📒 Files selected for processing (1)
tests/testkit_exports.rs
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Code Duplicationsrc/testkit/support.rs: What lead to degradation?The module contains 2 functions with similar structure: drive_chunked_internal,drive_internal Why does this problem occur?Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health. How to fix it?A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 8688
(ᵔ◡ᵔ)∫ |
|
@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph. If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced. Code Duplicationsrc/testkit/support.rs: What lead to degradation?The module contains 2 functions with similar structure: drive_chunked_internal,drive_internal Why does this problem occur?Duplicated code often leads to code that's harder to change since the same logical change has to be done in multiple functions. More duplication gives lower code health. How to fix it?A certain degree of duplicated code might be acceptable. The problems start when it is the same behavior that is duplicated across the functions in the module, ie. a violation of the Don't Repeat Yourself (DRY) principle. DRY violations lead to code that is changed together in predictable patterns, which is both expensive and risky. DRY violations can be identified using CodeScene's X-Ray analysis to detect clusters of change coupled functions with high code similarity. Read More |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 8688
File: The fix is already fully applied. Both functions delegate to
Further unification would be deleterious. Merging the two into a single function would require either removing them and pushing strategy-closure construction into every call site (scattering complexity), or introducing a runtime discriminator (enum/ CodeScene suppression message
|
…ented data Refactored existing test in tests/testkit_exports.rs to remove redundant emptiness checks and instead verify that the echoed payload after fragment reassembly exactly matches the expected data. This improves test precision and clarity by ensuring correctness of fragmented payload handling. Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/testkit_exports.rs`:
- Line 247: The error message for NonZeroUsize::new(capacity *
2).ok_or_else(...) is a stale literal ("8 is non-zero"); replace it with a
value-agnostic message that reflects the actual check, e.g. change the string to
something like "capacity * 2 is zero" or "zero capacity" so the failure text
remains correct if capacity changes, leaving the call to
NonZeroUsize::new(capacity * 2) and the surrounding logic unchanged.
🪄 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
Run ID: 010bcfa3-5e11-4e54-be3e-ad04bbf606d5
📒 Files selected for processing (1)
tests/testkit_exports.rs
The error message passed to io::Error::other was incorrect and misleading. Updated from a static "8 is non-zero" to reflect the actual expression "capacity * 2 is zero" to enhance clarity in test diagnostics. Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
…nt test Refactored the check on response payloads in the fragment test to only verify non-emptiness instead of deserializing and comparing payload contents. Removed unnecessary detailed payload validation to streamline the test. Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
|
@coderabbitai should we make WireframeError an implementation of std::error::Error? This feels like a gap in the library architecture. 2. ❌ Cannot fix:
|
|
@coderabbitai is this something we should devote further time to? 2. Fragment test payload validation (
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 4868
The gap
The blocking constraint is the Recommended approach: two complementary impls1. Blanket impl covering all real protocol-error types: use std::fmt;
impl<E: fmt::Display + fmt::Debug> fmt::Display for WireframeError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DuplicateRoute(id) => write!(f, "duplicate route: {id}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Protocol(e) => write!(f, "protocol error: {e}"),
Self::Codec(e) => write!(f, "codec error: {e}"),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for WireframeError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
Self::Codec(e) => Some(e),
Self::Protocol(e) => Some(e),
Self::DuplicateRoute(_) => None,
}
}
}2. Specific impl for the impl fmt::Display for WireframeError<()> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::DuplicateRoute(id) => write!(f, "duplicate route: {id}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Protocol(()) => write!(f, "protocol error"),
Self::Codec(e) => write!(f, "codec error: {e}"),
}
}
}
impl std::error::Error for WireframeError<()> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
Self::Codec(e) => Some(e),
_ => None,
}
}
}These two impls are not overlapping because Downstream effect on
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 5549
Here is the reasoning: The echo app echoes the raw FRAG-encoded fragment envelopes back across the wire. The test's purpose is narrow and correct: validate that Leave the test as-is. |
Summary
testkitand gate it behind the feature, i.e.wireframe::testkit.testkitsurface and in-crate driving helpers, reassembly/assertion utilities, and slow-I/O helpers behind thetestkitfeature. This includes root-level integration and behavioural tests referencing the new surface.wireframe_testingas a compatibility facade to preserve source compatibility while migrating helpers into the root surface in future steps.wireframe::testkitand thetestkitfeature gating, while maintaining existingwireframe_testingusage.Changes
testkit:[features]
testkit = []
src/lib.rs:src/testkit/suite with modules for fragment driving, partial-frame driving, reassembly assertions, slow-I/O helpers, result types, and shared support.wireframe::testkitbehind thetestkitfeature, including:docs/execplans/8-5-4-export-wireframe-testkit-behind-feature.md(new).testkitfeature and updated usage examples.wireframe = { path = "..", features = ["test-support", "pool", "testkit"] }).Rationale
Plan / Milestones
wireframe::testkitand gate behindtestkit.wireframe::testkit, convertwireframe_testingexports to a compatibility facade, add rstest/rstest-bdd coverage, and update ADRs/docs.Testing plan
testkit.wireframe::testkitsurface compiles and tests exercise the new API.Acceptance criteria
wireframe::testkitexists only withfeature = "testkit".wireframe_testingremains source-compatible for the covered helpers.wireframe::testkitsurface.wireframe::testkitvswireframe_testing.docs/roadmap.mdmarks 8.5.4 as done.Risks & mitigations
testkitand avoid direct re-exports from the default surface.📎 Task: https://www.devboxer.com/task/b714b319-2386-437b-bbfc-e9ab37542426