Temporary directory but _not_ deleted on panic?

This is kind of a weird ask, but I'm wondering if there exists a tempfile-like crate that creates temporary directories that are automatically deleted if you exit a scope normally, but not if you panic. This would allow me to test filesystem operations and have each test's scratch directory automatically stick around for inspection when the test fails.

You might be able to newtype this pretty easily -- hold an Option<TypicalTempDir>, check for panics on drop, take and forget the field if panicking.

3 Likes
2 Likes

Another option would be to wrap the TempDir in a ManuallyDrop, which will prevent it from being automatically dropped, and then call ManuallyDrop::into_inner at the end of the scope to actually drop it when you want it to.

2 Likes

the scopeguard crate is for this exact purpose. there's guard, guard_on_success, guard_on_unwind to guard a value, and there's also defer, defer_on_success, defer_on_unwind that simply defers some code if you don't have a value to guard.

// guards/wraps a `tempdir`, the wrapper implements `Deref`
// if the custom `drop_fn` is skipped, the wrapped value is dropped normally
let td = guard_on_unwind(tempdir(), |td| {
    mem::forget(td);
});
println!("path: {}", td.path().display());

that's roughly how ScopeGuard is implemented, with some added details like the Strategy generic parameter.

6 Likes

Thanks! I'll need to experiment a little but that looks like exactly what I need.

I wound up building a RAII "test scratch directory" object by hand, without using either tempdir or scope_guard, because of various other requirements. I'm going to document its Drop implementation here just because it was surprising to me that std::thread::panicking lives in std::thread rather than std::panic. I mean, I guess it does report a property of the current thread, but that feels like an exposed implementation detail.

impl Drop for TestScratchDir {
    fn drop(&mut self) {
        if !std::thread::panicking()
            && std::env::var_os("TEST_KEEP_SCRATCH").is_none()
        {
            let _ = fs::remove_dir_all(&self.path);
        }
    }
}