use core::time::Duration;
use std::sync::Arc;
use camino::{Utf8Path, Utf8PathBuf};
use serde_json::Value;
use super::census::{Census, CensusWork};
use super::config::Config;
use super::memory::MemoryPolicy;
use crate::discover::{Glob, Plan};
use crate::estimate::Workload;
use crate::model::{Mutant, Outcome};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TestBinary {
pub path: Utf8PathBuf,
pub package: String,
pub package_id: String,
pub target: String,
pub manifest_dir: Utf8PathBuf,
pub baseline: Duration,
pub tests: Option<usize>,
pub budget: Option<Duration>,
pub peak: Option<u64>,
pub memory: Option<u64>,
}
impl TestBinary {
#[must_use]
pub fn budget_for(&self, override_multiplier: Option<f64>, floor: Duration) -> Option<Duration> {
let budget = self.budget?;
Some(override_multiplier.map_or(budget, |multiplier| self.baseline.mul_f64(multiplier).max(floor)))
}
}
pub(super) fn apportion(binaries: &mut [TestBinary], multiplier: f64, floor: Duration, calibrated: bool) {
for binary in binaries.iter_mut() {
binary.budget = calibrated.then(|| binary.baseline.mul_f64(multiplier).max(floor));
}
}
pub(super) fn calibrate(binaries: &mut [TestBinary], config: &Config, memory: &MemoryPolicy) {
binaries.sort_by_key(|binary| binary.baseline);
apportion(binaries, config.test_timeout_multiplier, config.timeout_floor, config.baseline);
bound(binaries, memory, config.baseline);
}
pub(super) fn bound(binaries: &mut [TestBinary], policy: &MemoryPolicy, calibrated: bool) {
for binary in binaries.iter_mut() {
binary.memory = policy.ceiling(binary.peak, calibrated);
}
}
pub(super) const TEST_THREADS_VAR: &str = "RUST_TEST_THREADS";
pub(super) fn harness_threads(jobs: usize, cores: usize, inherited: Option<&str>) -> Option<usize> {
if inherited.is_some_and(|value| !value.trim().is_empty()) {
return None;
}
Some((cores / jobs.max(1)).max(1))
}
pub(super) fn test_binaries(stdout: &str) -> Vec<TestBinary> {
let mut binaries = Vec::new();
for line in stdout.lines() {
let Ok(message) = serde_json::from_str::<Value>(line) else {
continue;
};
if message.get("reason").and_then(Value::as_str) != Some("compiler-artifact") {
continue;
}
let is_test = message
.get("profile")
.and_then(|profile| profile.get("test"))
.and_then(Value::as_bool)
.unwrap_or(false);
if !is_test {
continue;
}
if let Some(path) = message.get("executable").and_then(Value::as_str) {
let package_id = message.get("package_id").and_then(Value::as_str).unwrap_or_default().to_owned();
let package = package_name(&package_id);
let target = message
.get("target")
.and_then(|target| target.get("name"))
.and_then(Value::as_str)
.unwrap_or_default()
.to_owned();
let manifest_dir = message
.get("manifest_path")
.and_then(Value::as_str)
.map(Utf8PathBuf::from)
.and_then(|manifest| manifest.parent().map(Utf8Path::to_path_buf))
.unwrap_or_default();
binaries.push(TestBinary {
path: Utf8PathBuf::from(path),
package,
package_id,
target,
manifest_dir,
baseline: Duration::ZERO,
budget: None,
tests: None,
peak: None,
memory: None,
});
}
}
binaries.sort_by(|left, right| left.path.cmp(&right.path));
binaries.dedup_by(|left, right| left.path == right.path);
binaries
}
pub(super) fn restrict(binaries: &mut Vec<TestBinary>, include: &[String], exclude: &[String]) {
if include.is_empty() && exclude.is_empty() {
return;
}
let patterns = TargetPatterns::new(include, exclude);
binaries.retain(|binary| patterns.admits(&binary.target));
}
pub(super) fn admits_target(name: &str, include: &[String], exclude: &[String]) -> bool {
TargetPatterns::new(include, exclude).admits(name)
}
pub(super) fn unmatched_test<'args>(tests: &[String], include: &'args [String], exclude: &'args [String]) -> Option<&'args str> {
TargetPatterns::new(include, exclude).unmatched(tests)
}
struct TargetPatterns<'args> {
include: Vec<(&'args str, Glob)>,
exclude: Vec<(&'args str, Glob)>,
}
impl<'args> TargetPatterns<'args> {
fn new(include: &'args [String], exclude: &'args [String]) -> Self {
let compile = |patterns: &'args [String]| patterns.iter().map(|pattern| (pattern.as_str(), Glob::new(pattern))).collect();
Self {
include: compile(include),
exclude: compile(exclude),
}
}
fn admits(&self, name: &str) -> bool {
!self.exclude.iter().any(|(_pattern, compiled)| compiled.matches(name))
&& (self.include.is_empty() || self.include.iter().any(|(_pattern, compiled)| compiled.matches(name)))
}
fn unmatched(&self, tests: &[String]) -> Option<&'args str> {
self.include
.iter()
.chain(&self.exclude)
.find(|(_pattern, compiled)| !tests.iter().any(|name| compiled.matches(name)))
.map(|(pattern, _compiled)| *pattern)
}
}
pub(super) fn reaches(binary: &TestBinary, package: &str, plan: &Plan, scope: &TestScope<'_>) -> bool {
if binary.tests == Some(0) {
return false;
}
if !scope.admits(&binary.package) {
return false;
}
if binary.package.is_empty() || package.is_empty() {
return true;
}
if scope.package_local && binary.package != package {
return false;
}
plan.reach.get(&binary.package).is_none_or(|reachable| reachable.contains(package))
}
pub(super) fn order_reachable(binaries: &mut [&TestBinary], mutant_package: &str) {
binaries.sort_by(|left, right| {
let tier = |binary: &TestBinary| u8::from(binary.package != mutant_package);
tier(left)
.cmp(&tier(right))
.then_with(|| left.baseline.cmp(&right.baseline))
.then_with(|| left.package.cmp(&right.package))
.then_with(|| left.target.cmp(&right.target))
.then_with(|| left.path.cmp(&right.path))
});
}
#[derive(Debug, Default)]
pub(super) struct Reachability<'binaries> {
by_package: crate::HashMap<Arc<str>, Vec<&'binaries TestBinary>>,
}
impl<'binaries> Reachability<'binaries> {
pub(super) fn build(plan: &Plan, binaries: &'binaries [TestBinary], scope: &TestScope<'_>) -> Self {
let mut by_package: crate::HashMap<Arc<str>, Vec<&TestBinary>> = crate::HashMap::default();
for mutant in plan
.mutants
.iter()
.filter(|mutant| mutant.ordinal > 0 && mutant.outcome == Outcome::Pending)
{
if by_package.contains_key(&mutant.package) {
continue;
}
let mut found: Vec<&TestBinary> = binaries
.iter()
.filter(|binary| reaches(binary, &mutant.package, plan, scope))
.collect();
order_reachable(&mut found, &mutant.package);
let _fresh = by_package.insert(Arc::clone(&mutant.package), found);
}
Self { by_package }
}
pub(super) fn reachable(&self, package: &str) -> Option<&[&'binaries TestBinary]> {
self.by_package.get(package).map(Vec::as_slice)
}
#[cfg(test)]
pub(super) fn len(&self) -> usize {
self.by_package.len()
}
}
pub(super) fn oracle_packages(selected: &[String], config: &Config) -> Vec<String> {
if !config.test_packages.is_empty() || config.test_workspace {
return config.test_packages.clone();
}
selected.to_vec()
}
pub(super) fn build_packages(plan: &Plan, scope: &TestScope<'_>) -> Option<Vec<String>> {
let mutated: crate::HashSet<&str> = plan
.mutants
.iter()
.filter(|mutant| mutant.ordinal > 0 && mutant.outcome == Outcome::Pending)
.map(|mutant| &*mutant.package)
.collect();
reaching_packages(&plan.reach, &mutated, scope)
}
pub(super) fn reaching_packages(
reach: &crate::HashMap<String, crate::HashSet<String>>,
mutated: &crate::HashSet<&str>,
scope: &TestScope<'_>,
) -> Option<Vec<String>> {
if reach.is_empty() {
return None;
}
let mut wanted: Vec<String> = reach
.iter()
.filter(|(package, reachable)| {
if !scope.admits(package) {
return mutated.contains(package.as_str());
}
if scope.package_local {
return mutated.contains(package.as_str());
}
scope.whole_workspace || reachable.iter().any(|name| mutated.contains(name.as_str()))
})
.map(|(package, _)| package.clone())
.collect();
if wanted.len() >= reach.len() {
return None;
}
wanted.sort();
Some(wanted)
}
pub(super) fn workload(mutants: &[Mutant], reach: &Reachability<'_>, census: Option<&Census>) -> Workload {
let mut total = Workload::default();
for mutant in mutants
.iter()
.filter(|mutant| mutant.ordinal > 0 && mutant.outcome == Outcome::Pending)
{
let reachable = reach
.reachable(&mutant.package)
.expect("the shared reachability index was built from these same pending mutants");
let mut suite = Duration::ZERO;
let mut worst = Duration::ZERO;
let mut running = 0_usize;
for binary in reachable {
match census.map_or(CensusWork::Whole, |census| census.work(binary, mutant.ordinal)) {
CensusWork::Whole => suite += binary.baseline,
CensusWork::Uncovered => continue,
CensusWork::Selected(duration) => suite += duration,
CensusWork::Hinted(_duration) => suite += binary.baseline,
}
worst += binary.budget.unwrap_or_default();
running = running.saturating_add(1);
}
let single = u32::try_from(running).map_or(worst, |count| worst.checked_div(count).unwrap_or(worst));
total.suite += suite;
total.budget += worst;
total.single += single;
}
total
}
#[derive(Debug, Clone, Copy)]
pub(super) struct TestScope<'names> {
pub(super) packages: &'names [String],
pub(super) package_local: bool,
pub(super) whole_workspace: bool,
}
impl TestScope<'_> {
pub(super) fn admits(&self, package: &str) -> bool {
self.packages.is_empty() || self.packages.iter().any(|wanted| wanted == package)
}
}
fn package_name(id: &str) -> String {
if let Some((locator, fragment)) = id.rsplit_once('#') {
return fragment.split_once('@').map_or_else(
|| {
if is_version(fragment) {
locator.rsplit('/').next().unwrap_or_default().to_owned()
} else {
fragment.to_owned()
}
},
|(name, _version)| name.to_owned(),
);
}
id.split_whitespace().next().unwrap_or_default().to_owned()
}
fn is_version(fragment: &str) -> bool {
let release = fragment.split(['-', '+']).next().unwrap_or(fragment);
!release.is_empty()
&& release.starts_with(|character: char| character.is_ascii_digit())
&& release.chars().all(|character| character.is_ascii_digit() || character == '.')
}
#[cfg(test)]
mod tests {
use super::*;
const ANY: TestScope<'static> = TestScope {
packages: &[],
package_local: false,
whole_workspace: false,
};
#[test]
fn test_binaries_are_read_from_cargo_json() {
let stdout = concat!(
r#"{"reason":"compiler-artifact","profile":{"test":false},"executable":null}"#,
"\n",
r#"{"reason":"compiler-artifact","profile":{"test":true},"executable":"/tmp/unit"}"#,
"\n",
r#"{"reason":"compiler-message","message":{"level":"error"}}"#,
"\n",
"not json at all",
"\n",
r#"{"reason":"compiler-artifact","profile":{"test":true},"executable":"/tmp/cli"}"#,
"\n",
);
let paths: Vec<Utf8PathBuf> = test_binaries(stdout).into_iter().map(|binary| binary.path).collect();
assert_eq!(paths, vec![Utf8PathBuf::from("/tmp/cli"), Utf8PathBuf::from("/tmp/unit")]);
}
#[test]
fn an_artifact_with_no_executable_string_contributes_no_binary() {
let stdout = r#"{"reason":"compiler-artifact","profile":{"test":true},"executable":null}"#;
assert!(test_binaries(stdout).is_empty());
}
#[test]
fn apportioning_across_no_binaries_does_nothing() {
let mut binaries: Vec<TestBinary> = Vec::new();
apportion(&mut binaries, 1.2, Duration::from_secs(1), true);
assert!(binaries.is_empty());
}
#[test]
fn a_binary_the_graph_says_nothing_about_is_admitted() {
let scope = TestScope {
packages: &[],
package_local: false,
whole_workspace: true,
};
assert!(reaches(&binary("unrelated"), "subject", &plan_reaching(&[]), &scope));
}
#[test]
fn a_whole_workspace_scope_still_does_not_reach_what_a_binary_cannot_link() {
let scope = TestScope {
packages: &[],
package_local: false,
whole_workspace: true,
};
let plan = plan_reaching(&[("harness", &["harness"])]);
assert!(!reaches(&binary("harness"), "subject", &plan, &scope));
assert!(reaches(&binary("harness"), "harness", &plan, &scope));
}
#[test]
fn a_binary_that_announced_no_tests_reaches_nothing() {
let scope = TestScope {
packages: &[],
package_local: false,
whole_workspace: true,
};
let empty = TestBinary {
tests: Some(0),
..binary("subject")
};
assert!(!reaches(&empty, "subject", &plan_reaching(&[]), &scope));
}
#[test]
fn a_binary_whose_tests_were_never_counted_still_reaches_what_it_links() {
let scope = TestScope {
packages: &[],
package_local: false,
whole_workspace: true,
};
let uncounted = TestBinary {
tests: None,
..binary("subject")
};
assert!(reaches(&uncounted, "subject", &plan_reaching(&[]), &scope));
}
#[test]
fn a_binary_that_announced_tests_reaches_what_it_links() {
let scope = TestScope {
packages: &[],
package_local: false,
whole_workspace: true,
};
let populated = TestBinary {
tests: Some(7),
..binary("subject")
};
assert!(reaches(&populated, "subject", &plan_reaching(&[]), &scope));
}
#[test]
fn the_harness_width_divides_the_machine_between_the_workers() {
assert_eq!(harness_threads(4, 16, None), Some(4));
assert_eq!(harness_threads(1, 16, None), Some(16));
assert_eq!(harness_threads(16, 16, None), Some(1));
}
#[test]
fn more_workers_than_cores_still_get_a_thread_each() {
assert_eq!(harness_threads(64, 8, None), Some(1));
assert_eq!(harness_threads(0, 8, None), Some(8));
}
#[test]
fn a_harness_width_the_caller_chose_is_left_alone() {
assert_eq!(harness_threads(4, 16, Some("2")), None);
assert_eq!(harness_threads(4, 16, Some(" ")), Some(4));
}
#[test]
fn a_binary_carries_the_directory_of_its_packages_manifest() {
let line = r#"{"reason":"compiler-artifact","package_id":"path+file:///w/crates/subject#0.1.0","manifest_path":"/w/crates/subject/Cargo.toml","profile":{"test":true},"executable":"/w/target/debug/deps/subject-abc"}"#;
let found = test_binaries(line);
assert_eq!(found.len(), 1);
assert_eq!(found[0].manifest_dir, "/w/crates/subject");
}
#[test]
fn a_binary_cargo_did_not_locate_has_no_manifest_directory() {
let line = r#"{"reason":"compiler-artifact","package_id":"path+file:///w#0.1.0","profile":{"test":true},"executable":"/w/target/debug/deps/subject-abc"}"#;
let found = test_binaries(line);
assert_eq!(found.len(), 1);
assert_eq!(found[0].manifest_dir, "");
}
fn binary(package: &str) -> TestBinary {
TestBinary {
package: package.to_owned(),
..crate::testing::test_binary("/tmp/t")
}
}
fn target(name: &str) -> TestBinary {
TestBinary {
target: name.to_owned(),
..crate::testing::test_binary(&format!("/tmp/{name}"))
}
}
fn names(binaries: &[TestBinary]) -> Vec<&str> {
binaries.iter().map(|binary| binary.target.as_str()).collect()
}
#[test]
fn the_target_name_is_kept_from_cargo_json() {
let stdout = concat!(
r#"{"reason":"compiler-artifact","profile":{"test":true},"target":{"name":"conformance_xsd"},"executable":"/tmp/c"}"#,
"\n"
);
let found = test_binaries(stdout);
assert_eq!(found.len(), 1);
assert_eq!(found[0].target, "conformance_xsd");
}
#[test]
fn a_binary_with_no_target_name_is_still_kept() {
let stdout = concat!(
r#"{"reason":"compiler-artifact","profile":{"test":true},"executable":"/tmp/c"}"#,
"\n"
);
let found = test_binaries(stdout);
assert_eq!(found.len(), 1);
assert_eq!(found[0].target, "");
}
#[test]
fn no_patterns_leave_every_binary_in_place() {
let mut binaries = vec![target("unit"), target("conformance_xsd")];
restrict(&mut binaries, &[], &[]);
assert_eq!(names(&binaries), vec!["unit", "conformance_xsd"]);
}
#[test]
fn an_exclusion_glob_removes_the_targets_it_names() {
let mut binaries = vec![target("unit"), target("conformance_xsd"), target("conformance_xpath")];
restrict(&mut binaries, &[], &["conformance_*".to_owned()]);
assert_eq!(names(&binaries), vec!["unit"]);
}
#[test]
fn an_inclusion_glob_keeps_only_the_targets_it_names() {
let mut binaries = vec![target("unit"), target("integration"), target("conformance_xsd")];
restrict(&mut binaries, &["unit".to_owned(), "integration".to_owned()], &[]);
assert_eq!(names(&binaries), vec!["unit", "integration"]);
}
#[test]
fn an_exclusion_beats_an_inclusion_that_also_matches() {
let mut binaries = vec![target("unit"), target("conformance_xsd")];
restrict(&mut binaries, &["*".to_owned()], &["conformance_*".to_owned()]);
assert_eq!(names(&binaries), vec!["unit"]);
}
#[test]
fn a_pattern_matching_a_declared_target_is_not_reported_as_unmatched() {
let tests = vec!["unit".to_owned(), "conformance_xsd".to_owned()];
assert_eq!(unmatched_test(&tests, &[], &["conformance_*".to_owned()]), None);
}
#[test]
fn a_pattern_matching_nothing_is_reported() {
let tests = vec!["unit".to_owned(), "conformance_xsd".to_owned()];
let exclude = vec!["conformance_*".to_owned(), "confrmance_xpath".to_owned()];
assert_eq!(unmatched_test(&tests, &[], &exclude), Some("confrmance_xpath"));
}
#[test]
fn an_unmatched_inclusion_is_reported_too() {
let tests = vec!["unit".to_owned()];
assert_eq!(unmatched_test(&tests, &["untis".to_owned()], &[]), Some("untis"));
}
fn plan_reaching(edges: &[(&str, &[&str])]) -> Plan {
let mut reach = crate::HashMap::default();
for (from, to) in edges {
let _previous = reach.insert((*from).to_owned(), to.iter().map(|name| (*name).to_owned()).collect());
}
Plan {
skipped: Vec::new(),
digests: crate::HashMap::default(),
root: Utf8PathBuf::from("/w"),
files: Vec::new(),
mutants: Vec::new(),
suppressed: 0,
idle: Vec::new(),
sharded_out: 0,
settled_out: 0,
reach,
specs: crate::HashMap::default(),
}
}
fn plan_mutating(edges: &[(&str, &[&str])], packages: &[&str]) -> Plan {
let mut plan = plan_reaching(edges);
plan.mutants = packages
.iter()
.enumerate()
.map(|(index, package)| Mutant {
id: (*package).to_owned().into(),
ordinal: u32::try_from(index).unwrap_or(0).saturating_add(1),
file: (Utf8PathBuf::from("src/lib.rs")).into(),
package: ((*package).to_owned()).into(),
span: 0..1,
line: 1,
end_line: 1,
column: 1,
mutator: ("arith.add_to_sub".to_owned()).into(),
item_path: ("f".to_owned()).into(),
trait_impl: None,
occurrence: 0,
replacement_index: 0,
original: "a + b".to_owned().into(),
replacement: "a - b".to_owned().into(),
shape: crate::ops::collect::Shape::Expr,
outcome: Outcome::Pending,
suppression: None,
expectation: None,
test_timeout_multiplier: None,
elapsed_ms: 0,
killed_by: None,
note: None,
})
.collect();
plan
}
fn graph() -> Vec<(&'static str, &'static [&'static str])> {
vec![
("app", &["app", "core"] as &[&str]),
("tool", &["tool", "core"]),
("core", &["core"]),
("aside", &["aside"]),
]
}
#[test]
fn reachability_memoizes_by_package_rather_than_by_mutant() {
let plan = plan_mutating(&graph(), &["core", "core", "app"]);
let binaries = vec![binary("app"), binary("core"), binary("tool")];
let reach = Reachability::build(&plan, &binaries, &ANY);
assert_eq!(reach.len(), 2, "one entry per distinct package, not one per mutant");
}
#[test]
fn reachability_answers_none_for_a_package_holding_no_pending_mutant() {
let plan = plan_mutating(&graph(), &["core"]);
let binaries = vec![binary("app"), binary("core"), binary("tool")];
let reach = Reachability::build(&plan, &binaries, &ANY);
assert!(
reach.reachable("aside").is_none(),
"aside holds no pending mutant, so nothing should have asked about it"
);
}
#[test]
fn reachability_orders_each_packages_binaries_with_order_reachable() {
let plan = plan_mutating(&graph(), &["core"]);
let mut app_dependent = binary("app");
app_dependent.baseline = Duration::from_millis(1);
let mut own = binary("core");
own.baseline = Duration::from_secs(1);
let binaries = vec![app_dependent, own];
let reach = Reachability::build(&plan, &binaries, &ANY);
let ordered = reach.reachable("core").expect("core holds a pending mutant");
assert_eq!(
ordered.iter().map(|binary| binary.package.as_str()).collect::<Vec<_>>(),
["core", "app"],
"own package first, exactly as order_reachable orders it directly"
);
}
#[test]
fn workload_sums_reachable_binary_baselines_with_no_census() {
let plan = plan_mutating(&graph(), &["core"]);
let mut app_dependent = binary("app");
app_dependent.baseline = Duration::from_millis(300);
app_dependent.budget = Some(Duration::from_secs(3));
let mut own = binary("core");
own.baseline = Duration::from_millis(700);
own.budget = Some(Duration::from_secs(7));
let binaries = vec![app_dependent, own];
let reach = Reachability::build(&plan, &binaries, &ANY);
let work = workload(&plan.mutants, &reach, None);
assert_eq!(
work.suite,
Duration::from_secs(1),
"one mutant, whole-binary cost for both of its reachable binaries"
);
assert_eq!(work.budget, Duration::from_secs(10), "both binaries' budgets, summed");
assert_eq!(work.single, Duration::from_secs(5), "the average of the two binaries' budgets");
}
#[test]
#[should_panic(expected = "the shared reachability index was built from these same pending mutants")]
fn workload_refuses_a_reachability_missing_a_pending_mutants_package() {
let plan = plan_mutating(&graph(), &["core"]);
let binaries = vec![binary("core")];
let stale_plan = plan_mutating(&graph(), &["aside"]);
let reach = Reachability::build(&stale_plan, &binaries, &ANY);
let _work = workload(&plan.mutants, &reach, None);
}
#[test]
fn a_package_whose_tests_can_reach_nothing_being_mutated_is_not_built() {
let plan = plan_mutating(&graph(), &["core"]);
assert_eq!(
build_packages(&plan, &ANY),
Some(vec!["app".to_owned(), "core".to_owned(), "tool".to_owned()])
);
}
#[test]
fn mutating_everything_asks_for_the_whole_workspace() {
let plan = plan_mutating(&graph(), &["core", "aside"]);
assert_eq!(build_packages(&plan, &ANY), None, "a subset of everything is not a subset");
}
#[test]
fn naming_the_tests_that_matter_narrows_the_build_to_them() {
let plan = plan_mutating(&graph(), &["core"]);
let named = [String::from("app")];
let scope = TestScope {
packages: &named,
package_local: false,
whole_workspace: false,
};
assert_eq!(build_packages(&plan, &scope), Some(vec!["app".to_owned(), "core".to_owned()]));
}
#[test]
fn package_local_testing_builds_only_the_mutated_packages_own_tests() {
let plan = plan_mutating(&graph(), &["core"]);
let selected = [String::from("app"), String::from("core"), String::from("tool")];
let scope = TestScope {
packages: &selected,
package_local: true,
whole_workspace: false,
};
assert_eq!(build_packages(&plan, &scope), Some(vec!["core".to_owned()]));
}
#[test]
fn testing_the_whole_workspace_builds_the_whole_workspace() {
let plan = plan_mutating(&graph(), &["core"]);
let scope = TestScope {
packages: &[],
package_local: false,
whole_workspace: true,
};
assert_eq!(build_packages(&plan, &scope), None);
}
#[test]
fn a_workspace_with_no_dependency_graph_is_built_whole() {
let plan = plan_mutating(&[], &["core"]);
assert_eq!(build_packages(&plan, &ANY), None, "nothing is known, so nothing can be ruled out");
}
#[test]
fn the_oracle_defaults_to_the_packages_cargo_itself_would_have_tested() {
let selected = [String::from("tick")];
assert_eq!(oracle_packages(&selected, &Config::default()), vec!["tick".to_owned()]);
}
#[test]
fn naming_the_test_packages_replaces_the_default_cap() {
let selected = [String::from("tick")];
let config = Config {
test_packages: vec!["harness".to_owned()],
..Config::default()
};
assert_eq!(oracle_packages(&selected, &config), vec!["harness".to_owned()]);
}
#[test]
fn testing_the_whole_workspace_lifts_the_cap_entirely() {
let selected = [String::from("tick")];
let config = Config {
test_workspace: true,
..Config::default()
};
assert!(
oracle_packages(&selected, &config).is_empty(),
"an empty restriction is what admits every package"
);
}
#[test]
fn the_cap_covers_every_selected_package_even_when_only_one_holds_mutants() {
let selected = [String::from("tick"), String::from("tock")];
assert_eq!(
oracle_packages(&selected, &Config::default()),
vec!["tick".to_owned(), "tock".to_owned()]
);
}
#[test]
fn a_dependents_tests_do_not_judge_a_mutant_in_the_crate_being_run_on() {
let plan = plan_reaching(&[("app", &["app", "core"]), ("core", &["core"])]);
let capped = [String::from("core")];
let scope = TestScope {
packages: &capped,
package_local: false,
whole_workspace: false,
};
assert!(!reaches(&binary("app"), "core", &plan, &scope), "the oracle escaped its cap");
assert!(reaches(&binary("core"), "core", &plan, &scope), "the crate cannot judge itself");
}
#[test]
fn a_workspace_run_judges_each_mutant_only_with_its_own_package() {
let plan = plan_reaching(&[("app", &["app", "core"]), ("core", &["core"])]);
let selected = [String::from("app"), String::from("core")];
let scope = TestScope {
packages: &selected,
package_local: true,
whole_workspace: false,
};
assert!(!reaches(&binary("app"), "core", &plan, &scope));
assert!(reaches(&binary("core"), "core", &plan, &scope));
assert!(reaches(&binary("app"), "app", &plan, &scope));
}
#[test]
fn a_binary_only_reaches_what_its_package_links() {
let plan = plan_reaching(&[("app", &["app", "core"]), ("core", &["core"])]);
assert!(reaches(&binary("app"), "core", &plan, &ANY));
assert!(reaches(&binary("app"), "app", &plan, &ANY));
assert!(!reaches(&binary("core"), "app", &plan, &ANY));
}
#[test]
fn an_unknown_package_reaches_everything() {
let plan = plan_reaching(&[("app", &["app"])]);
assert!(
reaches(&binary(""), "core", &plan, &ANY),
"an unattributed binary must not be skipped"
);
assert!(
reaches(&binary("app"), "", &plan, &ANY),
"an unattributed mutant must not be skipped"
);
assert!(reaches(&binary("stranger"), "core", &plan, &ANY), "a package we know nothing about");
}
#[test]
fn a_test_package_filter_excludes_other_binaries() {
let plan = plan_reaching(&[("app", &["app"]), ("other", &["other"])]);
let named = [String::from("app")];
let scope = TestScope {
packages: &named,
package_local: false,
whole_workspace: false,
};
assert!(!reaches(&binary("other"), "other", &plan, &scope));
assert!(reaches(&binary("app"), "app", &plan, &scope));
}
#[test]
fn a_binary_is_attributed_to_the_package_that_produced_it() {
let stdout = concat!(
r#"{"reason":"compiler-artifact","profile":{"test":true},"executable":"/tmp/a","#,
r#""package_id":"path+file:///w/crates/parser#cargo-gamma-lib@0.1.0"}"#,
"\n",
);
assert_eq!(test_binaries(stdout)[0].package, "cargo-gamma-lib");
}
#[test]
fn every_spelling_of_a_package_id_is_understood() {
assert_eq!(package_name("path+file:///w/crates/cargo-gamma-rt#0.1.0"), "cargo-gamma-rt");
assert_eq!(
package_name("path+file:///w/crates/parser#cargo-gamma-lib@0.1.0"),
"cargo-gamma-lib"
);
assert_eq!(
package_name("registry+https://github.com/rust-lang/crates.io-index#serde@1.0.0"),
"serde"
);
assert_eq!(package_name("serde 1.0.0 (registry+https://example.com)"), "serde");
}
#[test]
fn a_pre_release_version_is_not_mistaken_for_a_package_name() {
assert_eq!(package_name("path+file:///w/crates/cargo-gamma-rt#1.0.0-beta.1"), "cargo-gamma-rt");
assert_eq!(package_name("path+file:///w/crates/cargo-gamma-rt#1.0.0+build7"), "cargo-gamma-rt");
assert_eq!(package_name("path+file:///w/crates/x#3d-tiles"), "3d-tiles");
}
#[test]
fn a_budget_is_scaled_from_the_binary_baseline() {
let mut binaries = vec![binary("a"), binary("b")];
binaries[0].baseline = Duration::from_secs(10);
binaries[1].baseline = Duration::from_secs(30);
apportion(&mut binaries, 1.2, Duration::ZERO, true);
assert_eq!(binaries[0].budget, Some(Duration::from_secs(12)));
assert_eq!(binaries[1].budget, Some(Duration::from_secs(36)));
}
#[test]
fn binaries_whose_baseline_was_instantaneous_get_the_floor() {
let mut binaries = vec![binary("a"), binary("b"), binary("c")];
apportion(&mut binaries, 1.2, Duration::from_secs(20), true);
for entry in &binaries {
assert_eq!(entry.budget, Some(Duration::from_secs(20)));
}
}
#[test]
fn an_uncalibrated_run_gets_no_budget_rather_than_the_floor() {
let mut binaries = vec![binary("a"), binary("b")];
apportion(&mut binaries, 1.2, Duration::from_secs(20), false);
for entry in &binaries {
assert_eq!(entry.budget, None, "a cutoff was invented from no measurement");
assert_eq!(entry.budget_for(None, Duration::from_secs(20)), None);
assert_eq!(entry.budget_for(Some(3.0), Duration::from_secs(20)), None);
}
}
#[test]
fn the_floor_is_a_promise_about_one_binary() {
let mut binaries = vec![binary("a"), binary("b")];
binaries[0].baseline = Duration::from_millis(1);
binaries[1].baseline = Duration::from_secs(100);
apportion(&mut binaries, 1.2, Duration::from_secs(20), true);
assert_eq!(binaries[0].budget, Some(Duration::from_secs(20)));
assert_eq!(binaries[1].budget, Some(Duration::from_mins(2)));
}
#[test]
fn a_crowded_workspace_does_not_dilute_the_floor_away() {
let mut binaries: Vec<TestBinary> = (0..200).map(|_index| binary("a")).collect();
for entry in &mut binaries {
entry.baseline = Duration::from_millis(50);
}
apportion(&mut binaries, 1.2, Duration::from_secs(20), true);
for entry in &binaries {
assert_eq!(entry.budget, Some(Duration::from_secs(20)), "the floor was diluted again");
}
}
#[test]
fn a_binary_that_earns_more_than_the_floor_keeps_its_share() {
let mut binaries = vec![binary("a"), binary("b")];
binaries[0].baseline = Duration::from_secs(100);
binaries[1].baseline = Duration::from_secs(100);
apportion(&mut binaries, 1.2, Duration::from_secs(20), true);
assert_eq!(binaries[0].budget, Some(Duration::from_mins(2)));
}
#[test]
fn budget_for_computes_per_mutant_overridden_budget() {
let mut b = binary("a");
b.baseline = Duration::from_secs(10);
b.budget = Some(Duration::from_secs(12));
assert_eq!(b.budget_for(None, Duration::from_secs(1)), Some(Duration::from_secs(12)));
assert_eq!(b.budget_for(Some(3.0), Duration::from_secs(1)), Some(Duration::from_secs(30)));
assert_eq!(b.budget_for(Some(0.01), Duration::from_secs(5)), Some(Duration::from_secs(5)));
}
#[test]
fn an_unreadable_package_id_reaches_everything_rather_than_nothing() {
assert_eq!(package_name(""), "");
}
}