feat(adr-118/p6.9): BfldError Display format pinning (290/290 GREEN)
Iter 42. Pins the thiserror-derived Display output for every BfldError
variant. Operators grep log lines for these strings; format drift
between minor versions breaks monitoring queries and alerting rules.
This iter locks the contract.
Added (in tests/bfld_error_display.rs, 11 named tests):
- One test per BfldError variant asserting the documented substrings
appear in to_string():
invalid_magic_displays_both_expected_and_actual_in_hex
unsupported_version_displays_the_offending_version
crc_mismatch_displays_both_values_in_hex
privacy_violation_displays_the_sink_reason
invalid_privacy_class_displays_the_offending_byte
truncated_frame_displays_got_and_need_byte_counts
malformed_section_displays_offset_and_reason
invalid_demote_displays_both_from_and_to_class_bytes
- Meta tests:
bfld_error_implements_std_error_trait
(compile-time witness via fn assert_error_trait<E: std::error::Error>())
bfld_error_is_debug_so_panic_unwrap_messages_carry_diagnostics
every_variant_has_a_non_empty_display_string
(catch-all: 8 variants × non-empty Display assertion;
guards against a future PR that adds a new variant without
the #[error(...)] attribute)
ADR-124 status (iter step 0 sibling check):
- docs/adr/ADR-124-rvagent-mcp-ruvector-npm-integration.md unchanged
at 431 lines. SENSE-BRIDGE scope remains orthogonal.
ACs progressed:
- ADR-118 §2.1 operator observability — error-message contract now
pinned. A monitoring rule that greps for "payload CRC mismatch"
or "privacy violation" continues to fire correctly across BFLD
versions.
Test config:
- cargo test --no-default-features → 90 passed (bfld_error_display cfg-out)
- cargo test → 290 passed (279 + 11)
Out of scope (next iter target):
- PR-readiness pivot remains the genuine next move: CHANGELOG batch,
witness bundle regeneration, AC closeout table. All in-crate ACs
empirically covered; remaining work is external-resource-gated
(KIT BFId, Pi5/Nexmon hardware) or PR-prep.
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
parent
a7ccac7869
commit
d1bc3cfcf1
|
|
@ -0,0 +1,132 @@
|
|||
//! `BfldError` Display format pinning. Operators grep log lines for these
|
||||
//! strings; format drift between minor versions breaks monitoring queries.
|
||||
//! Each variant gets a test that asserts the documented substrings appear.
|
||||
|
||||
#![cfg(feature = "std")]
|
||||
|
||||
use wifi_densepose_bfld::BfldError;
|
||||
|
||||
#[test]
|
||||
fn invalid_magic_displays_both_expected_and_actual_in_hex() {
|
||||
let err = BfldError::InvalidMagic(0xDEAD_BEEF);
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("invalid BFLD magic"), "got: {s}");
|
||||
assert!(s.contains("0xBF1D0001"), "expected magic missing: {s}");
|
||||
assert!(s.contains("0xDEADBEEF"), "actual magic missing: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_version_displays_the_offending_version() {
|
||||
let err = BfldError::UnsupportedVersion(99);
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("unsupported BFLD version"), "got: {s}");
|
||||
assert!(s.contains("99"), "version number missing: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crc_mismatch_displays_both_values_in_hex() {
|
||||
let err = BfldError::Crc {
|
||||
expected: 0xCAFEBABE,
|
||||
actual: 0xDEADBEEF,
|
||||
};
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("payload CRC mismatch"), "got: {s}");
|
||||
assert!(s.contains("0xCAFEBABE"), "expected missing: {s}");
|
||||
assert!(s.contains("0xDEADBEEF"), "actual missing: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn privacy_violation_displays_the_sink_reason() {
|
||||
let err = BfldError::PrivacyViolation {
|
||||
reason: "NetworkKind",
|
||||
};
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("privacy violation"), "got: {s}");
|
||||
assert!(s.contains("NetworkKind"), "reason missing: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_privacy_class_displays_the_offending_byte() {
|
||||
let err = BfldError::InvalidPrivacyClass(7);
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("invalid PrivacyClass byte"), "got: {s}");
|
||||
assert!(s.contains("7"), "byte value missing: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn truncated_frame_displays_got_and_need_byte_counts() {
|
||||
let err = BfldError::TruncatedFrame { got: 50, need: 86 };
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("truncated frame"), "got: {s}");
|
||||
assert!(s.contains("50"), "got count missing: {s}");
|
||||
assert!(s.contains("86"), "need count missing: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_section_displays_offset_and_reason() {
|
||||
let err = BfldError::MalformedSection {
|
||||
offset: 1234,
|
||||
reason: "section body runs past buffer end",
|
||||
};
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("malformed payload section"), "got: {s}");
|
||||
assert!(s.contains("1234"), "offset missing: {s}");
|
||||
assert!(s.contains("buffer end"), "reason missing: {s}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_demote_displays_both_from_and_to_class_bytes() {
|
||||
let err = BfldError::InvalidDemote { from: 2, to: 1 };
|
||||
let s = err.to_string();
|
||||
assert!(s.contains("invalid demote"), "got: {s}");
|
||||
assert!(s.contains("from class 2"), "from missing: {s}");
|
||||
assert!(s.contains("to class 1"), "to missing: {s}");
|
||||
}
|
||||
|
||||
// --- meta: error implements std::error::Error (for ? + dyn use) -------
|
||||
|
||||
#[test]
|
||||
fn bfld_error_implements_std_error_trait() {
|
||||
fn assert_error_trait<E: std::error::Error>() {}
|
||||
assert_error_trait::<BfldError>();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bfld_error_is_debug_so_panic_unwrap_messages_carry_diagnostics() {
|
||||
let err = BfldError::Crc {
|
||||
expected: 0xAA,
|
||||
actual: 0xBB,
|
||||
};
|
||||
let debug = format!("{err:?}");
|
||||
assert!(debug.contains("Crc"), "Debug must show variant name: {debug}");
|
||||
}
|
||||
|
||||
// --- catch-all: every variant has a non-empty Display -----------------
|
||||
|
||||
#[test]
|
||||
fn every_variant_has_a_non_empty_display_string() {
|
||||
let cases: Vec<BfldError> = vec![
|
||||
BfldError::InvalidMagic(0),
|
||||
BfldError::UnsupportedVersion(0),
|
||||
BfldError::Crc {
|
||||
expected: 0,
|
||||
actual: 0,
|
||||
},
|
||||
BfldError::PrivacyViolation { reason: "X" },
|
||||
BfldError::InvalidPrivacyClass(0),
|
||||
BfldError::TruncatedFrame { got: 0, need: 0 },
|
||||
BfldError::MalformedSection {
|
||||
offset: 0,
|
||||
reason: "X",
|
||||
},
|
||||
BfldError::InvalidDemote { from: 0, to: 0 },
|
||||
];
|
||||
for err in cases {
|
||||
let s = err.to_string();
|
||||
assert!(!s.is_empty(), "Display for {err:?} returned empty string");
|
||||
assert!(
|
||||
s.len() >= 5,
|
||||
"Display for {err:?} suspiciously short: {s:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue