diff --git a/AGENTS.md b/AGENTS.md index f9fb3ac23..064ed377e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -193,22 +193,31 @@ working on the Rust portions of the project: - `make check-fmt` executes: ```sh - cargo fmt --all -- --check + cargo +nightly-2026-05-28 fmt --all -- --check ``` validating formatting across the Rust workspace without modifying files. - - `make lint` executes: + - `make lint` executes its Rustdoc and Clippy steps through + `RUST_DEBUG_CARGO`, then runs Whitaker separately: ```sh - RUSTDOCFLAGS="-D warnings" cargo doc --no-deps - cargo clippy --all-targets --all-features -- -D warnings - RUSTFLAGS="-D warnings" whitaker --all -- --all-targets --all-features + cd rust && RUSTDOCFLAGS="$(RUSTDOC_FLAGS)" \ + $(RUST_DEBUG_CARGO) doc --no-deps $(DOC_FLAGS) && \ + $(RUST_DEBUG_CARGO) clippy $(CLIPPY_FLAGS) + cd rust && $(LOCAL_TOOL_ENV) \ + RUSTFLAGS="$(WHITAKER_RUSTFLAGS)" $(WHITAKER) --all -- \ + $(WHITAKER_CARGO_FLAGS) yamllint --config-file .yamllint.yml .github/workflows actionlint ``` - linting every target with all features enabled, denying all Clippy - warnings, and validating GitHub Actions workflows. Keep `.yamllint.yml` + On Linux, `RUST_DEBUG_CARGO` selects `nightly-2026-08-23` with the explicit + `tools/dev-fast/config.toml` fragment; elsewhere it selects the prescribed + Cargo route. Whitaker never receives that fragment. The protected coverage, + release, verification, and MSRV paths also retain their separate toolchain + and linker policy. The target lints every target with all features enabled, + denies all Clippy warnings, and validates GitHub Actions workflows. Keep + `.yamllint.yml` compatible with GitHub's unquoted `on` trigger key and require each workflow to begin with `---`. CI must install yamllint with `uv tool` and use the pinned, checksum-verified actionlint @@ -221,20 +230,21 @@ working on the Rust portions of the project: ``` running the Rust test suite with warnings denied. Use `make fmt` - (`cargo fmt --all`) to apply formatting fixes reported by the formatter - check alongside the Python and Markdown formatters. + (`cargo +nightly-2026-05-28 fmt --all`) to apply formatting fixes reported + by the formatter check alongside the Python and Markdown formatters. - The project toolchain stays pinned to Rust `1.85.0` and declares `rustfmt`, `clippy`, and `rust-analyzer`. CI also provisions `nightly-2026-05-28` for the maintenance formatter, then restores the project toolchain. The current - `fmt` and `check-fmt` recipes still use the project toolchain. Linux-only - debug Rust work additionally uses the separately pinned dev-fast nightly - through `tools/dev-fast/config.toml`; run `make dev-fast-check` first, since - it requires both the `rustc-codegen-cranelift` and `clippy` components. The - stable toolchain remains mandatory for release, coverage, Whitaker, the - explicit `make msrv-check` verification target, and the macOS and Windows - platform alternatives. All Rust packages inherit Cargo's - `rust-version = "1.85.0"` contract, which also keeps MSRV-aware Clippy - findings valid when debug linting uses the dev-fast nightly. + `fmt` and `check-fmt` recipes explicitly select the pinned nightly only for + formatting. Linux-only debug Rust work additionally uses the separately + pinned dev-fast nightly through `tools/dev-fast/config.toml`; run + `make dev-fast-check` first, since it requires both the + `rustc-codegen-cranelift` and `clippy` components. The stable toolchain + remains mandatory for release, coverage, Whitaker, the explicit + `make msrv-check` verification target, and the macOS and Windows platform + alternatives. All Rust packages inherit Cargo's `rust-version = "1.85.0"` + contract, which also keeps MSRV-aware Clippy findings valid when debug + linting uses the dev-fast nightly. - Clippy warnings MUST be disallowed. - Fix any warnings emitted during tests in code instead of silencing them. - Where a function is too long, extract meaningfully named helper functions diff --git a/Makefile b/Makefile index 81bae9fc0..648704477 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,8 @@ DEVELOP_DEV_FAST_ENV = $(if $(DEVELOP_DEV_FAST_ENABLED),RUSTUP_TOOLCHAIN=$(DEV_F DEV_FAST_CHECK_COMMAND = test "$(DEV_FAST_HOST_IS_LINUX)" = yes || { printf '%s\n' 'dev-fast is supported only on Linux; use the stable backend on this host' >&2; exit 1; }; test -f "$(DEV_FAST_CONFIG_RELATIVE)" || { printf 'dev-fast configuration is missing: %s\n' "$(DEV_FAST_CONFIG_RELATIVE)" >&2; exit 1; }; command -v mold >/dev/null 2>&1 || { printf 'mold %s is required for Linux dev-fast builds\n' "$(DEV_FAST_MOLD_VERSION)" >&2; exit 1; }; mold --version | grep -q '^mold $(DEV_FAST_MOLD_VERSION_PATTERN)\($$\|[[:space:]]\)' || { printf 'mold %s is required for Linux dev-fast builds\n' "$(DEV_FAST_MOLD_VERSION)" >&2; exit 1; }; components="$$(rustup component list --installed --toolchain "$(DEV_FAST_TOOLCHAIN)")" || { printf 'cannot inspect the components installed for %s\n' "$(DEV_FAST_TOOLCHAIN)" >&2; exit 1; }; for component in $(DEV_FAST_REQUIRED_COMPONENTS); do printf '%s\n' "$$components" | grep -q "^$$component" || { printf 'install %s for %s before using dev-fast\n' "$$component" "$(DEV_FAST_TOOLCHAIN)" >&2; exit 1; }; done DEV_FAST_TEST_RUSTFLAGS = $(TEST_RUSTFLAGS) $(if $(DEV_FAST_HOST_IS_LINUX),-Clink-arg=-fuse-ld=mold) DEV_FAST_TEST_COMMAND = if $(LOCAL_TOOL_ENV) command -v cargo-nextest >/dev/null 2>&1; then cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(DEV_FAST_TEST_RUSTFLAGS)" $(DEV_FAST_CARGO_COMMAND) nextest run $(TEST_FLAGS) $(BUILD_JOBS); else echo "cargo-nextest not found; falling back to cargo test." >&2; cd $(RUST_DIR) && CARGO_BUILD_JOBS="$(TEST_CARGO_BUILD_JOBS)" RUSTFLAGS="$(DEV_FAST_TEST_RUSTFLAGS)" $(DEV_FAST_CARGO_COMMAND) test $(TEST_FLAGS) $(BUILD_JOBS); fi +RUSTFMT_TOOLCHAIN ?= nightly-2026-05-28 +RUSTFMT_CARGO ?= $(CARGO) +$(RUSTFMT_TOOLCHAIN) WHITAKER ?= whitaker BUILD_JOBS ?= RUST_FLAGS ?= -D warnings @@ -272,13 +274,13 @@ endif fmt: ruff ## Format sources $(RUFF) format $(RUFF) check --select I --fix - cd $(RUST_DIR) && $(CARGO) fmt --all + cd $(RUST_DIR) && $(RUSTFMT_CARGO) fmt --all $(LOCAL_TOOL_ENV) $(MDTABLEFIX) --in-place $(MDTABLEFIX_SELECT) $(MDTABLEFIX_EXTENSIONS) $(MDTABLEFIX_RULES) $(call run_markdownlint_files,$(MDLINT_FIX_COMMAND)) check-fmt: ruff ## Verify formatting $(RUFF) format --check - cd $(RUST_DIR) && $(CARGO) fmt --all -- --check + cd $(RUST_DIR) && $(RUSTFMT_CARGO) fmt --all -- --check $(LOCAL_TOOL_ENV) $(MDTABLEFIX) --check $(MDTABLEFIX_SELECT) $(MDTABLEFIX_EXTENSIONS) $(MDTABLEFIX_RULES) test-markdown-format: ## Validate the Markdown formatting Makefile contract diff --git a/cuprum/unittests/test_rust_formatter_toolchain.py b/cuprum/unittests/test_rust_formatter_toolchain.py index 2309f08f2..b6562f287 100644 --- a/cuprum/unittests/test_rust_formatter_toolchain.py +++ b/cuprum/unittests/test_rust_formatter_toolchain.py @@ -3,6 +3,9 @@ from __future__ import annotations import re +import shutil +import subprocess # ruff: ignore[suspicious-subprocess-import] - expands fixed local Makefile recipes. +import sys import tomllib from tests.helpers.docs import repo_root @@ -42,6 +45,59 @@ # Only these characters can begin a lexeme: `/` for comments, `r` for raw # strings, `"` for strings, and `'` for character literals. _LEXEME_STARTS = frozenset("/r\"'") +# The Makefile gates dev-fast on `uname -s`, which reports Linux for the hosts +# `sys.platform` reports as `linux*`. Keeping the check explicit means a broken +# host probe in the Makefile fails the contract below instead of mirroring it. +_HOST_IS_LINUX = sys.platform.startswith("linux") +_EXPECTED_DEBUG_ROUTE = ( + "RUSTUP_TOOLCHAIN=nightly-2026-08-23 probe-cargo " + "--config ../tools/dev-fast/config.toml" +) +_DEV_FAST_FRAGMENTS = ( + "RUSTUP_TOOLCHAIN=nightly-2026-08-23", + "--config ../tools/dev-fast/config.toml", +) + + +def _assert_target_avoids_the_formatter_route( + target: str, cargo_lines: list[str] +) -> None: + """Assert one lint or test target keeps the formatter's nightly route out. + + Parameters + ---------- + target + Makefile target whose expanded recipe is being checked. + cargo_lines + Cargo command lines from that target's dry-run recipe. + """ + assert all("probe-cargo +nightly-2026-05-28" not in line for line in cargo_lines), ( + f"{target} must not select the formatter's nightly Cargo route" + ) + + +def _assert_target_debug_route(target: str, cargo_lines: list[str]) -> None: + """Assert one lint or test target carries the host-appropriate debug route. + + The Makefile routes debug work through dev-fast only on Linux and falls back + to the bare injected Cargo elsewhere, so the fragment is required on Linux + and must be absent on every other host. + + Parameters + ---------- + target + Makefile target whose expanded recipe is being checked. + cargo_lines + Cargo command lines from that target's dry-run recipe. + """ + if _HOST_IS_LINUX: + assert any(_EXPECTED_DEBUG_ROUTE in line for line in cargo_lines), ( + f"{target} must retain the Linux dev-fast Cargo route" + ) + return + assert all( + fragment not in line for line in cargo_lines for fragment in _DEV_FAST_FRAGMENTS + ), f"{target} must omit the Linux-only dev-fast Cargo route off Linux" def test_formatter_toolchain_precedes_the_project_toolchain( @@ -244,3 +300,45 @@ def test_formatter_skips_are_limited_to_known_rstest_fixtures() -> None: assert observed == _FORMATTER_FIXTURE_SKIPS, ( "add a mutation proof before extending the formatter exception set" ) + + +def test_make_formatter_targets_select_the_pinned_nightly() -> None: + """Only formatter recipes select the pinned nightly cargo route.""" + make_executable = shutil.which("make") + assert make_executable is not None, "make must be available to expand recipes" + root = repo_root() + + def expanded_recipes(*targets: str) -> str: + """Return dry-run output for the fixed formatter contract targets.""" + completed = subprocess.run( # ruff: ignore[subprocess-without-shell-equals-true] + [make_executable, "--dry-run", "CARGO=probe-cargo", *targets], + check=True, + shell=False, + cwd=root, + capture_output=True, + encoding="utf-8", + ) + return completed.stdout + + formatter_recipes = expanded_recipes("fmt", "check-fmt") + formatter_lines = [ + line + for line in formatter_recipes.splitlines() + if line.startswith("cd rust && probe-cargo") + ] + expected_lines = [ + "cd rust && probe-cargo +nightly-2026-05-28 fmt --all", + "cd rust && probe-cargo +nightly-2026-05-28 fmt --all -- --check", + ] + assert formatter_lines == expected_lines, ( + "formatter targets must use only the injected pinned nightly cargo route" + ) + + for target in ("lint", "test"): + target_recipes = expanded_recipes(target) + cargo_lines = [ + line for line in target_recipes.splitlines() if "probe-cargo" in line + ] + assert cargo_lines, f"{target} must expand at least one Cargo command" + _assert_target_avoids_the_formatter_route(target, cargo_lines) + _assert_target_debug_route(target, cargo_lines) diff --git a/docs/developers-guide.md b/docs/developers-guide.md index ca2969230..2ea0b5c34 100644 --- a/docs/developers-guide.md +++ b/docs/developers-guide.md @@ -355,9 +355,18 @@ The lint job installs Nixie and Whitaker through the pinned `leynos/shared-actions` installers. Nixie's installer also provisions Merman; the bootstrap uses Rust `1.95.0` because Merman CLI `0.7.0` requires that compiler. The job then restores Rust `1.85.0`, the project's supported -toolchain, before installing Whitaker and running the project gates. The -Whitaker action receives `WHITAKER_INSTALLER_VERSION` from the job environment -(`0.2.7`, the workflow's configured installer version). +toolchain, before installing Whitaker and running the project gates. On Linux, +standard debug Rust build, test, Rustdoc, and Clippy commands select +`nightly-2026-08-23` with the explicit `tools/dev-fast/config.toml` fragment. +The current `typecheck` target is Python-only and selects no Cargo fragment. +`fmt` and `check-fmt` alone select `nightly-2026-05-28` through `RUSTFMT_CARGO` +in the Makefile. `rust/.rustfmt.toml` configures formatter behaviour only; +change the Makefile when changing the formatter toolchain. Coverage, release, +verification, MSRV, and Whitaker commands, together with macOS and Windows, +retain their prescribed fragment-free or separately pinned toolchains. The +stable pin declares `rustfmt`, `clippy`, and `rust-analyzer` for local +maintenance. The Whitaker action receives `WHITAKER_INSTALLER_VERSION` from the +job environment (`0.2.7`, the workflow's configured installer version). cargo-nextest is no longer installed here at all. The coverage job is the only place it runs, and the shared action installs it from checksummed official diff --git a/rust/.rustfmt.toml b/rust/.rustfmt.toml new file mode 100644 index 000000000..a2c5d8f56 --- /dev/null +++ b/rust/.rustfmt.toml @@ -0,0 +1,16 @@ +unstable_features = true +comment_width = 100 +format_code_in_doc_comments = true +imports_granularity = "Crate" +imports_layout = "HorizontalVertical" +wrap_comments = true +group_imports = "StdExternalCrate" +use_try_shorthand = true +hex_literal_case = "Lower" +format_strings = true +format_macro_matchers = true +fn_single_line = true +condense_wildcard_suffixes = true +use_field_init_shorthand = true + +ignore = ["cuprum-rust/tests/ui"] diff --git a/rust/cuprum-native-io/src/fd_ownership_kani_proofs.rs b/rust/cuprum-native-io/src/fd_ownership_kani_proofs.rs index 57ee51bfa..0d729ea15 100644 --- a/rust/cuprum-native-io/src/fd_ownership_kani_proofs.rs +++ b/rust/cuprum-native-io/src/fd_ownership_kani_proofs.rs @@ -2,10 +2,10 @@ //! //! The invariant under proof, over every modelled exit path: //! -//! - a reader passed through the production retention kernel is **never** closed by -//! Rust — it stays caller-owned on normal completion and error return; -//! - the `pump_stream` writer is **consumed** and closes exactly once on -//! every exit path, which is what signals EOF downstream. +//! - a reader passed through the production retention kernel is **never** closed by Rust — it stays +//! caller-owned on normal completion and error return; +//! - the `pump_stream` writer is **consumed** and closes exactly once on every exit path, which is +//! what signals EOF downstream. //! //! Scope. These are bounded-model proofs over the ownership *model* in //! [`super::fd_ownership_model`], not over real descriptors: Kani does not @@ -19,7 +19,11 @@ //! is bounded explicitly. use super::fd_ownership_model::{ - CloseLog, ExitMode, ModelFd, model_consume_stream, model_pump_stream, + CloseLog, + ExitMode, + ModelFd, + model_consume_stream, + model_pump_stream, model_with_borrowed_reader, }; diff --git a/rust/cuprum-native-io/src/fd_ownership_model.rs b/rust/cuprum-native-io/src/fd_ownership_model.rs index 205f46074..c8f2ddce3 100644 --- a/rust/cuprum-native-io/src/fd_ownership_model.rs +++ b/rust/cuprum-native-io/src/fd_ownership_model.rs @@ -6,9 +6,10 @@ //! real panic-unwind are proved by this model: native regressions exercise //! those paths, including the historical trailing-`mem::forget` mistake. -use crate::memory::with_retained_owner; use core::cell::Cell; +use crate::memory::with_retained_owner; + /// Records how many times the modelled descriptor was closed. /// /// A real close is an unmodellable side effect, so the model counts them. @@ -26,13 +27,9 @@ impl CloseLog { } /// Number of closes recorded so far. - pub(crate) const fn closes(self) -> u32 { - self.closes.into_inner() - } + pub(crate) const fn closes(self) -> u32 { self.closes.into_inner() } - fn record_close(&self) { - self.closes.set(self.closes.get().saturating_add(1)); - } + fn record_close(&self) { self.closes.set(self.closes.get().saturating_add(1)); } } /// A modelled *owning* descriptor handle. @@ -47,15 +44,11 @@ pub(crate) struct ModelFd<'log> { impl<'log> ModelFd<'log> { /// Reconstruct an owning handle that reports closes to `log`. - pub(crate) const fn new(log: &'log CloseLog) -> Self { - Self { log } - } + pub(crate) const fn new(log: &'log CloseLog) -> Self { Self { log } } } impl Drop for ModelFd<'_> { - fn drop(&mut self) { - self.log.record_close(); - } + fn drop(&mut self) { self.log.record_close(); } } /// Represents an ordinary operation error, not panic-unwind. @@ -118,9 +111,10 @@ mod tests { //! Concrete unit tests for the ownership model, mirroring the bounded //! Kani proofs so the contract is exercised by `make test` as well. - use super::{CloseLog, ExitMode, ModelFd, model_consume_stream, model_pump_stream}; use rstest::rstest; + use super::{CloseLog, ExitMode, ModelFd, model_consume_stream, model_pump_stream}; + #[rstest] #[case(ExitMode::Normal)] #[case(ExitMode::Error)] diff --git a/rust/cuprum-native-io/src/kani_proofs.rs b/rust/cuprum-native-io/src/kani_proofs.rs index a0af61759..b9ee4836c 100644 --- a/rust/cuprum-native-io/src/kani_proofs.rs +++ b/rust/cuprum-native-io/src/kani_proofs.rs @@ -13,9 +13,7 @@ use super::{ struct Resource<'a>(&'a Cell); impl Drop for Resource<'_> { - fn drop(&mut self) { - self.0.set(self.0.get() + 1); - } + fn drop(&mut self) { self.0.set(self.0.get() + 1); } } #[kani::proof] diff --git a/rust/cuprum-native-io/src/lib.rs b/rust/cuprum-native-io/src/lib.rs index befa1cf0b..4ebea514b 100644 --- a/rust/cuprum-native-io/src/lib.rs +++ b/rust/cuprum-native-io/src/lib.rs @@ -18,13 +18,14 @@ mod fd_ownership_model; #[cfg(unix)] pub use std::os::fd::{AsFd as AsStream, BorrowedFd as BorrowedStream, OwnedFd as OwnedStream}; +#[cfg(unix)] +use std::os::fd::{AsRawFd, FromRawFd}; #[cfg(windows)] pub use std::os::windows::io::{ - AsHandle as AsStream, BorrowedHandle as BorrowedStream, OwnedHandle as OwnedStream, + AsHandle as AsStream, + BorrowedHandle as BorrowedStream, + OwnedHandle as OwnedStream, }; - -#[cfg(unix)] -use std::os::fd::{AsRawFd, FromRawFd}; #[cfg(windows)] use std::os::windows::io::{FromRawHandle, RawHandle}; diff --git a/rust/cuprum-native-io/src/memory.rs b/rust/cuprum-native-io/src/memory.rs index d3e587fe8..2d3a9e134 100644 --- a/rust/cuprum-native-io/src/memory.rs +++ b/rust/cuprum-native-io/src/memory.rs @@ -27,15 +27,14 @@ pub(crate) fn with_retained_owner(value: T, operation: impl FnOnce(&mut T) #[cfg(test)] mod tests { //! Real unwinding through the production retention frame. - use super::with_retained_owner; use core::cell::Cell; use std::panic::{AssertUnwindSafe, catch_unwind}; + use super::with_retained_owner; + struct Close<'a>(&'a Cell); impl Drop for Close<'_> { - fn drop(&mut self) { - self.0.set(self.0.get() + 1); - } + fn drop(&mut self) { self.0.set(self.0.get() + 1); } } #[test] diff --git a/rust/cuprum-native-io/src/ownership_tests.rs b/rust/cuprum-native-io/src/ownership_tests.rs index d022fe47c..df3887ae4 100644 --- a/rust/cuprum-native-io/src/ownership_tests.rs +++ b/rust/cuprum-native-io/src/ownership_tests.rs @@ -4,10 +4,12 @@ use std::os::fd::{AsRawFd, IntoRawFd}; #[cfg(windows)] use std::os::windows::io::{AsRawHandle, IntoRawHandle}; -use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::{ + panic::{AssertUnwindSafe, catch_unwind}, + sync::{LockResult, Mutex, MutexGuard}, +}; use rstest::{fixture, rstest}; -use std::sync::{LockResult, Mutex, MutexGuard}; use super::{adopt_writer, borrow, borrow_reader, fd_is_open, pipe, read_once, with_owned_writer}; diff --git a/rust/cuprum-native-io/src/windows.rs b/rust/cuprum-native-io/src/windows.rs index 00236ba00..32bb15dce 100644 --- a/rust/cuprum-native-io/src/windows.rs +++ b/rust/cuprum-native-io/src/windows.rs @@ -1,15 +1,17 @@ //! Windows borrowed I/O, pipe ownership, and native handle observation. -use std::io::{self, Read, Write}; -use std::os::windows::io::{AsRawHandle, FromRawHandle}; +use std::{ + io::{self, Read, Write}, + os::windows::io::{AsRawHandle, FromRawHandle}, +}; +#[cfg(test)] +use windows_sys::Win32::Foundation::{ERROR_INVALID_HANDLE, GetHandleInformation}; use windows_sys::Win32::System::Pipes::CreatePipe; #[cfg(test)] use crate::PlatformFd; use crate::{BorrowedStream, OwnedStream}; -#[cfg(test)] -use windows_sys::Win32::Foundation::{ERROR_INVALID_HANDLE, GetHandleInformation}; /// Create two uniquely owned, non-inheritable anonymous pipe handles. /// diff --git a/rust/cuprum-rust/src/errors.rs b/rust/cuprum-rust/src/errors.rs index 0e3f4e6de..c7003a697 100644 --- a/rust/cuprum-rust/src/errors.rs +++ b/rust/cuprum-rust/src/errors.rs @@ -1,9 +1,9 @@ //! Map safe stream errors to Python exceptions without losing OS codes. -use cuprum_streams::PumpError; -use pyo3::PyErr; -use pyo3::exceptions::PyOSError; use std::io; +use cuprum_streams::PumpError; +use pyo3::{PyErr, exceptions::PyOSError}; + pub(crate) fn pump_error_to_py_err(err: PumpError) -> PyErr { match err { PumpError::Io(io_err) => io_error_to_py_err(io_err), @@ -38,9 +38,7 @@ fn strip_os_error_suffix(message: &str, code: i32) -> String { /// through `io::ErrorKind`, taken from the authoritative source rather than a /// parallel table. #[cfg(unix)] -fn os_error_to_py_err(code: i32, strerror: String) -> PyErr { - PyOSError::new_err((code, strerror)) -} +fn os_error_to_py_err(code: i32, strerror: String) -> PyErr { PyOSError::new_err((code, strerror)) } /// Build a Python `OSError` from a Win32 error code and its message. /// @@ -103,10 +101,12 @@ fn raw_os_error_parts(err: &io::Error) -> Option<(i32, String)> { mod tests { //! Unit tests for the canonical `PumpError` taxonomy: variant mapping, //! the non-fatal write predicate, and stable display messages. - use super::PumpError; - use rstest::rstest; use std::io; + use rstest::rstest; + + use super::PumpError; + #[test] fn io_errors_round_trip_their_kind() { let source = io::Error::new(io::ErrorKind::BrokenPipe, "downstream closed"); @@ -252,8 +252,8 @@ mod tests { fn a_synthesized_error_has_no_code_to_preserve(#[case] err: io::Error) { assert!( super::raw_os_error_parts(&err).is_none(), - "an error with no raw_os_error must select PyO3's ErrorKind mapping \ - rather than a fabricated code: {err:?}", + "an error with no raw_os_error must select PyO3's ErrorKind mapping rather than a \ + fabricated code: {err:?}", ); } diff --git a/rust/cuprum-rust/src/lib.rs b/rust/cuprum-rust/src/lib.rs index 0f6ef5d52..8f0b4d2a0 100644 --- a/rust/cuprum-rust/src/lib.rs +++ b/rust/cuprum-rust/src/lib.rs @@ -6,8 +6,7 @@ use cuprum_native_io::PlatformFd; use cuprum_streams::{BufferSize, PumpError, consume_stream, pump_stream}; -use pyo3::exceptions::PyValueError; -use pyo3::prelude::*; +use pyo3::{exceptions::PyValueError, prelude::*}; mod errors; #[cfg(test)] mod fd_tests; @@ -30,9 +29,7 @@ fn validate_buffer_size(size: i64) -> PyResult { #[must_use] #[doc(hidden)] #[pyfunction] -pub const fn is_available() -> bool { - true -} +pub const fn is_available() -> bool { true } #[expect( clippy::allow_attributes, diff --git a/rust/cuprum-rust/src/stream_pyfunctions.rs b/rust/cuprum-rust/src/stream_pyfunctions.rs index 30f162d6f..15b968569 100644 --- a/rust/cuprum-rust/src/stream_pyfunctions.rs +++ b/rust/cuprum-rust/src/stream_pyfunctions.rs @@ -1,8 +1,16 @@ //! Contain the Python stream exports and their generated `PyO3` wrappers. use super::{ - BufferSize, PumpError, PyResult, Python, ReaderFd, consume_stream, convert_fd, pump_stream, - pyfunction, validate_buffer_size, + BufferSize, + PumpError, + PyResult, + Python, + ReaderFd, + consume_stream, + convert_fd, + pump_stream, + pyfunction, + validate_buffer_size, }; /// Run a prepared stream operation after validating its buffer size. diff --git a/rust/cuprum-streams/src/buffer.rs b/rust/cuprum-streams/src/buffer.rs index ca8c6c198..e6623fd04 100644 --- a/rust/cuprum-streams/src/buffer.rs +++ b/rust/cuprum-streams/src/buffer.rs @@ -65,9 +65,7 @@ impl BufferBucket { } /// The rounded size in bytes. - const fn bytes(self) -> usize { - self.0 - } + const fn bytes(self) -> usize { self.0 } } #[cfg(test)] diff --git a/rust/cuprum-streams/src/consume_snapshot_tests.rs b/rust/cuprum-streams/src/consume_snapshot_tests.rs index 7e2d035ff..25cf108c8 100644 --- a/rust/cuprum-streams/src/consume_snapshot_tests.rs +++ b/rust/cuprum-streams/src/consume_snapshot_tests.rs @@ -20,9 +20,12 @@ //! Each case feeds a fixed payload through a real pipe, so the decode is driven //! by actual descriptor reads rather than a synthetic byte buffer. -use crate::errors::PumpError; -use crate::test_support::{make_pipe, write_all_to}; -use crate::{BufferSize, consume_stream_files}; +use crate::{ + BufferSize, + consume_stream_files, + errors::PumpError, + test_support::{make_pipe, write_all_to}, +}; /// Decode `payload` through the real `consume_stream_files` read loop using a /// pipe, reading `buffer_size` bytes at a time. @@ -130,9 +133,10 @@ mod properties { //! Payloads stay small so the unbuffered write in `consume` cannot fill the //! pipe and block, which is the precondition the helper documents. - use super::consume; use proptest::prelude::*; + use super::consume; + /// The largest payload a single unbuffered pipe write must accept. const MAX_PAYLOAD: usize = 512; /// The largest read buffer the properties exercise. diff --git a/rust/cuprum-streams/src/errors.rs b/rust/cuprum-streams/src/errors.rs index 94c0c1e91..6e1ea3034 100644 --- a/rust/cuprum-streams/src/errors.rs +++ b/rust/cuprum-streams/src/errors.rs @@ -1,5 +1,6 @@ //! Semantic errors for safe stream orchestration. use std::io; + use thiserror::Error; /// Semantic error for stream pump and consume operations. diff --git a/rust/cuprum-streams/src/io_utils/mod.rs b/rust/cuprum-streams/src/io_utils/mod.rs index 6df4072d3..0c99dc568 100644 --- a/rust/cuprum-streams/src/io_utils/mod.rs +++ b/rust/cuprum-streams/src/io_utils/mod.rs @@ -5,11 +5,9 @@ //! errors like broken pipes. Failures are reported through the crate's //! canonical [`PumpError`] taxonomy. -use std::cell::Cell; -use std::io; +use std::{cell::Cell, io}; -use crate::errors::PumpError; -use crate::pump_machine::WriteEvent; +use crate::{errors::PumpError, pump_machine::WriteEvent}; thread_local! { /// EINTR retries observed on the read path of the current operation. @@ -31,14 +29,10 @@ pub(crate) fn reset_retry_counters() { } /// EINTR retries accumulated on the read path since the last reset. -pub(crate) fn read_retry_count() -> u64 { - READ_RETRIES.with(Cell::get) -} +pub(crate) fn read_retry_count() -> u64 { READ_RETRIES.with(Cell::get) } /// EINTR retries accumulated on the write path since the last reset. -pub(crate) fn write_retry_count() -> u64 { - WRITE_RETRIES.with(Cell::get) -} +pub(crate) fn write_retry_count() -> u64 { WRITE_RETRIES.with(Cell::get) } /// Increment the current operation's read-path `EINTR` retry counter. /// diff --git a/rust/cuprum-streams/src/io_utils/tests.rs b/rust/cuprum-streams/src/io_utils/tests.rs index 9fbdd5426..7374b5bc8 100644 --- a/rust/cuprum-streams/src/io_utils/tests.rs +++ b/rust/cuprum-streams/src/io_utils/tests.rs @@ -1,17 +1,26 @@ //! Direct tests for descriptor-backed I/O helper contracts. -use std::io; -use std::os::fd::OwnedFd; +use std::{io, os::fd::OwnedFd}; use proptest::prelude::*; +use rstest::{fixture, rstest}; use super::{ - PumpError, WriteOutcome, classify_write_outcome, classify_write_with, handle_write, - map_short_write_error, read_raw_fd, read_raw_fd_with, read_stream, write_all_unix_with, + PumpError, + WriteOutcome, + classify_write_outcome, + classify_write_with, + handle_write, + map_short_write_error, + read_raw_fd, + read_raw_fd_with, + read_stream, + write_all_unix_with, +}; +use crate::{ + pump_machine::WriteEvent, + test_support::{make_pipe, unwrap_err, unwrap_ok, write_all_to}, }; -use crate::pump_machine::WriteEvent; -use crate::test_support::{make_pipe, unwrap_err, unwrap_ok, write_all_to}; -use rstest::{fixture, rstest}; /// A fresh `pipe(2)` pair (`read_end`, `write_end`) for descriptor-backed /// tests, so the shared setup lives in one place rather than a repeated diff --git a/rust/cuprum-streams/src/io_utils/tracing_tests.rs b/rust/cuprum-streams/src/io_utils/tracing_tests.rs index bbaa1701d..b8e142005 100644 --- a/rust/cuprum-streams/src/io_utils/tracing_tests.rs +++ b/rust/cuprum-streams/src/io_utils/tracing_tests.rs @@ -17,7 +17,11 @@ use proptest::prelude::*; use tracing::Level; use super::{ - operation_span, read_raw_fd_with, read_retry_count, reset_retry_counters, write_all_unix_with, + operation_span, + read_raw_fd_with, + read_retry_count, + reset_retry_counters, + write_all_unix_with, write_retry_count, }; use crate::tracing_capture::capture; @@ -45,8 +49,7 @@ fn warn_filter_keeps_eintr_warning_context() { assert!( captured.event_has_fields(Level::WARN, &["operation", "buffer_size"]), - "EINTR warn event must retain operation + buffer_size context under a \ - warn filter", + "EINTR warn event must retain operation + buffer_size context under a warn filter", ); } @@ -63,8 +66,7 @@ fn error_filter_keeps_fatal_write_context() { assert!( captured.event_has_fields(Level::ERROR, &["operation", "buffer_size"]), - "fatal write error event must retain operation + buffer_size context \ - under an error filter", + "fatal write error event must retain operation + buffer_size context under an error filter", ); } @@ -84,8 +86,8 @@ fn error_filter_keeps_ordinary_fatal_write_context() { assert!( captured.event_has_fields(Level::ERROR, &["operation", "buffer_size"]), - "map_short_write_error's fatal event must retain operation + \ - buffer_size context under an error filter", + "map_short_write_error's fatal event must retain operation + buffer_size context under an \ + error filter", ); } @@ -105,8 +107,8 @@ fn error_filter_keeps_read_overflow_context() { assert!( captured.event_has_fields(Level::ERROR, &["operation", "buffer_size"]), - "read length-overflow error event must retain operation + buffer_size \ - context under an error filter", + "read length-overflow error event must retain operation + buffer_size context under an \ + error filter", ); } diff --git a/rust/cuprum-streams/src/lib.rs b/rust/cuprum-streams/src/lib.rs index 2353a1622..17eab8c34 100644 --- a/rust/cuprum-streams/src/lib.rs +++ b/rust/cuprum-streams/src/lib.rs @@ -61,13 +61,9 @@ impl BufferSize { /// /// # Errors /// Rejects non-positive sizes and sizes above the allocation cap. - pub fn new(size: i64) -> Result { - checked_buffer_size(size).map(Self) - } + pub fn new(size: i64) -> Result { checked_buffer_size(size).map(Self) } - const fn value(self) -> usize { - self.0 - } + const fn value(self) -> usize { self.0 } } /// Pump from a borrowed reader, consuming the uniquely owned writer. diff --git a/rust/cuprum-streams/src/lib_tests.rs b/rust/cuprum-streams/src/lib_tests.rs index 4726d562c..9f5cb0fbd 100644 --- a/rust/cuprum-streams/src/lib_tests.rs +++ b/rust/cuprum-streams/src/lib_tests.rs @@ -1,15 +1,20 @@ //! Tests for the borrowed file-descriptor ownership contract. -use crate::buffer::ALLOCATION_FAILED_CATEGORY; -use crate::errors::PumpError; -use crate::io_utils::{classify_write, read_stream}; -use crate::pump_machine::WriteEvent; -use crate::test_support::{make_pipe, read_all_from, unwrap_ok, write_all_to}; -use crate::tracing_capture::capture; -use crate::{BufferSize, consume_stream_files, pump_stream_files_readwrite}; use rstest::rstest; use tracing::Level; +use crate::{ + BufferSize, + buffer::ALLOCATION_FAILED_CATEGORY, + consume_stream_files, + errors::PumpError, + io_utils::{classify_write, read_stream}, + pump_machine::WriteEvent, + pump_stream_files_readwrite, + test_support::{make_pipe, read_all_from, unwrap_ok, write_all_to}, + tracing_capture::capture, +}; + /// Consuming a pipe records its byte total and zero read retries in the span. #[rstest] fn consume_records_total_bytes_and_retries_on_span() { @@ -135,8 +140,7 @@ fn allocation_failure_reports_a_bounded_category_event() { ("platform", "unix"), ], ), - "a refused allocation must emit its stable category and bounded buffer \ - size at error level", + "a refused allocation must emit its stable category and bounded buffer size at error level", ); } @@ -245,8 +249,8 @@ fn pump_drains_the_reader_after_the_writer_breaks() { "broken pipe; draining reader", &[("bytes_transferred", "0")], ), - "the writer-close latch must report the hang-up by name, with the zero \ - byte total that reached the hung-up downstream", + "the writer-close latch must report the hang-up by name, with the zero byte total that \ + reached the hung-up downstream", ); // The reader must have been drained to EOF: a further read returns zero diff --git a/rust/cuprum-streams/src/pump_machine.rs b/rust/cuprum-streams/src/pump_machine.rs index ef9c3f4d4..99d79d8b4 100644 --- a/rust/cuprum-streams/src/pump_machine.rs +++ b/rust/cuprum-streams/src/pump_machine.rs @@ -69,14 +69,10 @@ impl PumpState { } /// Bytes confirmed written downstream so far. - pub(crate) const fn total_written(self) -> u64 { - self.total_written - } + pub(crate) const fn total_written(self) -> u64 { self.total_written } /// Whether the writer is still accepting data. - pub(crate) const fn writer_open(self) -> bool { - self.writer_open - } + pub(crate) const fn writer_open(self) -> bool { self.writer_open } } /// Whether the pump loop continues or stops after an iteration. diff --git a/rust/cuprum-streams/src/splice/mod.rs b/rust/cuprum-streams/src/splice/mod.rs index 8c2b7444d..6a2256d99 100644 --- a/rust/cuprum-streams/src/splice/mod.rs +++ b/rust/cuprum-streams/src/splice/mod.rs @@ -15,11 +15,11 @@ //! Splice requires at least one pipe endpoint. Regular files, most sockets, //! and other file descriptor types return `EINVAL` and trigger the fallback. -use cuprum_native_io::{AsStream, BorrowedStream, borrow}; use std::io; -use crate::errors::PumpError; -use crate::io_utils::read_raw_fd; +use cuprum_native_io::{AsStream, BorrowedStream, borrow}; + +use crate::{errors::PumpError, io_utils::read_raw_fd}; /// Attempt to pump data using `splice()`. Returns None if splice is not supported. /// diff --git a/rust/cuprum-streams/src/splice/tests.rs b/rust/cuprum-streams/src/splice/tests.rs index 310711d95..18590cfb8 100644 --- a/rust/cuprum-streams/src/splice/tests.rs +++ b/rust/cuprum-streams/src/splice/tests.rs @@ -2,16 +2,20 @@ //! drain: full transfer between pipes, fallback signalling for //! unsupported descriptor types, and broken-pipe draining. -use std::io::{self, Write}; -use std::os::fd::OwnedFd; +use std::{ + io::{self, Write}, + os::fd::OwnedFd, +}; use proptest::prelude::*; use rstest::{fixture, rstest}; use tempfile::NamedTempFile; use super::{accumulate_splices, drain_reader, splice_once_with, try_splice_pump}; -use crate::errors::PumpError; -use crate::test_support::{make_pipe, read_all_from, unwrap_ok, write_all_to}; +use crate::{ + errors::PumpError, + test_support::{make_pipe, read_all_from, unwrap_ok, write_all_to}, +}; /// A connected `pipe(2)` pair: `(read_end, write_end)`. type PipePair = (OwnedFd, OwnedFd); diff --git a/rust/cuprum-streams/src/test_support.rs b/rust/cuprum-streams/src/test_support.rs index d3dbaa827..0ed477cd2 100644 --- a/rust/cuprum-streams/src/test_support.rs +++ b/rust/cuprum-streams/src/test_support.rs @@ -1,24 +1,23 @@ //! Shared Unix file-descriptor fixtures and assertions for Rust unit tests. +use std::{ + fmt::Debug, + io::{self, Read, Write}, + os::fd::OwnedFd, +}; + use cap_std::fs::File; -use std::fmt::Debug; -use std::io::{self, Read, Write}; -use std::os::fd::OwnedFd; /// Create an anonymous pipe as `(read_end, write_end)`. /// /// Returns the operating-system error from `pipe(2)` so fixtures can pass it /// to the test body, where the failure receives assertion context. -pub(crate) fn make_pipe() -> io::Result<(OwnedFd, OwnedFd)> { - cuprum_native_io::pipe() -} +pub(crate) fn make_pipe() -> io::Result<(OwnedFd, OwnedFd)> { cuprum_native_io::pipe() } /// Duplicate a typed descriptor into an independently owned [`File`]. /// /// The caller retains its descriptor; cloning failures propagate to the test. -pub(crate) fn dup_as_file(fd: &OwnedFd) -> io::Result { - fd.try_clone().map(File::from) -} +pub(crate) fn dup_as_file(fd: &OwnedFd) -> io::Result { fd.try_clone().map(File::from) } /// Write every byte of `payload` through a duplicated descriptor. /// diff --git a/rust/cuprum-streams/src/tracing_capture.rs b/rust/cuprum-streams/src/tracing_capture.rs index 2813cca5d..b3fe85d19 100644 --- a/rust/cuprum-streams/src/tracing_capture.rs +++ b/rust/cuprum-streams/src/tracing_capture.rs @@ -8,15 +8,23 @@ //! production filters and that the pump/consume loops record `total_bytes` and //! retry counts on their span. -use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::fmt::Debug; -use std::sync::{Arc, LazyLock, Mutex, MutexGuard, PoisonError}; - -use tracing::field::{Field, Visit}; -use tracing::level_filters::LevelFilter; -use tracing::span::{Attributes, Id, Record}; -use tracing::subscriber::{Interest, NoSubscriber}; -use tracing::{Dispatch, Event, Level, Metadata, Subscriber}; +use std::{ + collections::{BTreeMap, BTreeSet, HashMap}, + fmt::Debug, + sync::{Arc, LazyLock, Mutex, MutexGuard, PoisonError}, +}; + +use tracing::{ + Dispatch, + Event, + Level, + Metadata, + Subscriber, + field::{Field, Visit}, + level_filters::LevelFilter, + span::{Attributes, Id, Record}, + subscriber::{Interest, NoSubscriber}, +}; /// A captured event: its level, the field names reachable from the active span /// stack when it was emitted, and the values it carried itself. @@ -184,9 +192,7 @@ impl Subscriber for FilterCapture { } /// Add the entered span to the active context stack. - fn enter(&self, span: &Id) { - lock(&self.state).stack.push(span.into_u64()); - } + fn enter(&self, span: &Id) { lock(&self.state).stack.push(span.into_u64()); } /// Remove the most recently entered matching span from the context stack. fn exit(&self, span: &Id) { @@ -209,9 +215,7 @@ struct DormantSubscriber; impl Subscriber for DormantSubscriber { /// Keep the retained guard dormant so it never receives events. - fn enabled(&self, _metadata: &Metadata<'_>) -> bool { - false - } + fn enabled(&self, _metadata: &Metadata<'_>) -> bool { false } /// Disable callsites while this is the registry's only dispatch. fn max_level_hint(&self) -> Option { @@ -221,9 +225,7 @@ impl Subscriber for DormantSubscriber { } /// Produce inert span identifiers because the guard retains no span state. - fn new_span(&self, attrs: &Attributes<'_>) -> Id { - NoSubscriber::new().new_span(attrs) - } + fn new_span(&self, attrs: &Attributes<'_>) -> Id { NoSubscriber::new().new_span(attrs) } /// Ignore records because the dormant guard retains no span state. fn record(&self, _span: &Id, _values: &Record<'_>) {} diff --git a/rust/cuprum-streams/src/tracing_capture_tests.rs b/rust/cuprum-streams/src/tracing_capture_tests.rs index 264be4839..c0b352fec 100644 --- a/rust/cuprum-streams/src/tracing_capture_tests.rs +++ b/rust/cuprum-streams/src/tracing_capture_tests.rs @@ -7,8 +7,7 @@ use proptest::prelude::*; use rstest::rstest; -use tracing::Level; -use tracing::callsite::Callsite; +use tracing::{Level, callsite::Callsite}; use super::{Captured, capture}; diff --git a/rust/cuprum-streams/src/utf8.rs b/rust/cuprum-streams/src/utf8.rs index 8e548f005..be9eabe57 100644 --- a/rust/cuprum-streams/src/utf8.rs +++ b/rust/cuprum-streams/src/utf8.rs @@ -9,9 +9,7 @@ pub(crate) struct ValidUpTo(usize); impl ValidUpTo { - pub(crate) const fn value(self) -> usize { - self.0 - } + pub(crate) const fn value(self) -> usize { self.0 } } /// Marker indicating whether this is the final chunk of input. @@ -19,13 +17,9 @@ impl ValidUpTo { pub(crate) struct FinalChunk(bool); impl FinalChunk { - pub(crate) const fn new(is_final: bool) -> Self { - Self(is_final) - } + pub(crate) const fn new(is_final: bool) -> Self { Self(is_final) } - pub(crate) const fn is_final(self) -> bool { - self.0 - } + pub(crate) const fn is_final(self) -> bool { self.0 } } /// Decode pending bytes as UTF-8, replacing invalid sequences. @@ -182,14 +176,14 @@ mod tests { fn incomplete_tail_strategy() -> impl Strategy> { prop_oneof![ - Just(vec![0xC2]), - Just(vec![0xDF]), - Just(vec![0xE0, 0xA0]), - Just(vec![0xE1, 0x80]), - Just(vec![0xEF, 0xBF]), - Just(vec![0xF0, 0x90, 0x80]), - Just(vec![0xF1, 0x80, 0x80]), - Just(vec![0xF4, 0x8F, 0xBF]), + Just(vec![0xc2]), + Just(vec![0xdf]), + Just(vec![0xe0, 0xa0]), + Just(vec![0xe1, 0x80]), + Just(vec![0xef, 0xbf]), + Just(vec![0xf0, 0x90, 0x80]), + Just(vec![0xf1, 0x80, 0x80]), + Just(vec![0xf4, 0x8f, 0xbf]), ] } diff --git a/rust/cuprum-streams/src/utf8_kani_proofs.rs b/rust/cuprum-streams/src/utf8_kani_proofs.rs index 2d7afc7bf..81f63ba8c 100644 --- a/rust/cuprum-streams/src/utf8_kani_proofs.rs +++ b/rust/cuprum-streams/src/utf8_kani_proofs.rs @@ -12,18 +12,18 @@ fn decode_single_chunk(input: &[u8], final_chunk: bool) -> (String, Vec) { fn is_valid_incomplete_utf8_sequence(pending: &[u8]) -> bool { match pending { [first] => matches!(first, 0xC2..=0xDF | 0xE0..=0xEF | 0xF0..=0xF4), - [0xE0, second] => matches!(second, 0xA0..=0xBF), - [0xE1..=0xEC, second] => matches!(second, 0x80..=0xBF), - [0xED, second] => matches!(second, 0x80..=0x9F), - [0xEE..=0xEF, second] => matches!(second, 0x80..=0xBF), - [0xF0, second] => matches!(second, 0x90..=0xBF), - [0xF1..=0xF3, second] => matches!(second, 0x80..=0xBF), - [0xF4, second] => matches!(second, 0x80..=0x8F), - [0xF0, second, third] => matches!(second, 0x90..=0xBF) && matches!(third, 0x80..=0xBF), - [0xF1..=0xF3, second, third] => { - matches!(second, 0x80..=0xBF) && matches!(third, 0x80..=0xBF) + [0xe0, second] => matches!(second, 0xa0..=0xbf), + [0xe1..=0xec, second] => matches!(second, 0x80..=0xbf), + [0xed, second] => matches!(second, 0x80..=0x9f), + [0xee..=0xef, second] => matches!(second, 0x80..=0xbf), + [0xf0, second] => matches!(second, 0x90..=0xbf), + [0xf1..=0xf3, second] => matches!(second, 0x80..=0xbf), + [0xf4, second] => matches!(second, 0x80..=0x8f), + [0xf0, second, third] => matches!(second, 0x90..=0xbf) && matches!(third, 0x80..=0xbf), + [0xf1..=0xf3, second, third] => { + matches!(second, 0x80..=0xbf) && matches!(third, 0x80..=0xbf) } - [0xF4, second, third] => matches!(second, 0x80..=0x8F) && matches!(third, 0x80..=0xBF), + [0xf4, second, third] => matches!(second, 0x80..=0x8f) && matches!(third, 0x80..=0xbf), _ => false, } } @@ -31,16 +31,16 @@ fn is_valid_incomplete_utf8_sequence(pending: &[u8]) -> bool { #[kani::proof] #[kani::unwind(5)] fn single_chunk_matches_from_utf8_lossy() { - let input = [b'a', 0xFF, b'b']; + let input = [b'a', 0xff, b'b']; let (output, pending) = decode_single_chunk(&input, true); let output_bytes = output.as_bytes(); kani::cover!(output_bytes.len() == 5, "exercise invalid UTF-8 payloads"); kani::assert(output_bytes.len() == 5, "replacement output length"); kani::assert(output_bytes[0] == b'a', "valid prefix is preserved"); - kani::assert(output_bytes[1] == 0xEF, "replacement byte 1"); - kani::assert(output_bytes[2] == 0xBF, "replacement byte 2"); - kani::assert(output_bytes[3] == 0xBD, "replacement byte 3"); + kani::assert(output_bytes[1] == 0xef, "replacement byte 1"); + kani::assert(output_bytes[2] == 0xbf, "replacement byte 2"); + kani::assert(output_bytes[3] == 0xbd, "replacement byte 3"); kani::assert(output_bytes[4] == b'b', "valid suffix is preserved"); kani::assert( pending.is_empty(), @@ -51,7 +51,7 @@ fn single_chunk_matches_from_utf8_lossy() { #[kani::proof] #[kani::unwind(5)] fn two_chunk_boundaries_match_from_utf8_lossy() { - let input = [0xC2, 0xA2]; + let input = [0xc2, 0xa2]; let split_point = 1_usize; let (left, right) = input.split_at(split_point); let chunks = [left, right]; @@ -63,8 +63,8 @@ fn two_chunk_boundaries_match_from_utf8_lossy() { "exercise split points that bisect valid multi-byte sequences" ); kani::assert(output_bytes.len() == 2, "two chunks decode one scalar"); - kani::assert(output_bytes[0] == 0xC2, "first scalar byte is preserved"); - kani::assert(output_bytes[1] == 0xA2, "second scalar byte is preserved"); + kani::assert(output_bytes[0] == 0xc2, "first scalar byte is preserved"); + kani::assert(output_bytes[1] == 0xa2, "second scalar byte is preserved"); kani::assert( pending.is_empty(), "final chunk must leave no pending bytes", @@ -74,7 +74,7 @@ fn two_chunk_boundaries_match_from_utf8_lossy() { #[kani::proof] #[kani::unwind(5)] fn pending_state_is_valid_incomplete_utf8() { - let input = [0xE0, 0xA0]; + let input = [0xe0, 0xa0]; let (_output, pending) = decode_single_chunk(&input, false); kani::assert( diff --git a/rust/cuprum-streams/tests/compile_tests.rs b/rust/cuprum-streams/tests/compile_tests.rs index f52935fdc..4215cc7b3 100644 --- a/rust/cuprum-streams/tests/compile_tests.rs +++ b/rust/cuprum-streams/tests/compile_tests.rs @@ -1,6 +1,4 @@ //! Compile contracts for safe stream policy. #![forbid(unsafe_code)] #[test] -fn transition_privacy() { - trybuild::TestCases::new().compile_fail("tests/ui/fail/*.rs"); -} +fn transition_privacy() { trybuild::TestCases::new().compile_fail("tests/ui/fail/*.rs"); }