use mitts::adapter::rust::inputs::is_immutable_package_dir;
use mitts::cli::coverage_tools::resolve_llvm_tool;
use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
fn mark(dir: &Path) {
fs::create_dir_all(dir).unwrap();
fs::write(dir.join(".mitts-ok"), "").unwrap();
fs::write(dir.join("Cargo.toml"), "[package]\nname = \"x\"\n").unwrap();
}
#[test]
fn a_crate_at_the_root_of_a_git_checkout_is_immutable() {
let cache = TempDir::new().unwrap();
let revision = cache.path().join("git/checkouts/repo-1a2b3c/0123abc");
mark(&revision);
assert!(is_immutable_package_dir(&revision, false));
}
#[test]
fn a_crate_in_a_subdirectory_of_a_git_checkout_is_immutable() {
let cache = TempDir::new().unwrap();
let revision = cache.path().join("git/checkouts/repo-1a2b3c/0123abc");
mark(&revision);
let member = revision.join("crates/member");
fs::create_dir_all(&member).unwrap();
assert!(is_immutable_package_dir(&member, false));
}
#[test]
fn only_a_marked_git_checkout_counts() {
let cache = TempDir::new().unwrap();
let unmarked = cache.path().join("git/checkouts/repo-1a2b3c/0123abc");
fs::create_dir_all(&unmarked).unwrap();
assert!(!is_immutable_package_dir(&unmarked, false), "no marker");
let elsewhere = cache.path().join("projects/repo-1a2b3c/0123abc");
mark(&elsewhere);
assert!(
!is_immutable_package_dir(&elsewhere, false),
"a marker outside git/checkouts does not make a package immutable"
);
let wrong_depth = cache.path().join("git/checkouts/0123abc");
mark(&wrong_depth);
assert!(!is_immutable_package_dir(&wrong_depth, false));
}
#[test]
fn a_workspace_package_is_never_immutable() {
let cache = TempDir::new().unwrap();
let revision = cache.path().join("git/checkouts/repo-1a2b3c/0123abc");
mark(&revision);
assert!(!is_immutable_package_dir(&revision, true));
}
fn git(dir: &Path, args: &[&str]) {
let status = Command::new("git")
.current_dir(dir)
.args(["-c", "user.email=t@example.com", "-c", "user.name=t"])
.args(args)
.status()
.expect("run git");
assert!(status.success(), "git {args:?}");
}
fn reported_files(symlinked_cache: bool) -> Option<Vec<String>> {
let tools =
resolve_llvm_tool("llvm-profdata").is_some() && resolve_llvm_tool("llvm-cov").is_some();
let has_git = Command::new("git").arg("--version").output().is_ok();
if !tools || !has_git {
eprintln!("skipped: llvm-tools or git are not installed");
return None;
}
let dir = TempDir::new().unwrap();
let cache = dir.path().join("cache");
if symlinked_cache {
#[cfg(unix)]
{
fs::create_dir_all(dir.path().join("real-cache")).unwrap();
std::os::unix::fs::symlink(dir.path().join("real-cache"), &cache).unwrap();
}
}
let dep = dir.path().join("dep");
let app = dir.path().join("app");
for path in [dep.join("src"), app.join("src")] {
fs::create_dir_all(path).unwrap();
}
fs::write(
dep.join("Cargo.toml"),
"[package]\nname = \"gitdep\"\nversion = \"0.2.0\"\nedition = \"2021\"\n",
)
.unwrap();
fs::write(
dep.join("src/lib.rs"),
"pub fn helper() -> u32 { 1 }\npub fn dead_one() -> u32 { 8 }\npub fn dead_two() -> u32 { 9 }\n",
)
.unwrap();
git(&dep, &["init", "-q", "."]);
git(&dep, &["add", "-A"]);
git(&dep, &["commit", "-q", "-m", "init"]);
fs::write(
app.join("Cargo.toml"),
format!(
"[package]\nname = \"app\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n\
[dependencies]\ngitdep = {{ git = \"file://{}\" }}\n",
dep.display()
),
)
.unwrap();
fs::write(
app.join("src/lib.rs"),
"pub fn covered() -> u32 { gitdep::helper() + 1 }\n\n\
#[cfg(test)]\nmod tests {\n #[test]\n fn covers() { assert_eq!(super::covered(), 2); }\n}\n",
)
.unwrap();
let lcov = dir.path().join("out/report.lcov");
let output = Command::new(env!("CARGO_BIN_EXE_mitts"))
.current_dir(&app)
.args(["llvm-cov", "--lcov", "--output-path"])
.arg(&lcov)
.arg("--cache-dir")
.arg(&cache)
.env("CARGO_HOME", dir.path().join("cargo-home"))
.output()
.unwrap();
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
let report = fs::read_to_string(&lcov).unwrap();
Some(
report
.lines()
.filter_map(|line| line.strip_prefix("SF:"))
.map(str::to_owned)
.collect(),
)
}
#[test]
fn coverage_leaves_a_git_dependency_out_of_the_report() {
if let Some(files) = reported_files(false) {
assert_eq!(
files,
["src/lib.rs"],
"only the measured crate is reported; the git dependency is left out"
);
}
}
#[cfg(unix)]
#[test]
fn coverage_leaves_a_git_dependency_out_when_the_cache_path_is_a_symlink() {
if let Some(files) = reported_files(true) {
assert_eq!(
files,
["src/lib.rs"],
"the cache is excluded under the spelling the profile records, not only the canonical one"
);
}
}