use std::path::Path;
use crate::{cargo_affected, git, init_git_with_initial_commit, replace_in_file};
fn write_workspace(dir: &Path, ws_name: &str) {
std::fs::write(
dir.join("Cargo.toml"),
r#"[workspace]
resolver = "2"
members = ["math", "strings"]
"#,
)
.unwrap();
std::fs::write(dir.join(".gitignore"), "/target\n/Cargo.lock\n").unwrap();
let math = dir.join("math");
std::fs::create_dir_all(math.join("src")).unwrap();
std::fs::create_dir_all(math.join("tests")).unwrap();
std::fs::write(
math.join("Cargo.toml"),
format!(
r#"[package]
name = "{ws_name}_math"
version = "0.1.0"
edition = "2021"
[dependencies]
{ws_name}_strings = {{ path = "../strings" }}
"#
),
)
.unwrap();
std::fs::write(
math.join("src/lib.rs"),
r#"pub mod algo;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_math_add() {
assert_eq!(algo::add(2, 3), 5);
}
}
"#,
)
.unwrap();
std::fs::write(
math.join("src/algo.rs"),
r#"pub fn add(a: i32, b: i32) -> i32 {
a + b
}
"#,
)
.unwrap();
std::fs::write(
math.join("tests/integration.rs"),
format!(
r#"#[test]
fn test_math_integration() {{
assert_eq!({ws_name}_math::algo::add(1, 1), 2);
}}
"#
),
)
.unwrap();
let strings = dir.join("strings");
std::fs::create_dir_all(strings.join("src")).unwrap();
std::fs::write(
strings.join("Cargo.toml"),
format!(
r#"[package]
name = "{ws_name}_strings"
version = "0.1.0"
edition = "2021"
"#
),
)
.unwrap();
std::fs::write(
strings.join("src/lib.rs"),
r#"pub mod fmt;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_strings_greet() {
assert_eq!(fmt::greet("world"), "hello, world");
}
}
"#,
)
.unwrap();
std::fs::write(
strings.join("src/fmt.rs"),
r#"pub fn greet(name: &str) -> String {
format!("hello, {name}")
}
"#,
)
.unwrap();
}
#[test]
fn workspace_collect_finds_all_tests_across_targets() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_workspace(dir, "sample_ws_collect");
init_git_with_initial_commit(dir);
let collect = cargo_affected(dir, &["affected", "collect"]);
assert!(
collect.status.success(),
"collect failed: stderr=\n{}\nstdout=\n{}",
String::from_utf8_lossy(&collect.stderr),
String::from_utf8_lossy(&collect.stdout)
);
let db_path = dir.join("target/affected/coverage.db");
assert!(
db_path.exists(),
"DB should live at workspace root (target/affected/coverage.db)"
);
let conn = rusqlite::Connection::open(&db_path).unwrap();
let test_names: Vec<String> = {
let mut stmt = conn
.prepare("SELECT DISTINCT test_name FROM test_regions")
.unwrap();
stmt.query_map([], |r| r.get(0))
.unwrap()
.map(|r| r.unwrap())
.collect()
};
for expected in ["test_math_add", "test_math_integration", "test_strings_greet"] {
assert!(
test_names.iter().any(|t| t.contains(expected)),
"expected {expected} in DB, got: {test_names:?}"
);
}
}
#[test]
fn editing_lib_in_one_member_does_not_pull_in_unrelated_member() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_workspace(dir, "sample_ws_lib_edit");
init_git_with_initial_commit(dir);
let collect = cargo_affected(dir, &["affected", "collect"]);
assert!(
collect.status.success(),
"collect failed: {}",
String::from_utf8_lossy(&collect.stderr)
);
replace_in_file(
&dir.join("math/src/lib.rs"),
"pub mod algo;",
"// edit at the top of the crate root\npub mod algo;",
);
let status = cargo_affected(dir, &["affected", "status", "-v"]);
assert!(
status.status.success(),
"status failed: {}",
String::from_utf8_lossy(&status.stderr)
);
let stdout = String::from_utf8_lossy(&status.stdout);
assert!(
stdout.contains("test_math_add"),
"edit in math/src/lib.rs should select test_math_add, got:\n{stdout}"
);
assert!(
stdout.contains("test_math_integration"),
"edit in math/src/lib.rs should select test_math_integration \
(within-package structural guarantee), got:\n{stdout}"
);
assert!(
!stdout.contains("test_strings_greet"),
"edit in math (which strings does not depend on) must NOT pull in \
strings's tests, got:\n{stdout}"
);
git(dir, &["checkout", "--", "math/src/lib.rs"]);
}
#[test]
fn editing_integration_test_does_not_pull_in_lib_unit_tests() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_workspace(dir, "sample_ws_int_edit");
init_git_with_initial_commit(dir);
let collect = cargo_affected(dir, &["affected", "collect"]);
assert!(
collect.status.success(),
"collect failed: {}",
String::from_utf8_lossy(&collect.stderr)
);
replace_in_file(
&dir.join("math/tests/integration.rs"),
"add(1, 1), 2",
"add(1, 1), 2 /* edited */",
);
let status = cargo_affected(dir, &["affected", "status", "-v"]);
assert!(
status.status.success(),
"status failed: {}",
String::from_utf8_lossy(&status.stderr)
);
let stdout = String::from_utf8_lossy(&status.stdout);
assert!(
stdout.contains("test_math_integration"),
"edit in tests/integration.rs should select test_math_integration, got:\n{stdout}"
);
assert!(
!stdout.contains("test_math_add"),
"edit in tests/integration.rs must NOT pull in test_math_add \
(per-target narrowing within a package), got:\n{stdout}"
);
assert!(
!stdout.contains("test_strings_greet"),
"edit in math's integration test must NOT pull in strings's tests, got:\n{stdout}"
);
git(dir, &["checkout", "--", "math/tests/integration.rs"]);
}
#[test]
fn editing_dep_lib_pulls_in_dependent_tests() {
let tmp = tempfile::tempdir().unwrap();
let dir = tmp.path();
write_workspace(dir, "sample_ws_dep_edit");
init_git_with_initial_commit(dir);
let collect = cargo_affected(dir, &["affected", "collect"]);
assert!(
collect.status.success(),
"collect failed: {}",
String::from_utf8_lossy(&collect.stderr)
);
replace_in_file(
&dir.join("strings/src/lib.rs"),
"pub mod fmt;",
"// edit at the top of strings's crate root\npub mod fmt;",
);
let status = cargo_affected(dir, &["affected", "status", "-v"]);
assert!(
status.status.success(),
"status failed: {}",
String::from_utf8_lossy(&status.stderr)
);
let stdout = String::from_utf8_lossy(&status.stdout);
assert!(
stdout.contains("test_strings_greet"),
"edit in strings/src/lib.rs should select test_strings_greet, got:\n{stdout}"
);
assert!(
stdout.contains("test_math_add"),
"edit in strings/src/lib.rs should pull in test_math_add \
(math depends on strings via path dep), got:\n{stdout}"
);
assert!(
stdout.contains("test_math_integration"),
"edit in strings/src/lib.rs should pull in test_math_integration, got:\n{stdout}"
);
git(dir, &["checkout", "--", "strings/src/lib.rs"]);
}