Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3355daf
docs(execplans): add ExecPlan for converting root manifest to hybrid …
leynos Apr 10, 2026
09e6f04
feat(cargo-workspace): convert root manifest into explicit hybrid wor…
leynos Apr 11, 2026
b6008db
fix(logging): recover logger mutex after panic to keep buffered state
leynos Apr 11, 2026
45cda05
refactor(tests): use camino and cap-std for UTF-8 path handling in tests
leynos Apr 11, 2026
4832884
refactor(tests/fixtures): improve workspace_manifest fixture with UTF…
leynos Apr 11, 2026
b5a59b8
refactor(helpers/tests): import Serializer from prelude instead of di…
leynos Apr 12, 2026
7e415a9
refactor(frame, message_assembler): simplify sequence tracking and pr…
leynos Apr 12, 2026
bc4df6a
style(frame): reformat clippy lint reason string for readability
leynos Apr 12, 2026
c243e15
refactor(tests): extract check_budget_abort_error helper in memory_bu…
leynos Apr 12, 2026
39586c0
fix(tests): pass std::io::Error by reference in memory budget test he…
leynos Apr 12, 2026
5d4e7da
refactor(tests): update imports to use wireframe::prelude::WireframeC…
leynos Apr 12, 2026
f33b5e8
test(workspace manifest): use cargo pkgid to get package IDs in tests
leynos Apr 12, 2026
64e2323
refactor(tests): simplify integration_helpers.rs and workspace_manife…
leynos Apr 12, 2026
bc0f347
test(workspace-manifest): refactor tests to use shared helpers
leynos Apr 12, 2026
7e83e61
test(workspace_manifest_support): add has_manifest_line helper for ma…
leynos Apr 12, 2026
2ada1f9
docs(tests): add detailed developer guide on workspace manifest test …
leynos Apr 13, 2026
b11afaf
refactor(logging): refactor LoggerHandle to use shared_logger function
leynos Apr 14, 2026
5bc3ccf
refactor(integration_helpers): refactor async test for idiomatic erro…
leynos Apr 14, 2026
e0e27d1
docs(testing): add detailed documentation for test infrastructure and…
leynos Apr 15, 2026
a9a6e9b
fix(rebase): restore bytes_to_u64 buffer
leynos Apr 15, 2026
0ccf3c5
test(workspace_manifest): add serde_json and improve workspace manife…
leynos Apr 15, 2026
9c9015a
refactor(message_assembler): refactor sequence advancement to use hel…
leynos Apr 15, 2026
25314bd
test(workspace_manifest): improve verification of workspace default m…
leynos Apr 15, 2026
0d2c2df
test(workspace_manifest): enhance workspace manifest tests with detai…
leynos Apr 16, 2026
be96370
feat(message_assembler): add sequence tracking and overflow handling
leynos Apr 16, 2026
9109a8e
test(message_assembler): replace property tests with focused unit tes…
leynos Apr 16, 2026
6e692f8
test(frame, message_assembler): add unit test modules for buffer plac…
leynos Apr 16, 2026
1b56033
feat(frame): make fill_buf_with_prefix return io::Result for validation
leynos Apr 16, 2026
5db825a
docs(developers-guide): fix punctuation in LoggerHandle::new() behavi…
leynos Apr 16, 2026
18a962f
test(message_assembler/series): move sequence tracking tests to dedic…
leynos Apr 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ keywords = ["async", "networking", "binary-protocol", "protocol", "tokio"]
categories = ["network-programming", "asynchronous"]
documentation = "https://docs.rs/wireframe"

[workspace]
members = ["."]
default-members = ["."]
resolver = "3"

[lib]
name = "wireframe"
path = "src/lib.rs"
Expand Down Expand Up @@ -63,6 +68,9 @@ metrics-util = "0.20.0"
tracing-test = "0.2.5"
mockall = "0.13.1"
criterion = "0.5.1"
cap-std = { version = "3.4.5", features = ["fs_utf8"] }
camino = "1.2.2"
serde_json = "1.0.141"

tokio = { version = "1.47.1", default-features = false, features = [
"macros",
Expand Down
145 changes: 137 additions & 8 deletions docs/developers-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,47 @@ Message continuation ordering lives in `src/message_assembler/series.rs`. The
public-facing control point is `validate_and_advance_sequence()`, which keeps
the series state machine readable by delegating to two private helpers:

- `handle_untracked_first_sequence()` handles the first numbered continuation
after an untracked start. It switches the series into tracked mode and
derives the next expected sequence.
- `start_sequence_tracking()` handles the first numbered continuation after an
untracked start. It switches the series into tracked mode and then delegates
to `advance_sequence_or_overflow()` to derive and record the next expected
sequence number.
- `advance_tracked_sequence()` handles all later numbered continuations once
tracking is active. It rejects duplicates, gaps, and sequence overflow before
advancing the expected sequence.

This split is intentional. The helpers isolate the untracked-to-tracked
transition from steady-state tracked validation so sequence policy stays flat,
testable, and documentation-friendly during future refactors.
calling `advance_sequence_or_overflow()` to advance the expected sequence.

`advance_sequence_or_overflow()` is the shared leaf helper: it calls
`checked_increment()` on the incoming sequence and raises
`MessageSeriesError::SequenceOverflow` only when the counter wraps while more
frames are still expected.

This three-helper split is intentional. `start_sequence_tracking()` isolates
the untracked-to-tracked transition; `advance_tracked_sequence()` owns the
duplicate/gap/out-of-order validation; and `advance_sequence_or_overflow()`
owns the overflow decision. Each piece is independently testable and the
decision tree stays flat and readable during future refactors.

### `fill_buf_with_prefix`

The private helper `fill_buf_with_prefix(buf, prefix, endianness)` in
`src/frame/conversion.rs` copies a validated length-prefix byte slice into the
correct position of an 8-byte staging buffer. For big-endian prefixes it places
the bytes at the high end of the buffer (`buf[8 - size..]`); for little-endian
prefixes it places them at the low end (`buf[..size]`). Callers must guarantee
that `prefix.len()` is one of `{1, 2, 4, 8}`; this invariant is enforced
upstream in `bytes_to_u64`, which is why the helper validates the range before
copying and reports an error when the prefix width falls outside the supported
set.

### `ServerShutdownHandle`

`ServerShutdownHandle` in `wireframe_testing::client_pair` is a type alias for
the tuple `(oneshot::Sender<()>, JoinHandle<Result<(), ServerError>>)` that
`PendingServer` stores between server start-up and explicit shutdown. Naming
the alias keeps `PendingServer`'s field types readable and makes
`PendingServer::take` return a self-documenting
`Option<ServerShutdownHandle>` instead of an opaque inline tuple. Tests that
call `WireframePair::shutdown()` do not interact with this type directly; it
is an internal implementation detail of the pair harness.

## Vocabulary normalization outcome (2026-02-20)

Expand All @@ -81,3 +112,101 @@ Use the Makefile targets as the contributor entrypoint for routine validation:
Install Whitaker through the standalone installer described in the
[Whitaker user's guide](whitaker-users-guide.md) so local linting matches
continuous integration (CI).

## Cargo workspace semantics

Wireframe now uses a hybrid root manifest: the repository root `Cargo.toml`
contains both `[package]` and `[workspace]`.

During roadmap item 10.1.1 the workspace is intentionally staged with only the
root package as a member and default member:

- `members = ["."]`
- `default-members = ["."]`

That means ordinary root-level commands such as `cargo build`, `cargo check`,
`cargo test`, and `cargo clippy` retain their existing ergonomics and continue
to target the main `wireframe` package by default.

Use plain root-level Cargo commands for day-to-day work on the main crate.
Reach for `--workspace` when a task is explicitly meant to cover every current
workspace member, for example repository-wide validation in CI or when later
roadmap items add internal verification crates.

One Cargo nuance is worth knowing: `cargo metadata` for this repository still
reports the in-tree helper crate `wireframe_testing` in `workspace_members`
because it lives under the repository root as a path dependency. That does not
change day-to-day command ergonomics because `default-members = ["."]` keeps
plain root-level Cargo commands focused on the main `wireframe` package.

### Workspace manifest test support

The shared module `tests/common/workspace_manifest_support.rs` keeps the
workspace-contract helpers beside the integration tests that use them. It is a
module, not a library crate, because the code is test-only scaffolding and
should not widen the published crate surface or add another Cargo target.

The support layer uses `cap-std` with the `fs_utf8` feature for
capability-oriented directory access, `camino` for UTF-8-typed paths, and
`serde_json` for structured assertions over `cargo metadata` output.

- `repo_root()` locates the repository root as a `Utf8PathBuf`.
- `repo_dir()` opens that root as a `cap_std::fs_utf8::Dir`.
- `root_manifest()` reads the root `Cargo.toml` into a `String`.
- `run_cargo(args)` runs `cargo` in the repository root and returns UTF-8
stdout, or an error that includes stderr.
- `cargo_metadata()` wraps `cargo metadata --no-deps --format-version 1`.
- `root_package_id()` wraps `cargo pkgid -- wireframe` and trims trailing
whitespace.
- `has_manifest_line(manifest, line)` checks for a complete trimmed line rather
than a substring match.

`WorkspaceManifestWorld` in `tests/fixtures/workspace_manifest.rs` is the
behaviour-driven development (BDD) fixture for these assertions. Extend it by
loading more workspace-state inputs in `load()` and adding focused verification
methods that the step definitions and scenario can reuse.

## Test infrastructure and framework

### rstest and rstest-bdd

The test suite uses [`rstest`](https://crates.io/crates/rstest) for
fixture-based parametric tests and `rstest-bdd` (via `rstest_bdd_macros`) for
behaviour-driven development (BDD) scenarios expressed in Gherkin.

Fixtures are plain Rust functions annotated with `#[fixture]`. Inject them into
tests by listing them as parameters; `rstest` constructs each fixture before
running the test body.

BDD scenarios live in `.feature` files under `tests/features/`. Each file
describes one or more scenarios using the standard Given/When/Then syntax.
Scenario functions are annotated with `#[scenario(path = "…", name = "…")]` and
receive fixture parameters by name.

### Feature files and step definitions

Each `.feature` file under `tests/features/` has a corresponding
step-definition module under `tests/steps/`. Step functions are annotated with
`#[given]`, `#[when]`, or `#[then]` and accept a mutable reference to the BDD
world fixture as their first argument.

Add new scenarios by:

1. Writing a new Gherkin scenario in the relevant `.feature` file.
2. Implementing the missing step functions in the corresponding
`tests/steps/` module.
3. Adding a scenario function in `tests/scenarios/` that names the new
scenario, injects the fixture, and delegates to a helper that invokes the
step logic in sequence.

### `LoggerHandle::Default` and `ObservabilityHandle::Default`

Both `LoggerHandle` (in `wireframe_testing::logging`) and `ObservabilityHandle`
(in `wireframe_testing::observability`) implement `Default`. The `default()`
method delegates to `new()` in each case, providing a convenient way to acquire
a fresh handle without explicitly calling the constructor.

`LoggerHandle::new()` tolerates a poisoned mutex: if a prior test panicked
while holding the logger lock, `new()` recovers the guard via `into_inner()`
and drains any buffered log records, so the next test starts from a clean
state.
Loading
Loading