Skip to content
Open
Changes from all commits
Commits
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
32 changes: 29 additions & 3 deletions src/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ pub(crate) enum CacheStatus {
}

impl CacheStatus {
/// Stable kebab-case string used by the JSON serializer and the
/// stderr summary line. One canonical mapping; the serde derive
/// uses the same encoding.
/// Stable kebab-case string for the stderr summary line (reached via
/// the [`std::fmt::Display`] impl). The serde derive independently produces the
/// same kebab-case encoding for JSON; these are two mappings that must
/// agree, and `cache_status_as_str_matches_serde` guards that they do.
pub(crate) fn as_str(self) -> &'static str {
match self {
Self::HitExact => "hit-exact",
Expand Down Expand Up @@ -837,6 +838,31 @@ mod tests {
assert_eq!(json, "\"hit-with-divergence\"");
}

/// `as_str` (stderr summary, via `Display`) and the serde derive (JSON)
/// are two independent kebab-case mappings that must produce the same
/// string for every variant. Guard the whole set so renaming one
/// encoding without the other fails loudly instead of silently drifting.
#[test]
fn cache_status_as_str_matches_serde() {
for status in [
CacheStatus::HitExact,
CacheStatus::HitWithDivergence,
CacheStatus::MissFingerprint,
CacheStatus::MissNoCoverage,
CacheStatus::MissNoReachableSha,
CacheStatus::ForcedAll,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This array is a third hand-maintained enumeration of the variants. as_str's match is exhaustive, so the compiler forces a new variant to be added there — but nothing forces it into this array, so a newly-added variant would silently escape the guard. That's the same silent-drift failure mode the PR is closing, just one level up.

A dependency-free way to make it fail loudly: drop an exhaustive match next to the array so adding a variant breaks compilation until it's listed here too, e.g.

// Compile-time reminder: a new variant must be added to the array above.
fn _exhaustive(s: CacheStatus) {
    match s {
        CacheStatus::HitExact
        | CacheStatus::HitWithDivergence
        | CacheStatus::MissFingerprint
        | CacheStatus::MissNoCoverage
        | CacheStatus::MissNoReachableSha
        | CacheStatus::ForcedAll => {}
    }
}

Not blocking — the current test fully covers the rename-one-encoding case it targets.

] {
let serde = serde_json::to_string(&status).unwrap();
assert_eq!(
serde,
format!("\"{}\"", status.as_str()),
"as_str and serde disagree for {status:?}",
);
// Display routes through as_str — keep it in the same guard.
assert_eq!(status.to_string(), status.as_str());
}
}

/// Stored fingerprint diff against current: differing labels are
/// the symmetric difference of (label, hash) sets.
#[test]
Expand Down
Loading