crates/gunnar-lfs/tests/swallowed_errors.rs
#![cfg(feature = "fs")]
//! **An abandoned LFS upload that will not unlink must produce a line.**
//!
//! `FsPendingWrite::drop` removed the partial file with `let _ =
//! std::fs::remove_file(temp)`. That swallow is not the same as the two the
//! audit already closed in `gunnar-store`: a leaked `.keep` is collected by the
//! next `repack()`, and a leaked repack scratch file is collected by
//! `sweep_stale_scratch`. **Nothing in the tree ever looks at
//! `lfs/incomplete/`.** So a rejected or severed upload whose temp file cannot
//! be removed stays on disk at full size, under a name no oid resolves to, and
//! nothing anywhere says so.
//!
//! The failure below is a real `EACCES` from the real filesystem — a marker
//! inside a directory made read-only — rather than a stub, because the error
//! being asserted about is the one the production path actually discarded.
use std::io::Write as _;
use std::sync::{Arc, Mutex};
use gunnar_lfs::fs_store::FsLfsStore;
use gunnar_lfs::store::LfsStore;
use gunnar_lfs::Oid;
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)>>>);
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);
let events = capture.0.lock().expect("the capture lock").clone();
(out, events)
}
/// An oid whose value does not matter: the upload is abandoned, never committed.
fn some_oid() -> Oid {
Oid::parse("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")
.expect("a valid sha256 oid")
}
#[test]
fn an_abandoned_upload_that_cannot_be_removed_is_reported() {
let dir = tempfile::tempdir().expect("temporary directory");
let store = FsLfsStore::open_or_init(dir.path().join("repo.git")).expect("store");
let incomplete = dir.path().join("repo.git").join("lfs").join("incomplete");
let normal = std::fs::metadata(&incomplete)
.expect("the incomplete directory")
.permissions();
let (temp_files, events) = capturing(|| {
let mut pending = store.begin_write(&some_oid(), 8).expect("begin_write");
pending.write_all(b"half of a").expect("write");
// The upload is now on disk under `incomplete/`. Make the directory
// read-only so the unlink in `Drop` gets a genuine EACCES.
let mut locked = normal.clone();
locked.set_readonly(true);
std::fs::set_permissions(&incomplete, locked).expect("make the directory read-only");
drop(pending);
std::fs::read_dir(&incomplete)
.expect("read incomplete/")
.flatten()
.count()
});
// Undo before any assertion, or a failure leaves the tempdir undeletable.
std::fs::set_permissions(&incomplete, normal).expect("restore permissions");
// The premise, asserted rather than assumed: the file really did survive.
// Without this the test would pass on a machine where the removal succeeded
// anyway (root, or a filesystem that ignores the mode), having proved
// nothing about the missing log line.
assert_eq!(
temp_files, 1,
"the abandoned upload was removed after all, so no error was possible and this test \
cannot tell a reported failure from a silent one. Almost always: the suite is \
running as ROOT, which ignores the directory mode this injection depends on — in \
the toolchain container, `podman run --userns=keep-id`."
);
let reported: Vec<_> = events
.iter()
.filter(|(target, fields)| {
target == gunnar_lfs::LFS_TARGET && fields.contains("incomplete")
})
.collect();
assert_eq!(
reported.len(),
1,
"an abandoned LFS upload that could not be removed produced {} lines, not one. All \
events: {events:?}",
reported.len()
);
}
/// The other side: the ordinary abandoned upload is **silent** and is really
/// gone.
///
/// Without this, a `drop_incomplete` that logged unconditionally would satisfy
/// the test above and put an error line in an operator's journal on every
/// cancelled upload.
#[test]
fn an_abandoned_upload_that_can_be_removed_is_silent_and_actually_goes() {
let dir = tempfile::tempdir().expect("temporary directory");
let store = FsLfsStore::open_or_init(dir.path().join("repo.git")).expect("store");
let incomplete = dir.path().join("repo.git").join("lfs").join("incomplete");
let (left_behind, events) = capturing(|| {
let mut pending = store.begin_write(&some_oid(), 8).expect("begin_write");
pending.write_all(b"half of a").expect("write");
drop(pending);
std::fs::read_dir(&incomplete)
.expect("read incomplete/")
.flatten()
.count()
});
assert_eq!(left_behind, 0, "a dropped upload left a file behind");
assert!(
events.is_empty(),
"a cancelled upload is the ordinary case and must be silent, but it logged: {events:?}"
);
}