#![warn(clippy::pedantic)]
use std::fs::write;
use anyhow::{Context, ensure};
use camino::{Utf8Path, Utf8PathBuf};
use tempfile::TempDir;
use tracing::info;
use crate::{
Result,
console::Console,
copy_tree::copy_tree,
manifest::{fix_cargo_config, fix_manifest},
options::Options,
workspace::Workspace,
};
#[derive(Debug)]
pub struct BuildDir {
path: Utf8PathBuf,
#[allow(dead_code)]
temp_dir: Option<TempDir>,
}
impl BuildDir {
pub fn for_baseline(
workspace: &Workspace,
options: &Options,
console: &Console,
) -> Result<BuildDir> {
if options.in_place {
BuildDir::in_place(workspace.root())
} else {
BuildDir::copy_from(workspace.root(), options, console)
}
}
pub fn copy_from(source: &Utf8Path, options: &Options, console: &Console) -> Result<BuildDir> {
let name_base = format!("cargo-mutants-{}-", source.file_name().unwrap_or("unnamed"));
let source_abs = source
.canonicalize_utf8()
.context("canonicalize source path")?;
let temp_dir = copy_tree(source, &name_base, options, console)?;
let path: Utf8PathBuf = temp_dir
.path()
.to_owned()
.try_into()
.context("tempdir path to UTF-8")?;
fix_manifest(&path.join("Cargo.toml"), &source_abs)?;
fix_cargo_config(&path, &source_abs)?;
let temp_dir = if options.leak_dirs {
let _ = temp_dir.keep();
info!(?path, "Build directory will be leaked for inspection");
None
} else {
Some(temp_dir)
};
let build_dir = BuildDir { path, temp_dir };
Ok(build_dir)
}
pub fn in_place(source_path: &Utf8Path) -> Result<BuildDir> {
Ok(BuildDir {
temp_dir: None,
path: source_path
.canonicalize_utf8()
.context("canonicalize source path")?,
})
}
pub fn path(&self) -> &Utf8Path {
self.path.as_path()
}
pub fn overwrite_file(&self, relative_path: &Utf8Path, code: &str) -> Result<()> {
let full_path = self.path.join(relative_path);
ensure!(full_path.is_file(), "{full_path:?} is not a file");
write(&full_path, code.as_bytes())
.with_context(|| format!("failed to write code to {full_path:?}"))
}
}
#[cfg(test)]
mod test {
use crate::test_util::copy_of_testdata;
use super::*;
#[test]
fn build_dir_copy_from() {
let tmp = copy_of_testdata("factorial");
let workspace = Workspace::open(tmp.path()).unwrap();
let options = Options {
in_place: false,
gitignore: true,
leak_dirs: false,
..Default::default()
};
let build_dir = BuildDir::copy_from(workspace.root(), &options, &Console::new()).unwrap();
let debug_form = format!("{build_dir:?}");
println!("debug form is {debug_form:?}");
assert!(debug_form.starts_with("BuildDir { path: "));
assert!(build_dir.path().is_dir());
assert!(build_dir.path().join("Cargo.toml").is_file());
assert!(build_dir.path().join("src").is_dir());
}
#[test]
fn for_baseline_in_place() -> Result<()> {
let tmp = copy_of_testdata("factorial");
let workspace = Workspace::open(tmp.path())?;
let options = Options {
in_place: true,
..Default::default()
};
let build_dir = BuildDir::for_baseline(&workspace, &options, &Console::new())?;
assert_eq!(
build_dir.path().canonicalize_utf8()?,
workspace.root().canonicalize_utf8()?
);
assert!(build_dir.temp_dir.is_none());
Ok(())
}
#[test]
fn for_baseline_copied() -> Result<()> {
let tmp = copy_of_testdata("factorial");
let workspace = Workspace::open(tmp.path())?;
let options = Options {
in_place: false,
..Default::default()
};
let build_dir = BuildDir::for_baseline(&workspace, &options, &Console::new())?;
assert!(build_dir.path().is_dir());
assert!(build_dir.path().join("Cargo.toml").is_file());
assert!(build_dir.path().join("src").is_dir());
assert!(build_dir.temp_dir.is_some());
assert_ne!(
build_dir.path().canonicalize_utf8()?,
workspace.root().canonicalize_utf8()?
);
Ok(())
}
#[test]
fn build_dir_in_place() -> Result<()> {
let tmp = copy_of_testdata("factorial");
let workspace = Workspace::open(tmp.path())?;
let build_dir = BuildDir::in_place(workspace.root())?;
assert_eq!(
build_dir.path().canonicalize_utf8()?,
workspace.root().canonicalize_utf8()?
);
Ok(())
}
}