repositories / smith
smith
There are many coding harnesses - but this one is fast
owned by admin
xtask/src/nightly.rs
Raw//! Staged nightly CI (`SMH-SPEC-SPEC0001`, testing acceptance).
use std::{ffi::OsString, fs, path::Path, process::ExitCode};
const SHARDS: usize = 8;
const LISTS: [&str; 4] = ["caught.txt", "missed.txt", "timeout.txt", "unviable.txt"];
/// Each Luci matrix child restores the same job's caches. Bootstrap clears
/// evidence; shard finding codes defer failure; report publishes before verdict.
pub(crate) fn run(extra: &[OsString]) -> ExitCode {
let Some((phase, extra)) = crate::without_separator(extra).split_first() else {
return crate::fail("nightly needs bootstrap, 0..7, report, or verdict\n");
};
let Some(phase) = phase.to_str() else {
return crate::fail("nightly phase must be UTF-8\n");
};
execute(&crate::target_dir(), phase, extra, |step, args| step(args))
}
fn execute(
target: &Path,
phase: &str,
extra: &[OsString],
mut call: impl FnMut(crate::Step, &[OsString]) -> ExitCode,
) -> ExitCode {
let stages = target.join("nightly");
let summary = target.join("nightly-summary");
let output = stages.join(phase);
let result = (|| -> std::io::Result<ExitCode> {
match phase {
"bootstrap" => {
if stages.exists() {
fs::remove_dir_all(&stages)?;
}
if summary.exists() {
fs::remove_dir_all(&summary)?;
}
for (step, args) in [
(crate::bootstrap as crate::Step, &[][..]),
(crate::cargo_nextest, extra),
(crate::ub, extra),
] {
let status = call(step, args);
if status != ExitCode::SUCCESS {
return Ok(status);
}
}
fs::create_dir_all(&output)?;
fs::write(
output.join("index.md"),
"# Nightly bootstrap\n\nBaseline tests and Miri passed.\n",
)?;
}
"report" => {
aggregate(&stages, &summary)?;
let status = call(report_stage, extra);
if status != ExitCode::SUCCESS {
return Ok(status);
}
let report = target.join("report");
for name in LISTS {
fs::copy(summary.join(name), report.join(name))?;
}
if output.exists() {
fs::remove_dir_all(&output)?;
}
fs::rename(report, &output)?;
}
"verdict" => {
let report = stages.join("report");
// No report means no complete run, even if stale shard lists exist.
if !report.join("index.md").is_file()
|| !LISTS.iter().all(|name| report.join(name).is_file())
{
return Ok(crate::fail("nightly verdict requires the report stage\n"));
}
let Some([_, missed, timeout, _]) = crate::mutants_counts(&report) else {
return Ok(crate::fail("nightly verdict requires mutation evidence\n"));
};
fs::create_dir_all(&output)?;
fs::write(
output.join("index.md"),
format!("# Nightly verdict\n\nMissed: {missed}; timeout: {timeout}.\n"),
)?;
if missed != 0 || timeout != 0 {
return Ok(crate::fail(
"nightly mutation findings; see the published report\n",
));
}
}
_ => {
let shard = phase
.parse::<usize>()
.ok()
.filter(|&shard| shard < SHARDS && shard.to_string() == phase);
let Some(shard) = shard else {
return Ok(crate::fail("unknown nightly phase\n"));
};
if output.exists() {
fs::remove_dir_all(&output)?;
}
let mut args = Vec::with_capacity(extra.len() + 5);
args.push(OsString::from("--all"));
args.extend_from_slice(extra);
args.extend([
OsString::from("--shard"),
format!("{shard}/{SHARDS}").into(),
OsString::from("--output"),
output.into_os_string(),
]);
let status = call(crate::cargo_mutants, &args);
if ![ExitCode::SUCCESS, ExitCode::from(2), ExitCode::from(3)].contains(&status) {
return Ok(status);
}
let evidence = stages.join(phase).join("mutants.out");
for name in LISTS.into_iter().chain(["outcomes.json"]) {
fs::metadata(evidence.join(name))?;
}
}
}
Ok(ExitCode::SUCCESS)
})();
result.unwrap_or_else(|error| crate::fail(&format!("nightly {phase}: {error}\n")))
}
fn report_stage(extra: &[OsString]) -> ExitCode {
crate::report_with_mutants(extra, &crate::target_dir().join("nightly-summary"))
}
/// Preserve each shard's JSON evidence; merge only the four text worklists
/// consumed by the existing report. Missing shards are infrastructure failure.
fn aggregate(stages: &Path, summary: &Path) -> std::io::Result<()> {
fs::create_dir_all(summary)?;
for name in LISTS {
let mut combined = String::new();
for shard in 0..SHARDS {
let source = stages.join(shard.to_string()).join("mutants.out");
let list = fs::read_to_string(source.join(name))?;
combined.push_str(&list);
if !list.is_empty() && !list.ends_with('\n') {
combined.push('\n');
}
}
fs::write(summary.join(name), combined)?;
}
for shard in 0..SHARDS {
fs::copy(
stages
.join(shard.to_string())
.join("mutants.out/outcomes.json"),
summary.join(format!("shard-{shard}.json")),
)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::{ffi::OsString, fs, path::Path, process::ExitCode};
fn fixture(name: &str) -> std::path::PathBuf {
let root =
std::env::temp_dir().join(format!("smith-nightly-{name}-{}", std::process::id()));
let _ = fs::remove_dir_all(&root);
fs::create_dir_all(&root).unwrap();
root
}
fn evidence(root: &Path, shard: usize, missed: bool) {
let dir = root
.join("nightly")
.join(shard.to_string())
.join("mutants.out");
fs::create_dir_all(&dir).unwrap();
for name in ["caught", "missed", "timeout", "unviable"] {
fs::write(
dir.join(format!("{name}.txt")),
if name == "missed" && missed {
"miss\n"
} else {
""
},
)
.unwrap();
}
fs::write(dir.join("outcomes.json"), "{}").unwrap();
}
#[test]
fn findings_keep_shards_running_but_fail_the_verdict() {
let root = fixture("findings");
for shard in 0..8 {
let status = super::execute(&root, &shard.to_string(), &[], |step, args| {
assert!(std::ptr::fn_addr_eq(
step,
crate::cargo_mutants as crate::Step
));
assert_eq!(
args,
&[
OsString::from("--all"),
OsString::from("--shard"),
OsString::from(format!("{shard}/8")),
OsString::from("--output"),
root.join("nightly")
.join(shard.to_string())
.into_os_string(),
]
);
evidence(&root, shard, shard == 0);
if shard == 0 {
ExitCode::from(2)
} else {
ExitCode::SUCCESS
}
});
assert_eq!(status, ExitCode::SUCCESS);
}
let status = super::execute(&root, "report", &[], |_, _| {
let out = root.join("report");
fs::create_dir_all(&out).unwrap();
fs::write(out.join("index.md"), "report").unwrap();
ExitCode::SUCCESS
});
assert_eq!(status, ExitCode::SUCCESS);
assert!(root.join("nightly/report/index.md").exists());
assert_eq!(
fs::read_to_string(root.join("nightly/report/missed.txt")).unwrap(),
"miss\n"
);
assert_eq!(
super::execute(&root, "verdict", &[], |_, _| panic!(
"verdict runs no tools"
)),
ExitCode::FAILURE
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn infrastructure_errors_are_not_findings() {
for (name, status, write) in [
("infra", ExitCode::FAILURE, true),
("missing", ExitCode::SUCCESS, false),
("timeout", ExitCode::from(3), true),
] {
let root = fixture(name);
let result = super::execute(&root, "0", &[], |_, _| {
if write {
evidence(&root, 0, false);
}
status
});
assert_eq!(
result,
if write && status == ExitCode::from(3) {
ExitCode::SUCCESS
} else {
ExitCode::FAILURE
}
);
fs::remove_dir_all(root).unwrap();
}
}
#[test]
fn report_requires_every_shard_without_reusing_old_evidence() {
let root = fixture("incomplete");
evidence(&root, 0, false);
assert_eq!(
super::execute(&root, "report", &[], |_, _| panic!(
"incomplete shards must stop before coverage"
)),
ExitCode::FAILURE
);
assert_eq!(
super::execute(&root, "bootstrap", &[], |_, _| ExitCode::SUCCESS),
ExitCode::SUCCESS
);
assert!(!root.join("nightly/0").exists());
assert_eq!(
super::execute(&root, "verdict", &[], |_, _| panic!(
"verdict runs no tools"
)),
ExitCode::FAILURE
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn bootstrap_verifies_baseline_before_miri_and_stops_on_failure() {
let root = fixture("baseline");
let extra = [OsString::from("-p"), OsString::from("xtask")];
let mut calls = 0;
let status = super::execute(&root, "bootstrap", &extra, |step, args| {
calls += 1;
match calls {
1 => {
assert!(std::ptr::fn_addr_eq(step, crate::bootstrap as crate::Step));
assert!(args.is_empty());
ExitCode::SUCCESS
}
2 => {
assert!(std::ptr::fn_addr_eq(
step,
crate::cargo_nextest as crate::Step
));
assert_eq!(args, extra);
ExitCode::FAILURE
}
_ => panic!("Miri must not run after failing baseline"),
}
});
assert_eq!(calls, 2);
assert_eq!(status, ExitCode::FAILURE);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn complete_run_passes_but_timeouts_fail_the_verdict() {
let root = fixture("verdict");
for shard in 0..8 {
evidence(&root, shard, false);
}
let report = || {
assert_eq!(
super::execute(&root, "report", &[], |_, _| {
fs::create_dir_all(root.join("report")).unwrap();
fs::write(root.join("report/index.md"), "report").unwrap();
ExitCode::SUCCESS
}),
ExitCode::SUCCESS
);
};
report();
assert_eq!(
super::execute(&root, "verdict", &[], |_, _| panic!("no tools")),
ExitCode::SUCCESS
);
fs::write(root.join("nightly/7/mutants.out/timeout.txt"), "timeout").unwrap();
report();
assert_eq!(
fs::read_to_string(root.join("nightly/report/timeout.txt")).unwrap(),
"timeout\n"
);
assert_eq!(
super::execute(&root, "verdict", &[], |_, _| panic!("no tools")),
ExitCode::FAILURE
);
fs::remove_dir_all(root).unwrap();
}
#[test]
fn native_exit_codes_distinguish_findings_from_failed_runs() {
for code in [0, 1, 2, 3, 4, 5, 6, 70] {
let expected = match code {
0 => ExitCode::SUCCESS,
2 => ExitCode::from(2),
3 => ExitCode::from(3),
_ => ExitCode::FAILURE,
};
assert_eq!(crate::mutants_exit(Some(code)), expected);
}
assert_eq!(crate::mutants_exit(None), ExitCode::FAILURE);
}
#[test]
fn invalid_phases_do_not_run_tools() {
let root = fixture("invalid");
for phase in ["", "8", "-1", "00", "whatever"] {
assert_eq!(
super::execute(&root, phase, &[], |_, _| panic!(
"invalid phases must not run tools"
)),
ExitCode::FAILURE
);
}
fs::remove_dir_all(root).unwrap();
}
#[test]
fn kdl_stages_share_one_job_in_execution_order() {
let config = include_str!("../../.ci/nightly.kdl");
assert!(config.contains("matrix {\n phase \"bootstrap\" \"0\" \"1\" \"2\" \"3\" \"4\" \"5\" \"6\" \"7\" \"report\" \"verdict\"\n }"));
assert!(config.contains("cargo run --locked -p xtask -- nightly ${phase}"));
assert!(config.contains("from \"target/nightly/${phase}\""));
assert!(!config.contains("|| echo"));
}
}