#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc = include_str!("../README.md")]
#![allow(renamed_and_removed_lints)] #![allow(unknown_lints)] #![warn(missing_docs)]
#![warn(noop_method_call)]
#![warn(unreachable_pub)]
#![warn(clippy::all)]
#![deny(clippy::await_holding_lock)]
#![deny(clippy::cargo_common_metadata)]
#![deny(clippy::cast_lossless)]
#![deny(clippy::checked_conversions)]
#![warn(clippy::cognitive_complexity)]
#![deny(clippy::debug_assert_with_mut_call)]
#![deny(clippy::exhaustive_enums)]
#![deny(clippy::exhaustive_structs)]
#![deny(clippy::expl_impl_clone_on_copy)]
#![deny(clippy::fallible_impl_from)]
#![deny(clippy::implicit_clone)]
#![deny(clippy::large_stack_arrays)]
#![warn(clippy::manual_ok_or)]
#![deny(clippy::missing_docs_in_private_items)]
#![warn(clippy::needless_borrow)]
#![warn(clippy::needless_pass_by_value)]
#![warn(clippy::option_option)]
#![deny(clippy::print_stderr)]
#![deny(clippy::print_stdout)]
#![warn(clippy::rc_buffer)]
#![deny(clippy::ref_option_ref)]
#![warn(clippy::semicolon_if_nothing_returned)]
#![warn(clippy::trait_duplication_in_bounds)]
#![deny(clippy::unchecked_time_subtraction)]
#![deny(clippy::unnecessary_wraps)]
#![warn(clippy::unseparated_literal_suffix)]
#![deny(clippy::unwrap_used)]
#![deny(clippy::mod_module_files)]
#![allow(clippy::let_unit_value)] #![allow(clippy::uninlined_format_args)]
#![allow(clippy::significant_drop_in_scrutinee)] #![allow(clippy::result_large_err)] #![allow(clippy::needless_raw_string_hashes)] #![allow(clippy::needless_lifetimes)] #![allow(mismatched_lifetime_syntaxes)] #![allow(clippy::collapsible_if)] #![deny(clippy::unused_async)]
#![allow(clippy::print_stdout)]
use std::env::{self, VarError};
use std::fs;
use std::io::{self, ErrorKind};
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use anyhow::{Context as _, anyhow};
use derive_more::{Deref, DerefMut};
use educe::Educe;
const RETAIN_VAR: &str = "TEST_TEMP_RETAIN";
#[derive(Debug)]
#[non_exhaustive]
pub enum TestTempDir {
Ephemeral(tempfile::TempDir),
Persistent(PathBuf),
}
#[derive(Clone, Copy, Deref, DerefMut, Educe)]
#[educe(Debug(bound))]
pub struct TestTempDirGuard<'d, T> {
#[deref]
#[deref_mut]
thing: T,
#[educe(Debug(ignore))]
tempdir: PhantomData<&'d ()>,
}
impl TestTempDir {
pub fn from_module_path_and_thread(mod_path: &str) -> TestTempDir {
let path = (|| {
let (crate_, m_mod) = mod_path
.split_once("::")
.ok_or_else(|| anyhow!("module path {:?} doesn't contain `::`", &mod_path))?;
let thread = std::thread::current();
let thread = thread.name().context("get current thread name")?;
let (t_mod, fn_) = thread
.rsplit_once("::")
.ok_or_else(|| anyhow!("current thread name {:?} doesn't contain `::`", &thread))?;
if m_mod != t_mod {
return Err(anyhow!(
"module path {:?} implies module name {:?} but thread name {:?} implies module name {:?}",
mod_path, m_mod, thread, t_mod
));
}
Ok::<_, anyhow::Error>(format!("{crate_}::{m_mod}::{fn_}"))
})()
.expect("unable to calculate complete test function path");
Self::from_complete_item_path(&path)
}
pub fn from_complete_item_path(item_path: &str) -> Self {
let subdir = item_path;
#[cfg(target_os = "windows")]
let subdir = subdir.replace("::", ",");
#[allow(clippy::needless_borrow)] Self::from_stable_unique_subdir(&subdir)
}
pub fn from_stable_unique_subdir(subdir: &str) -> Self {
let retain = env::var(RETAIN_VAR);
let retain = match &retain {
Ok(y) => y,
Err(VarError::NotPresent) => "0",
Err(VarError::NotUnicode(_)) => panic!("{} not unicode", RETAIN_VAR),
};
let target: PathBuf = if retain == "0" {
println!("test {subdir}: {RETAIN_VAR} not enabled, using ephemeral temp dir");
let dir = tempfile::tempdir().expect("failed to create temp dir");
return TestTempDir::Ephemeral(dir);
} else if retain.starts_with('.') || retain.starts_with('/') {
retain.into()
} else if retain == "1" {
let target = env::var_os("CARGO_TARGET_DIR").unwrap_or_else(|| "target".into());
let mut dir = PathBuf::from(target);
dir.push("test");
dir
} else {
panic!("invalid value for {}: {:?}", RETAIN_VAR, retain)
};
let dir = {
let mut dir = target;
dir.push(subdir);
dir
};
let dir_display_lossy;
#[allow(clippy::disallowed_methods)]
{
dir_display_lossy = dir.display();
}
println!("test {subdir}, temp dir is {}", dir_display_lossy);
match fs::remove_dir_all(&dir) {
Err(e) if e.kind() == io::ErrorKind::NotFound => Ok(()),
other => other,
}
.expect("pre-remove temp dir");
fs::create_dir_all(&dir).expect("create temp dir");
TestTempDir::Persistent(dir)
}
pub fn as_path_untracked(&self) -> &Path {
match self {
TestTempDir::Ephemeral(t) => t.as_ref(),
TestTempDir::Persistent(t) => t.as_ref(),
}
}
pub fn subdir_untracked(&self, subdir: &str) -> PathBuf {
let mut r = self.as_path_untracked().to_owned();
r.push(subdir);
r
}
#[allow(clippy::needless_lifetimes)] pub fn used_by<'d, T>(&'d self, f: impl FnOnce(&Path) -> T) -> TestTempDirGuard<'d, T> {
let thing = f(self.as_path_untracked());
TestTempDirGuard::with_path(thing, self.as_path_untracked())
}
pub fn subdir_used_by<'d, T>(
&'d self,
subdir: &str,
f: impl FnOnce(PathBuf) -> T,
) -> TestTempDirGuard<'d, T> {
self.used_by(|dir| {
let dir = dir.join(subdir);
match fs::create_dir(&dir) {
Err(e) if e.kind() == ErrorKind::AlreadyExists => Ok(()),
other => other,
}
.expect("create subdir");
f(dir)
})
}
}
impl<'d, T> TestTempDirGuard<'d, T> {
pub fn into_untracked(self) -> T {
self.thing
}
pub fn with_path(thing: T, _path: &'d Path) -> Self {
Self::new_untracked(thing)
}
pub fn new_untracked(thing: T) -> Self {
Self {
thing,
tempdir: PhantomData,
}
}
}
#[macro_export]
macro_rules! test_temp_dir { {} => {
$crate::TestTempDir::from_module_path_and_thread(module_path!())
} }