Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/storage-git-gix/parked-gix-2/gunnar-store-gix/tests/swallowed_errors.rs

//! `S-030`: the failures this crate used to drop on the floor now say so.
//!
//! # Why a subscriber and not an eyeball
//!
//! Every fix under `S-030` replaces `let _ = …` with an `error!`, and "it emits
//! an error" is a claim about behaviour that only a subscriber can check. A
//! test that merely called the function and observed no panic would pass
//! against the swallow it is supposed to have removed — which is the shape the
//! whole finding is about.
//!
//! The capture is thread-local (`tracing::subscriber::with_default`), which is
//! sound here and would not be for a transfer: [`gunnar_store::drop_keep`] is
//! synchronous and runs on the caller's thread.

#![cfg(feature = "fs")]

use std::sync::{Arc, Mutex};

use tracing::subscriber::with_default;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Layer;

/// Every event seen, as `target` plus the rendered fields.
#[derive(Clone, Default)]
struct Capture(Arc<Mutex<Vec<(String, String)>>>);

impl Capture {
    fn events(&self) -> Vec<(String, String)> {
        self.0.lock().expect("the capture lock").clone()
    }
}

struct CaptureLayer(Capture);

impl<S: tracing::Subscriber> Layer<S> for CaptureLayer {
    fn on_event(&self, event: &tracing::Event<'_>, _: tracing_subscriber::layer::Context<'_, S>) {
        struct Render(String);
        impl tracing::field::Visit for Render {
            fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
                self.0.push_str(&format!("{}={value:?} ", field.name()));
            }
        }
        let mut render = Render(String::new());
        event.record(&mut render);
        self.0
             .0
            .lock()
            .expect("the capture lock")
            .push((event.metadata().target().to_owned(), render.0));
    }
}

fn capturing<T>(body: impl FnOnce() -> T) -> (T, Vec<(String, String)>) {
    let capture = Capture::default();
    let subscriber = tracing_subscriber::registry().with(CaptureLayer(capture.clone()));
    let out = with_default(subscriber, body);
    (out, capture.events())
}

/// A `.keep` that will not go is **reported**, and the caller still returns.
///
/// The failure is forced by pointing `drop_keep` at a path inside a read-only
/// directory, which is a real `EACCES` from the real filesystem rather than a
/// stub — the point being that this is the error the production call sites
/// used to discard.
#[test]
fn a_keep_marker_that_cannot_be_removed_is_reported() {
    let dir = tempfile::tempdir().expect("temporary directory");
    let locked = dir.path().join("pack");
    std::fs::create_dir(&locked).expect("create");
    let keep = locked.join("pack-deadbeef.keep");
    std::fs::write(&keep, b"gunnar").expect("write the marker");

    let mut perms = std::fs::metadata(&locked).expect("metadata").permissions();
    perms.set_readonly(true);
    std::fs::set_permissions(&locked, perms).expect("make the directory read-only");

    let (_, events) = capturing(|| gunnar_store::drop_keep(&keep));

    // Put the permissions back before any assertion can leave the tempdir
    // undeletable.
    let mut perms = std::fs::metadata(&locked).expect("metadata").permissions();
    #[allow(clippy::permissions_set_readonly_false)]
    perms.set_readonly(false);
    std::fs::set_permissions(&locked, perms).expect("restore");

    assert!(
        keep.exists(),
        "the marker was removed after all, so this test did not force the failure \
         it is about"
    );
    let reported: Vec<_> = events
        .iter()
        .filter(|(target, fields)| target == "gunnar::store" && fields.contains("store.keep"))
        .collect();
    assert_eq!(
        reported.len(),
        1,
        "a .keep that could not be removed produced {} lines, not one. All events: {events:?}",
        reported.len()
    );
    assert!(
        reported[0].1.contains("pack-deadbeef.keep"),
        "the line does not name the marker: {:?}",
        reported[0]
    );
}

/// The half that stops the first test being satisfied by a `drop_keep` that
/// logs unconditionally — which would put an error line in an operator's
/// journal on every successful repack and every successful push.
#[test]
fn removing_a_keep_that_is_there_is_silent_and_actually_removes_it() {
    let dir = tempfile::tempdir().expect("temporary directory");
    let keep = dir.path().join("pack-c0ffee.keep");
    std::fs::write(&keep, b"gunnar").expect("write the marker");

    let (_, events) = capturing(|| gunnar_store::drop_keep(&keep));

    assert!(!keep.exists(), "the marker was not removed");
    assert!(
        events.is_empty(),
        "a successful removal was logged: {events:?}"
    );

    // Already gone is the outcome this function wants, not a failure.
    let (_, events) = capturing(|| gunnar_store::drop_keep(&keep));
    assert!(
        events.is_empty(),
        "removing an absent marker was reported as a failure: {events:?}"
    );
}

/// The target `drop_keep` writes to is the one an operator filters on.
///
/// `gunnar-store` cannot import `gunnar_core::observe::TARGET_STORE` — the
/// dependency runs the other way — so the string is restated in `repack.rs`,
/// and a restated constant that nothing compares is a constant that drifts.
#[test]
fn the_store_target_is_still_the_one_the_tree_agrees_on() {
    let dir = tempfile::tempdir().expect("temporary directory");
    let keep = dir.path().join("nested").join("pack-f00d.keep");
    // The parent does not exist, so removal fails with NotFound's sibling
    // rather than NotFound itself on some platforms; either way, if it does
    // not fail we say so instead of asserting on an empty capture.
    let (_, events) = capturing(|| gunnar_store::drop_keep(&keep));
    if events.is_empty() {
        // A missing parent yields NotFound, which is deliberately silent.
        // Nothing to assert; the read-only case above carries the target.
        return;
    }
    assert_eq!(events[0].0, "gunnar::store");
}