repositories / smith
smith
There are many coding harnesses - but this one is fast
owned by admin
xtask/src/main.rs
Raw#![deny(unsafe_code)]
#![warn(missing_docs)]
//! Cargo-only Smith project automation (`SMH-SPEC-SPEC0001`, acceptance:
//! quality, architecture, documentation gates).
//!
//! Gate tools follow one rule: install immutable artifacts or pinned
//! versions with `--locked`, never live dependency resolution, so upstream
//! releases cannot break a gate without a change in this repository.
use std::{
env,
ffi::OsString,
fs,
io::{BufRead, Write},
path::{Path, PathBuf},
process::Command,
process::ExitCode,
};
use std::collections::{BTreeMap, BTreeSet};
mod dependency;
mod nightly;
const HELP: &str = concat!(
"usage: cargo x <check|lint|fmt|test|e2e|coverage|report|doc|arch|mutants [args...]|audit|deps|ci|release|bootstrap>\n",
" cargo x perf [--pin] [args...] instruction budgets (callgrind), gated against smith-bench/budgets.toml\n",
" cargo x bench [args...] wall-clock criterion benches; never gate\n",
" cargo x deps --verify <locked|fresh-compatible|direct-minimum> [-p package] [--target triple]\n",
" cargo x dev [args...] build and launch the dev smith binary\n",
" cargo x ub [args...] tests of unsafe opt-in crates under Miri (the user's nightly); on demand\n",
" cargo x nightly <bootstrap|0..7|report|verdict> [args...] cached CI stages; findings fail at verdict\n",
" cargo x ci-image [gate...] [-- args] gates (default check e2e perf) in the CI image of .ci/perf.kdl,\n",
" scratch and report in target/ci-image/; needs podman or docker, rsync, network\n",
);
/// The only nightly toolchain, floating: Miri ships on nightly alone, and
/// shipped artifacts build on stable (`rust-toolchain.toml`).
const UB_TOOLCHAIN: &str = "nightly";
/// `rustup` arguments installing [`UB_TOOLCHAIN`] with Miri; a downgrade
/// picks the newest nightly that ships Miri. No self-update: installing a
/// toolchain leaves rustup itself alone, and the update looks for rustup
/// under the cargo home, which CI points into its workspace.
const UB_INSTALL: &[&str] = &[
"toolchain",
"install",
UB_TOOLCHAIN,
"--profile",
"minimal",
"--allow-downgrade",
"--no-self-update",
"--component",
"miri,rust-src",
];
/// Version of `cargo-nextest` used by the test gate.
const NEXTEST_VERSION: &str = "0.9.146";
/// Version of `cargo-binstall` used to install `cargo-nextest`.
const BINSTALL_VERSION: &str = "1.23.0";
const MACHETE_VERSION: &str = "0.9.2";
const DENY_VERSION: &str = "0.20.2";
const AUDIT_VERSION: &str = "0.22.2";
const MUTANTS_VERSION: &str = "27.1.0";
const LLVM_COV_VERSION: &str = "0.9.1";
const INSTA_VERSION: &str = "1.44";
/// Version of `gungraun-runner`; must match the `gungraun` crate in the lockfile.
const GUNGRAUN_VERSION: &str = "0.20.0";
/// gungraun bench targets of `smith-bench`, gated by `cargo x perf`.
const INSTRUCTION_BENCHES: &[&str] = &["instructions", "startup"];
/// criterion bench target of `smith-bench`, run by `cargo x bench`.
const WALL_CLOCK_BENCH: &str = "wall_clock";
/// Instruction pins, one per gungraun bench.
const BUDGETS: &str = "smith-bench/budgets.toml";
/// Allowed deviation of a measured `Ir` from its pin, either direction.
const BAND_PERCENT: u64 = 2;
/// Provider selection and credential variables that never reach a
/// measured `smith` run.
const PROVIDER_VARS: &[&str] = &[
"SMITH_PROVIDER",
"SMITH_MODEL",
"SMITH_BASE_URL",
"ANTHROPIC_API_KEY",
"OPENAI_API_KEY",
"GEMINI_API_KEY",
];
/// Runtime duplicate report: normal dependencies only, excluding macro, dev, benchmark, and xtask edges.
const RUNTIME_DUPLICATE_COMMAND: &[&str] = &[
"tree",
"--locked",
"--workspace",
"--exclude",
"smith-bench",
"--exclude",
"xtask",
"--target",
"all",
"--edges",
"normal,no-proc-macro",
"--duplicates",
"--depth",
"0",
"--prefix",
"none",
"--color",
"never",
];
/// Full workspace policy pass; duplicate warnings are reported by `RUNTIME_DUPLICATE_COMMAND`.
const FULL_DENY_COMMAND: &[&str] = &[
"deny",
"--config",
".config/deny.toml",
"--locked",
"--workspace",
"check",
"--allow",
"duplicate",
];
const CRATES: &[&str] = &[
"smith",
"smith-core",
"smith-ai",
"smith-ui",
"smith-tui",
"smith-rpc",
"smith-harness",
"smith-cli",
"smith-alloc",
"smith-bench",
"xtask",
];
/// Every `cargo x` command.
const COMMANDS: &[(&str, Step)] = &[
("check", check),
("lint", lint),
("fmt", cargo_fmt_step),
("test", cargo_nextest),
("e2e", cargo_e2e),
("coverage", cargo_coverage),
("report", report),
("nightly", nightly::run),
("doc", cargo_doc),
("arch", arch),
("mutants", cargo_mutants),
("ub", ub),
("perf", perf),
("bench", bench),
("audit", audit),
("deps", dependency::run),
("ci", ci),
("release", release),
("bootstrap", bootstrap),
("dev", dev),
("ci-image", ci_image),
];
/// Commands `ci-image` refuses: floating-nightly workflows, the interactive
/// launcher, the undefined release, and itself.
const HOST_ONLY: &[&str] = &["ub", "nightly", "dev", "release", "ci-image"];
fn main() -> ExitCode {
let mut args = env::args_os();
let _program = args.next();
let command = args.next();
let rest: Vec<OsString> = args.collect();
COMMANDS
.iter()
.find(|(name, _)| command.as_deref() == Some(name.as_ref()))
.map_or_else(|| fail(HELP), |(_, step)| step(&rest))
}
fn check(extra: &[OsString]) -> ExitCode {
sequence(
&[
cargo_fmt_step,
cargo_clippy,
arch,
cargo_nextest,
cargo_doc,
cargo_machete,
],
extra,
)
}
type Step = fn(&[OsString]) -> ExitCode;
fn sequence(steps: &[Step], extra: &[OsString]) -> ExitCode {
for step in steps {
let code = step(extra);
if code != ExitCode::SUCCESS {
return code;
}
}
ExitCode::SUCCESS
}
/// Append passthrough args; package scoping overrides workspace defaults,
/// so `-p crate` drops `--workspace` and its `--exclude <crate>` pairs
/// rather than conflicting.
fn scoped(base: &[&str], extra: &[OsString]) -> Vec<OsString> {
let narrowed = narrows(extra);
let mut args = Vec::with_capacity(base.len() + extra.len());
let mut base = base.iter();
while let Some(arg) = base.next() {
match *arg {
"--workspace" if narrowed => {}
"--exclude" if narrowed => {
base.next();
}
arg => args.push(OsString::from(arg)),
}
}
args.extend_from_slice(extra);
args
}
/// Whether the passthrough arguments select packages.
fn narrows(extra: &[OsString]) -> bool {
selected_package(extra).is_some()
}
fn lint(extra: &[OsString]) -> ExitCode {
sequence(&[cargo_fmt_step, cargo_clippy], extra)
}
/// Audit is a fixed project-wide pipeline: runtime duplicates, full policy, lockfile audit.
fn audit(extra: &[OsString]) -> ExitCode {
if !extra.is_empty() {
return fail("audit is project-wide and admits no scoping\n");
}
sequence(
&[
install_deny,
install_audit,
cargo_runtime_duplicates,
cargo_deny,
cargo_audit,
],
extra,
)
}
/// The merge-gate pipeline: every gate, machine context, fixed order.
/// Passthrough is rejected so workflows cannot drift from the definition.
fn ci(extra: &[OsString]) -> ExitCode {
if !extra.is_empty() {
return fail("ci is a fixed pipeline and admits no scoping\n");
}
sequence(&[bootstrap, check, audit], extra)
}
/// Where the CI image is named: the job that took the instruction pins.
const CI_IMAGE_SOURCE: &str = ".ci/perf.kdl";
/// Gates `ci-image` runs when none are named.
const CI_IMAGE_DEFAULT: &[&str] = &["check", "e2e", "perf"];
/// Source paths the work copy never mirrors: build output wherever
/// `.gitignore` ignores it, the VCS stores, and a CI cargo home.
const CI_IMAGE_EXCLUDES: &[&str] = &[
"target/",
"mutants.out*",
"/.git/",
"/.jj/",
"/.cargo-home/",
];
/// Head of every container script: fail fast, one output stream, cached
/// valgrind and installed tools first on `PATH`.
const CI_IMAGE_PREFIX: &str =
"set -eu; exec 2>&1; export PATH=/ci/valgrind/bin:/ci/cargo-home/bin:$PATH\n";
/// Idempotent tool setup under `/ci`: the rustup home seeded once from
/// the image's own toolchain, which a directory override selects over
/// `rust-toolchain.toml`'s floating `stable`; rustfmt, clippy, and the
/// guest target only when missing; valgrind of the CI jobs built once at
/// its mount prefix; then the gate tools through `cargo x bootstrap`.
const CI_IMAGE_SETUP: &str = r#"tc="$RUST_VERSION-$(uname -m)-unknown-linux-gnu"
test -d "/ci/rustup/toolchains/$tc" || { mkdir -p /ci/rustup && cp -a /usr/local/rustup/. /ci/rustup/; }
rustup override set "$tc" --path /ci/work
rustfmt --version >/dev/null 2>&1 || rustup component add rustfmt
cargo clippy --version >/dev/null 2>&1 || rustup component add clippy
rustup target list --installed | grep -qx wasm32-wasip2 || rustup target add wasm32-wasip2
test -x /ci/valgrind/bin/valgrind || (curl -fsSL https://sourceware.org/pub/valgrind/valgrind-3.25.1.tar.bz2 | tar -xj -C /tmp && cd /tmp/valgrind-3.25.1 && ./configure -q --prefix=/ci/valgrind >/dev/null && make -s -j"$(nproc)" >/dev/null && make -s install >/dev/null)
cargo x bootstrap
"#;
/// What the pinned counts depend on, recorded after the gates.
const CI_IMAGE_VERSIONS: &str = "rustc --version; ldd --version | head -n 1; valgrind --version; gungraun-runner --version; cargo nextest --version\n";
/// Pinned-platform gates: `cargo x ci-image [gate...] [-- args]` runs
/// `cargo x <gate> <args>` per gate (default check, e2e, perf) in the image
/// [`CI_IMAGE_SOURCE`] names, where the instruction pins were taken; every
/// gate runs and reports, and any failure fails the command.
///
/// One scratch, `target/ci-image/`, reused across runs: `work/` is an
/// `rsync --checksum --delete` mirror of the source without
/// [`CI_IMAGE_EXCLUDES`]; the cargo home, rustup home, target dir, and
/// valgrind prefix sit beside it, so the sync never wipes them, and mount
/// at fixed `/ci` paths, the target dir at the work copy's `target/`.
/// `report/` holds `setup.log`, `<gate>.log` each, `summary.md` with exit
/// codes, `versions.txt`, `source.txt` (checksum differences between
/// source and work after the run), and `perf.md`.
/// Rootless podman with `--userns=keep-id`, or docker with `--user`, leaves
/// every file owned by the caller, so `cargo clean` removes it all.
///
/// Needs podman or docker and rsync on `PATH`, and network to refresh the
/// CI image each run and fetch missing crates, tools, and valgrind. Files the gates change, e.g.
/// `perf --pin`, stay in `work/`.
fn ci_image(extra: &[OsString]) -> ExitCode {
use std::fmt::Write as _;
let (gates, passthrough) = match ci_image_gates(extra) {
Ok(split) => split,
Err(message) => return fail(&message),
};
let Some(image) = fs::read_to_string(CI_IMAGE_SOURCE)
.ok()
.and_then(|kdl| ci_image_of(&kdl).map(str::to_owned))
else {
return fail(&format!("ci-image: no `image` in {CI_IMAGE_SOURCE}\n"));
};
let scratch = target_dir().join("ci-image");
let report = scratch.join("report");
let _ = fs::remove_dir_all(&report);
if fs::create_dir_all(&report).is_err() {
return fail(&format!("cannot create {}\n", report.display()));
}
let Ok(scratch) = std::path::absolute(&scratch) else {
return fail("cannot resolve target/ci-image\n");
};
let work = scratch.join("work");
let synced = run_os("rsync", &sync_args(false, &work));
if synced != ExitCode::SUCCESS {
return synced;
}
// Both ends of the target mount exist before the engine would create
// them as the container's root, a subordinate id the caller cannot own.
for dir in [scratch.join("target"), work.join("target")] {
if let Err(error) = fs::create_dir_all(&dir) {
return fail(&format!("cannot create {}: {error}\n", dir.display()));
}
}
let engine = if present("podman") {
Engine::Podman
} else if let (true, Some((uid, gid))) = (present("docker"), owner()) {
Engine::Docker { uid, gid }
} else {
return fail("ci-image needs podman or docker on PATH\n");
};
note(&format!("ci-image: {image} via {}\n", engine.program()));
let container = |script: &str, args: &[OsString], log: &str| {
logged(
Command::new(engine.program())
.args(container_args(engine, &scratch, &image, script, args))
.stderr(std::process::Stdio::inherit()),
&report.join(log),
)
};
if !container(CI_IMAGE_SETUP, &[], "setup.log").is_ok_and(|status| status.success()) {
return fail("ci-image: setup failed; see target/ci-image/report/setup.log\n");
}
let perf_table = scratch.join("target").join("report").join("perf.md");
let _ = fs::remove_file(&perf_table);
let mut summary = format!("Image `{image}`.\n\n| gate | exit | seconds |\n|---|---|---|\n");
let mut failed = false;
for gate in &gates {
let started = std::time::Instant::now();
let mut args = vec![OsString::from(*gate)];
args.extend_from_slice(passthrough);
let status = container("exec cargo x \"$@\"\n", &args, &format!("{gate}.log"));
let exit = match &status {
Ok(status) => status
.code()
.map_or_else(|| status.to_string(), |code| code.to_string()),
Err(error) => error.to_string(),
};
failed |= !status.is_ok_and(|status| status.success());
let _ = writeln!(
summary,
"| {gate} | {exit} | {} |",
started.elapsed().as_secs()
);
}
let _ = container(CI_IMAGE_VERSIONS, &[], "versions.txt");
let _ = fs::copy(&perf_table, report.join("perf.md"));
let proof = Command::new("rsync")
.args(sync_args(true, &work))
.output()
.map(|output| String::from_utf8_lossy(&output.stdout).into_owned());
let proof = proof.map_or_else(
|error| format!("rsync dry run failed: {error}\n"),
|changes| source_proof(&changes),
);
let wrote = fs::write(report.join("source.txt"), proof)
.and_then(|()| fs::write(report.join("summary.md"), &summary));
note(&summary);
if wrote.is_err() {
return fail("cannot write target/ci-image/report\n");
}
if failed {
return fail("ci-image: a gate failed; see target/ci-image/report/\n");
}
ExitCode::SUCCESS
}
/// Gates and passthrough of `ci-image` arguments: names before `--`, each
/// a `cargo x` command outside [`HOST_ONLY`], [`CI_IMAGE_DEFAULT`] when
/// none; everything after the first `--` goes to every gate.
fn ci_image_gates(extra: &[OsString]) -> Result<(Vec<&'static str>, &[OsString]), String> {
let split = extra.iter().position(|arg| arg == "--");
let (names, passthrough) =
split.map_or((extra, &[][..]), |at| (&extra[..at], &extra[at + 1..]));
let runnable = || {
COMMANDS
.iter()
.map(|(name, _)| *name)
.filter(|name| !HOST_ONLY.contains(name))
};
let mut gates = names
.iter()
.map(|name| {
runnable().find(|gate| name == *gate).ok_or_else(|| {
format!(
"ci-image: `{}` is no gate it runs; pick from: {}\n",
name.to_string_lossy(),
runnable().collect::<Vec<_>>().join(" ")
)
})
})
.collect::<Result<Vec<_>, _>>()?;
if gates.is_empty() {
gates = CI_IMAGE_DEFAULT.to_vec();
}
Ok((gates, passthrough))
}
/// The first `image "<name>"` line of a Luci job definition.
fn ci_image_of(kdl: &str) -> Option<&str> {
kdl.lines()
.find_map(|line| line.trim().strip_prefix("image \"")?.strip_suffix('"'))
}
/// `rsync` arguments mirroring the source into `work`; `dry_run` lists
/// the files whose checksums differ instead of copying them.
fn sync_args(dry_run: bool, work: &Path) -> Vec<OsString> {
let mut args: Vec<OsString> = ["-a", "--checksum", "--delete", "--omit-dir-times"]
.map(OsString::from)
.to_vec();
args.extend(
CI_IMAGE_EXCLUDES
.iter()
.map(|path| format!("--exclude={path}").into()),
);
if dry_run {
args.extend(["--dry-run", "--itemize-changes"].map(OsString::from));
}
args.push("./".into());
let mut dest = work.as_os_str().to_owned();
dest.push("/");
args.push(dest);
args
}
/// Container engine, with the caller's ids where the engine needs them
/// to write files the caller owns.
#[derive(Clone, Copy)]
enum Engine {
/// Rootless podman: `--userns=keep-id` maps the caller onto itself.
Podman,
/// Docker: the container runs as the caller's ids.
Docker { uid: u32, gid: u32 },
}
impl Engine {
const fn program(self) -> &'static str {
match self {
Self::Podman => "podman",
Self::Docker { .. } => "docker",
}
}
}
/// `run` arguments of one container: `script` after [`CI_IMAGE_PREFIX`]
/// with `args` as `$@`, the scratch mounted at `/ci`, the build homes
/// and the valgrind tools under it, its `target/` at the work copy's.
fn container_args(
engine: Engine,
scratch: &Path,
image: &str,
script: &str,
args: &[OsString],
) -> Vec<OsString> {
let mut run: Vec<OsString> = [
"run",
"--rm",
"--pull=always",
"--security-opt",
"label=disable",
]
.map(OsString::from)
.to_vec();
match engine {
Engine::Podman => run.push("--userns=keep-id".into()),
Engine::Docker { uid, gid } => run.extend(["--user".into(), format!("{uid}:{gid}").into()]),
}
// The target cache mounts where CI keeps it, the default `target/`:
// tests locate build artifacts there, and the sync never deletes the
// excluded path.
for (host, container) in [
(scratch.to_owned(), "/ci"),
(scratch.join("target"), "/ci/work/target"),
] {
let mut mount = OsString::from("--volume=");
mount.push(host);
mount.push(":");
mount.push(container);
run.push(mount);
}
run.extend(
[
"--workdir=/ci/work",
"--env=CARGO_HOME=/ci/cargo-home",
"--env=RUSTUP_HOME=/ci/rustup",
"--env=VALGRIND_LIB=/ci/valgrind/libexec/valgrind",
"--env=HOME=/tmp",
image,
"sh",
"-c",
]
.map(OsString::from),
);
run.push(format!("{CI_IMAGE_PREFIX}{script}").into());
run.push("sh".into());
run.extend_from_slice(args);
run
}
/// `source.txt` of an `rsync --itemize-changes` dry run's output.
fn source_proof(changes: &str) -> String {
if changes.is_empty() {
"work matches source: rsync --checksum dry run lists no differences\n".to_string()
} else {
format!("work differs from source after the run:\n{changes}")
}
}
/// The caller's user and group ids, from `id`.
fn owner() -> Option<(u32, u32)> {
let id = |flag| {
let output = Command::new("id").arg(flag).output().ok()?;
String::from_utf8_lossy(&output.stdout).trim().parse().ok()
};
Some((id("-u")?, id("-g")?))
}
/// Run `command` with its standard output copied to the terminal and to
/// `log`.
fn logged(command: &mut Command, log: &Path) -> std::io::Result<std::process::ExitStatus> {
let mut file = fs::File::create(log)?;
let mut child = command.stdout(std::process::Stdio::piped()).spawn()?;
if let Some(mut out) = child.stdout.take() {
let mut buffer = [0; 8192];
loop {
let read = std::io::Read::read(&mut out, &mut buffer)?;
if read == 0 {
break;
}
file.write_all(&buffer[..read])?;
let _ignored = std::io::stdout().write_all(&buffer[..read]);
}
}
child.wait()
}
/// Release gates are defined by the release-readiness plan; the command
/// exists so workflows reference a stable name today.
fn release(extra: &[OsString]) -> ExitCode {
let _ = extra;
fail("release gates are not defined yet; see SMH-PLAN-RELS0001\n")
}
/// Mutation testing runs on demand, never in the blocking check chain.
///
/// Split of concerns, kept CI-sensible:
/// - `.cargo/mutants.toml` owns tree properties (scope, tool, profiles)
/// so bare `cargo mutants` behaves identically anywhere.
/// - this step owns run context: disk-backed scratch (system tmpfs is
/// 16G RAM and tree copies crawl the machine), a modest default job
/// count overridable through `CARGO_MUTANTS_JOBS`, and a skipped
/// baseline because `cargo x check` owns test verification.
fn cargo_mutants(extra: &[OsString]) -> ExitCode {
let installed = install_mutants(&[]);
if installed != ExitCode::SUCCESS {
return installed;
}
let scratch = target_dir().join("mutants-tmp");
if std::fs::create_dir_all(&scratch).is_err() {
return fail("cannot create target/mutants-tmp\n");
}
let Ok(scratch) = scratch.canonicalize() else {
return fail("cannot resolve target/mutants-tmp\n");
};
// `cargo x mutants -- -f x` and `cargo x mutants -f x` are equivalent,
// so cargo-run habits cannot turn scope flags into stray nextest test
// filters.
let extra = without_separator(extra);
let all = extra.iter().any(|arg| arg == "--all");
if !all && !mutants_scoped(extra) {
return fail(MUTANTS_SCOPE_HELP);
}
let passthrough: Vec<&OsString> = extra.iter().filter(|arg| *arg != "--all").collect();
// Local runs are pinned to one job and a third of the cores for both
// the build and the test phase: four jobs and a full fan-out crashed
// this machine. Only `--all` (CI) reads `CARGO_MUTANTS_JOBS`.
if !all && passthrough.iter().any(|arg| jobs_flag(arg)) {
return fail("cargo x mutants runs one job locally; drop -j/--jobs\n");
}
let jobs = if all {
std::env::var("CARGO_MUTANTS_JOBS").unwrap_or_else(|_| "1".to_string())
} else {
"1".to_string()
};
let cores = std::thread::available_parallelism().map_or(2, |cores| (cores.get() / 3).max(2));
let mut command = Command::new("nice");
command
.args(["-n", "19", "cargo-mutants"])
.env("TMPDIR", &scratch)
.env("TEMP", &scratch)
.env("TMP", &scratch)
.args([
"mutants",
"--output",
"target",
"--jobs",
&jobs,
"--baseline",
"skip",
"--timeout",
"120",
// A mutant once hung rustc itself; builds are bounded too.
"--build-timeout",
"300",
"--no-shuffle",
]);
if !all {
command
.env("NEXTEST_TEST_THREADS", cores.to_string())
.env("CARGO_BUILD_JOBS", cores.to_string());
}
// Passthrough args come last and override the defaults above.
command.args(passthrough);
match command.status() {
Ok(status) => mutants_exit(status.code()),
Err(error) => fail(&format!("failed to run cargo mutants: {error}\n")),
}
}
/// Preserve cargo-mutants' finding codes so staged CI can defer findings,
/// without swallowing infrastructure errors (1) or process signals (None).
fn mutants_exit(code: Option<i32>) -> ExitCode {
match code {
Some(0) => ExitCode::SUCCESS,
Some(code @ (2 | 3)) => {
let _ = fail(&format!(
"mutation findings (exit {code}); see the mutation output directory\n"
));
// The match restricts the code to 2 or 3.
ExitCode::from(code as u8)
}
_ => fail(&format!("cargo mutants failed: exit {code:?}\n")),
}
}
const MUTANTS_SCOPE_HELP: &str = "\
cargo x mutants needs a scope: a workspace-wide run copies the tree per job and \
has crashed this machine before.
-f <glob> one file or directory, e.g. -f smith-core/src/session.rs
-D <patch> mutants on changed lines only (jj diff --git > /tmp/d.patch)
-p <crate> one crate
--shard k/n one of n equal shards, e.g. --shard 0/8
-F/-E <regex> mutant-name filters
--list inventory only, no build
--all the whole workspace; CI only
";
fn jobs_flag(arg: &OsString) -> bool {
let arg = arg.to_string_lossy();
arg == "-j"
|| arg == "--jobs"
|| arg.starts_with("-j") && arg[2..].bytes().all(|b| b.is_ascii_digit())
|| arg.starts_with("--jobs=")
}
/// Whether the passthrough arguments narrow a mutants run.
fn mutants_scoped(extra: &[OsString]) -> bool {
extra.iter().any(|arg| {
let arg = arg.to_string_lossy();
matches!(
arg.as_ref(),
"-f" | "--file"
| "-D"
| "--in-diff"
| "-p"
| "--package"
| "--shard"
| "-F"
| "--re"
| "-E"
| "--exclude-re"
| "--list"
) || arg.starts_with("--file=")
|| arg.starts_with("--in-diff=")
|| arg.starts_with("--package=")
|| arg.starts_with("--shard=")
|| arg.starts_with("--re=")
|| arg.starts_with("--exclude-re=")
})
}
/// Undefined-behaviour check: the tests of every crate that opts into
/// `unsafe_code`, or of the `-p` scope, under Miri on the user's floating
/// nightly, installed into their rustup and printed before the run.
/// Development tooling, run on demand and never by `check` or `ci`.
/// `MIRIFLAGS` adds Miri options.
fn ub(extra: &[OsString]) -> ExitCode {
let flags = miri_flags(env::var_os("MIRIFLAGS"));
let Some(test) = ub_test_args(&unsafe_opt_in_crates(Path::new("")), extra) else {
note("ub: no crate opts into unsafe_code\n");
return ExitCode::SUCCESS;
};
let toolchain = format!("+{UB_TOOLCHAIN}");
let os = |args: &[&str]| args.iter().map(OsString::from).collect();
let steps = [
("rustup", os(UB_INSTALL)),
("rustc", os(&[&toolchain, "--version"])),
("cargo", os(&[&toolchain, "miri", "setup"])),
("cargo", test),
];
for (program, args) in steps {
let code = run_command(Command::new(program).args(args).env("MIRIFLAGS", &flags));
if code != ExitCode::SUCCESS {
return code;
}
}
ExitCode::SUCCESS
}
/// `cargo` arguments of the Miri test run: the discovered crates unless
/// `extra` selects packages itself, then `extra`; `None` when neither
/// names a crate.
fn ub_test_args(discovered: &[&str], extra: &[OsString]) -> Option<Vec<OsString>> {
if discovered.is_empty() && !narrows(extra) {
return None;
}
let toolchain = format!("+{UB_TOOLCHAIN}");
let mut args: Vec<OsString> = [toolchain.as_str(), "miri", "test", "--locked"]
.map(OsString::from)
.to_vec();
if !narrows(extra) {
for crate_name in discovered {
args.extend(["-p", crate_name].map(OsString::from));
}
}
args.extend_from_slice(extra);
Some(args)
}
/// `MIRIFLAGS` of the run: Tree Borrows, then the user's own flags.
/// `smith-alloc` reaches each block's header through the pointer it is
/// asked to free; Stacked Borrows narrows that pointer to the user block
/// once a `Box` held it, Tree Borrows admits the access.
fn miri_flags(user: Option<OsString>) -> OsString {
let mut flags = OsString::from("-Zmiri-tree-borrows");
if let Some(user) = user {
flags.push(" ");
flags.push(user);
}
flags
}
/// Instruction budgets: every gungraun bench's callgrind `Ir` against its
/// pin in `smith-bench/budgets.toml`, within ±2 % in either direction, so
/// a regression fails and an improvement fails until re-pinned.
///
/// `--pin` rewrites the pins from this run instead of gating. Other
/// arguments go to the gungraun runner; a filter narrows the run, and pins
/// of benches it skipped are kept rather than reported stale. Every run
/// writes `target/report/perf.md`.
fn perf(extra: &[OsString]) -> ExitCode {
let repin = extra.iter().any(|arg| arg == "--pin");
let passthrough: Vec<OsString> = without_separator(extra)
.iter()
.filter(|arg| *arg != "--pin")
.cloned()
.collect();
let installed = install_gungraun_runner(&[]);
if installed != ExitCode::SUCCESS {
return installed;
}
let smith = match release_smith() {
Ok(path) => path,
Err(code) => return code,
};
let measured = match instruction_counts(&smith, &passthrough) {
Ok(measured) => measured,
Err(message) => return fail(&message),
};
let budgets = match fs::read_to_string(BUDGETS) {
Ok(text) => parse_budget_file(&text),
Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(BudgetFile::default()),
Err(error) => Err(format!("cannot read {BUDGETS}: {error}")),
};
let budgets = match budgets {
Ok(budgets) => budgets,
Err(message) => return fail(&format!("{message}\n")),
};
let complete = passthrough.is_empty();
let platform = platform();
let written = write_perf_report(&measured, &budgets, complete, &platform);
if written != ExitCode::SUCCESS {
return written;
}
if repin {
let mut pinned = if complete {
BudgetFile {
pins: BTreeMap::new(),
..budgets
}
} else {
budgets
};
pinned.pins.extend(measured);
if fs::write(BUDGETS, render_budgets(&pinned, &platform)).is_err() {
return fail(&format!("cannot write {BUDGETS}\n"));
}
note(&format!(
"perf: pinned {} benches in {BUDGETS}\n",
pinned.pins.len()
));
return ExitCode::SUCCESS;
}
// Instruction counts belong to one toolchain and libc: the gate runs
// where the pins were taken (the CI image); elsewhere the run reports.
let pinned_on = fs::read_to_string(BUDGETS)
.ok()
.and_then(|text| pinned_platform(&text));
if pinned_on.as_deref() != Some(platform.as_str()) {
note(&format!(
"perf: pins belong to `{}`, this run is `{platform}`; counts reported in target/report/perf.md, gate skipped\n",
pinned_on.as_deref().unwrap_or("no platform")
));
return ExitCode::SUCCESS;
}
let violations = budget_violations(&measured, &budgets.pins, complete);
if violations.is_empty() {
note(&format!(
"perf: {} benches within ±{BAND_PERCENT} % of their pins\n",
measured.len()
));
ExitCode::SUCCESS
} else {
fail(&format!(
"instruction budgets violated (re-pin deliberately with `cargo x perf --pin`):\n{}",
violations.concat()
))
}
}
/// Wall clock: the criterion benches, for A/B comparisons of two builds
/// on one machine in one session. Numbers are never committed and never
/// gate. Arguments go to criterion, e.g. `--save-baseline <change>`.
fn bench(extra: &[OsString]) -> ExitCode {
let mut args: Vec<OsString> = [
"bench",
"--locked",
"-p",
"smith-bench",
"--bench",
WALL_CLOCK_BENCH,
"--",
]
.map(OsString::from)
.to_vec();
args.extend_from_slice(without_separator(extra));
run_os("cargo", &args)
}
/// Build the release `smith` binary and return its absolute path.
fn release_smith() -> Result<PathBuf, ExitCode> {
let built = cargo(&[
"build",
"--locked",
"--release",
"-p",
"smith-cli",
"--bin",
"smith",
]);
if built != ExitCode::SUCCESS {
return Err(built);
}
let binary = target_dir().join("release").join(binary_name());
std::path::absolute(&binary)
.map_err(|error| fail(&format!("cannot resolve {}: {error}\n", binary.display())))
}
/// Run the gungraun benches against `smith` and collect every bench's
/// `Ir` from the JSON summary lines on standard output.
fn instruction_counts(
smith: &Path,
passthrough: &[OsString],
) -> Result<BTreeMap<String, u64>, String> {
let mut command = Command::new("cargo");
command.args(["bench", "--locked", "-p", "smith-bench"]);
for bench in INSTRUCTION_BENCHES {
command.args(["--bench", bench]);
}
command
// ASLR stays on: containers refuse the personality switch, and
// cache simulation is off, so instruction counts are unaffected.
.args([
"--",
"--save-summary=json",
"--output-format=json",
"--allow-aslr=true",
])
.args(passthrough)
.env("SMITH_BIN", smith)
.stderr(std::process::Stdio::inherit());
let output = command
.output()
.map_err(|error| format!("failed to run cargo bench: {error}\n"))?;
if !output.status.success() {
// Exit code 3 is gungraun's own regression verdict from limits
// passed through; anything else is a build or runner failure.
return Err(format!("cargo bench exited with {}\n", output.status));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut measured = BTreeMap::new();
for line in stdout.lines().filter(|line| line.starts_with('{')) {
let Some((bench, ir)) = bench_ir(line) else {
return Err(format!("unrecognized gungraun summary line: {line}\n"));
};
measured.insert(bench, ir);
}
if measured.is_empty() {
return Err("gungraun reported no benches\n".to_string());
}
Ok(measured)
}
/// `(<module_path>::<id>, Ir)` of one gungraun `--output-format=json`
/// line, accepting summary schemas v6 and v7. Only Callgrind data is evidence
/// for instruction budgets; v7 exposes its measured count at `values.new`.
fn bench_ir(line: &str) -> Option<(String, u64)> {
let summary: serde_json::Value = serde_json::from_str(line).ok()?;
let module = summary.get("module_path")?.as_str()?;
let id = summary.get("id")?.as_str()?;
let profiles = summary.get("profiles")?.as_array()?;
let count = profiles.iter().find_map(|profile| {
if profile.get("tool")?.as_str()? != "Callgrind" {
return None;
}
let total = profile
.get("data")
.and_then(|data| data.get("total"))
.or_else(|| {
profile
.get("summaries")
.and_then(|summaries| summaries.get("total"))
})?;
if let Some(values) = total.pointer("/metrics/Ir/values") {
return values.get("new").and_then(metric_count);
}
let ir = total.pointer("/summary/Callgrind/Ir")?;
metric_count(ir.get("metrics")?)
})?;
Some((format!("{module}::{id}"), count))
}
/// Current measured count from v7 `new` values or v6 first-run/repeated metrics.
fn metric_count(value: &serde_json::Value) -> Option<u64> {
value
.as_u64()
.or_else(|| value.get("Int")?.as_u64())
.or_else(|| value.as_array()?.first()?.get("Int")?.as_u64())
.or_else(|| value.get("Left")?.get("Int")?.as_u64())
.or_else(|| value.get("Both")?.as_array()?.first()?.get("Int")?.as_u64())
}
/// Pins plus model-floor mappings and non-empty task-complexity judgments.
#[derive(Default)]
struct BudgetFile {
pins: BTreeMap<String, u64>,
floors: BTreeMap<String, String>,
gaps: BTreeMap<String, String>,
}
/// Parse the narrow TOML subset Smith writes. Unknown tables and malformed or
/// duplicate fields fail rather than silently dropping budget metadata.
fn parse_budget_file(text: &str) -> Result<BudgetFile, String> {
let mut file = BudgetFile::default();
let mut table = None;
for (number, raw) in text.lines().enumerate() {
let line = raw.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
table = match line {
"[budgets]" => Some("budgets"),
"[floors]" => Some("floors"),
"[gaps]" => Some("gaps"),
_ if line.starts_with('[') => {
return Err(format!("{BUDGETS}:{}: unknown table {line}", number + 1));
}
_ => table,
};
if matches!(line, "[budgets]" | "[floors]" | "[gaps]") {
continue;
}
let (key, value) = quoted_assignment(line)
.ok_or_else(|| format!("{BUDGETS}:{}: expected quoted key assignment", number + 1))?;
match table {
Some("budgets") => {
let ir = budget_value(value)
.ok_or_else(|| format!("{BUDGETS}:{}: expected {{ ir = <n> }}", number + 1))?;
if file.pins.insert(key.to_string(), ir).is_some() {
return Err(format!("{BUDGETS}:{}: {key} pinned twice", number + 1));
}
}
Some("floors") => insert_metadata(&mut file.floors, key, value, number, "floor")?,
Some("gaps") => insert_metadata(&mut file.gaps, key, value, number, "gap")?,
None => return Err(format!("{BUDGETS}:{}: entry outside a table", number + 1)),
Some(_) => unreachable!(),
}
}
validate_metadata(&file)?;
Ok(file)
}
#[cfg(test)]
fn parse_budgets(text: &str) -> Result<BTreeMap<String, u64>, String> {
parse_budget_file(text).map(|file| file.pins)
}
fn quoted_assignment(line: &str) -> Option<(&str, &str)> {
let key_end = line.strip_prefix('"')?.find('"')? + 1;
let key = &line[1..key_end];
let value = line[key_end + 1..].trim_start().strip_prefix('=')?.trim();
Some((key, value))
}
fn budget_value(value: &str) -> Option<u64> {
let fields = value.strip_prefix('{')?.strip_suffix('}')?.trim();
fields
.strip_prefix("ir")?
.trim_start()
.strip_prefix('=')?
.trim()
.parse()
.ok()
}
fn insert_metadata(
values: &mut BTreeMap<String, String>,
key: &str,
value: &str,
number: usize,
kind: &str,
) -> Result<(), String> {
let value = quoted_string(value)
.ok_or_else(|| format!("{BUDGETS}:{}: expected quoted {kind}", number + 1))?;
if value.trim().is_empty() {
return Err(format!(
"{BUDGETS}:{}: {kind} for {key} is empty",
number + 1
));
}
if values.insert(key.to_string(), value).is_some() {
return Err(format!(
"{BUDGETS}:{}: {key} has {kind} metadata twice",
number + 1
));
}
Ok(())
}
fn quoted_string(value: &str) -> Option<String> {
let inner = value.strip_prefix('"')?.strip_suffix('"')?;
let mut out = String::new();
let mut escaped = false;
for character in inner.chars() {
if escaped {
out.push(match character {
'"' => '"',
'\\' => '\\',
'n' => '\n',
_ => return None,
});
escaped = false;
} else if character == '\\' {
escaped = true;
} else {
out.push(character);
}
}
(!escaped).then_some(out)
}
fn validate_metadata(file: &BudgetFile) -> Result<(), String> {
let mut used_floors = BTreeSet::new();
for (bench, floor) in &file.floors {
if bench == floor {
return Err(format!("{BUDGETS}: {bench} maps to itself"));
}
if !used_floors.insert(floor) {
return Err(format!(
"{BUDGETS}: floor {floor} maps from more than one bench"
));
}
if !file.gaps.contains_key(bench) {
return Err(format!(
"{BUDGETS}: floor mapping {bench} lacks a gap judgment"
));
}
}
Ok(())
}
/// The platform named in a budgets file header, if any.
fn pinned_platform(text: &str) -> Option<String> {
text.lines()
.find_map(|line| line.trim().strip_prefix("# Pinned with "))
.map(|rest| rest.trim_end_matches('.').to_string())
}
fn render_budgets(file: &BudgetFile, platform: &str) -> String {
use std::fmt::Write as _;
let mut out = format!(
"# Callgrind instruction budgets (Ir) per bench, gated within ±{BAND_PERCENT} % by `cargo x perf`.\n\
# Written by `cargo x perf --pin`; a re-pin is a deliberate commit that says why.\n\
# Pinned with {platform}.\n\n[budgets]\n"
);
for (bench, ir) in &file.pins {
let _ = writeln!(out, "\"{bench}\" = {{ ir = {ir} }}");
}
render_metadata(&mut out, "floors", &file.floors);
render_metadata(&mut out, "gaps", &file.gaps);
out
}
fn render_metadata(out: &mut String, table: &str, values: &BTreeMap<String, String>) {
use std::fmt::Write as _;
if values.is_empty() {
return;
}
let _ = writeln!(out, "\n[{table}]");
for (key, value) in values {
let _ = writeln!(out, "\"{key}\" = \"{}\"", escape_toml(value));
}
}
fn escape_toml(value: &str) -> String {
value
.replace('\\', "\\\\")
.replace('"', "\\\"")
.replace('\n', "\\n")
}
/// One line per bench outside its band, without a pin, or, when every
/// bench ran, pinned but no longer measured.
fn budget_violations(
measured: &BTreeMap<String, u64>,
pins: &BTreeMap<String, u64>,
complete: bool,
) -> Vec<String> {
let mut violations = Vec::new();
for (bench, ir) in measured {
match pins.get(bench) {
None => violations.push(format!(" {bench}: measured {ir}, no pin\n")),
Some(pin) if !within_band(*pin, *ir) => violations.push(format!(
" {bench}: pin {pin}, measured {ir}, delta {} (band ±{BAND_PERCENT} %)\n",
delta(*pin, *ir)
)),
Some(_) => {}
}
}
if complete {
for (bench, pin) in pins {
if !measured.contains_key(bench) {
violations.push(format!(" {bench}: pin {pin}, not measured (stale pin)\n"));
}
}
}
violations
}
fn within_band(pin: u64, ir: u64) -> bool {
u128::from(ir.abs_diff(pin)) * 100 <= u128::from(pin) * u128::from(BAND_PERCENT)
}
/// Measured implementation-to-model ratio, without a policy threshold.
fn ratio(implementation: u64, floor: u64) -> String {
if floor == 0 {
return "n/a (zero floor)".to_string();
}
let hundredths = u128::from(implementation) * 100 / u128::from(floor);
format!("{}.{:02}×", hundredths / 100, hundredths % 100)
}
/// Signed change of `ir` against `pin` in percent, two decimals.
fn delta(pin: u64, ir: u64) -> String {
if pin == 0 {
return "n/a".to_string();
}
let basis_points = (i128::from(ir) - i128::from(pin)) * 10_000 / i128::from(pin);
let sign = if basis_points < 0 { '-' } else { '+' };
let magnitude = basis_points.unsigned_abs();
format!("{sign}{}.{:02} %", magnitude / 100, magnitude % 100)
}
/// `target/report/perf.md`: every measured bench against its pin, with the
/// toolchain the counts belong to.
fn write_perf_report(
measured: &BTreeMap<String, u64>,
budgets: &BudgetFile,
complete: bool,
platform: &str,
) -> ExitCode {
use std::fmt::Write as _;
let mut table = format!(
"Callgrind instruction counts (`Ir`) against `{BUDGETS}`, band ±{BAND_PERCENT} %.\n\n\
Platform: `{platform}`.\n\n| bench | ir | pin | delta | floor | gap | judgment |\n|---|---|---|---|---|---|---|\n"
);
for (bench, ir) in measured {
let (pin, change) = budgets.pins.get(bench).map_or_else(
|| ("none".to_string(), "n/a".to_string()),
|pin| (pin.to_string(), delta(*pin, *ir)),
);
let floor = budgets
.floors
.get(bench)
.map_or("not applicable", String::as_str);
let gap = budgets.floors.get(bench).map_or_else(
|| "not applicable".to_string(),
|floor| {
measured.get(floor).map_or_else(
|| "not measured".to_string(),
|floor_ir| ratio(*ir, *floor_ir),
)
},
);
let judgment = budgets.gaps.get(bench).map_or("missing", String::as_str);
let _ = writeln!(
table,
"| `{bench}` | {ir} | {pin} | {change} | `{floor}` | {gap} | {judgment} |"
);
}
if complete {
for (bench, pin) in budgets
.pins
.iter()
.filter(|(bench, _)| !measured.contains_key(*bench))
{
let floor = budgets
.floors
.get(bench)
.map_or("not applicable", String::as_str);
let judgment = budgets.gaps.get(bench).map_or("missing", String::as_str);
let _ = writeln!(
table,
"| `{bench}` | not measured | {pin} | n/a | `{floor}` | not measured | {judgment} |"
);
}
}
let out = target_dir().join("report");
if fs::create_dir_all(&out).is_err() || fs::write(out.join("perf.md"), table).is_err() {
return fail("cannot write target/report/perf.md\n");
}
ExitCode::SUCCESS
}
/// What instruction counts depend on: the toolchain building the benches
/// and the libc they link, e.g. `rustc 1.98.1 (…) | glibc 2.41`.
fn platform() -> String {
format!("{} | {}", tool_first_line("rustc"), libc_version())
}
fn tool_first_line(program: &str) -> String {
Command::new(program)
.arg("--version")
.output()
.ok()
.filter(|output| output.status.success())
.and_then(|output| {
String::from_utf8_lossy(&output.stdout)
.lines()
.next()
.map(str::to_string)
})
.unwrap_or_else(|| format!("{program} version unavailable"))
}
/// `glibc <version>` from `ldd --version`, whose first line ends with the
/// version on every glibc distribution; other libcs report as unknown.
fn libc_version() -> String {
let line = tool_first_line("ldd");
line.rsplit(' ')
.next()
.filter(|last| last.starts_with(|c: char| c.is_ascii_digit()))
.map_or_else(
|| "libc unknown".to_string(),
|version| format!("glibc {version}"),
)
}
/// `extra` without one leading `--`: `cargo x cmd -- args` and
/// `cargo x cmd args` pass the same arguments on.
fn without_separator(extra: &[OsString]) -> &[OsString] {
match extra.split_first() {
Some((first, rest)) if first == "--" => rest,
_ => extra,
}
}
fn cargo_fmt_step(extra: &[OsString]) -> ExitCode {
let args = scoped(&["fmt", "--check"], extra);
run_os("cargo", &args)
}
fn cargo_clippy(extra: &[OsString]) -> ExitCode {
sequence(&[clippy_prod, clippy_tests, clippy_guests], extra)
}
/// Lint levels of every clippy scope: zero warnings, and clippy's default
/// groups (correctness, suspicious, style, complexity, perf) denied.
///
/// Lint levels live here, per scope, and not in `.cargo/config.toml`
/// rustflags: rustflags reach every rustc invocation, test and bench
/// builds included, so rustc lints there (`warnings`, `missing_docs`,
/// `unsafe_op_in_unsafe_fn`) could not relax outside production (RULES.md
/// Build gates, lint scopes). Bare `cargo clippy` runs default levels.
const SHARED_LINTS: &[&str] = &["-D", "warnings", "-D", "clippy::all"];
/// Levels production targets add to [`SHARED_LINTS`].
const PRODUCTION_LINTS: &[&str] = &[
"-D",
"clippy::pedantic",
// High-signal rule: pedantic members kept only when they catch real
// bugs far more often than they cry wolf. Demoted after observed
// false-positive friction:
"-A",
"clippy::module_name_repetitions",
"-A",
"clippy::must_use_candidate",
"-W",
"clippy::nursery",
// Restriction lints: prohibited patterns in shipped code.
"-D",
"clippy::unwrap_used",
"-D",
"clippy::expect_used",
"-D",
"clippy::panic",
"-D",
"clippy::todo",
"-D",
"clippy::unimplemented",
"-D",
"clippy::missing_assert_message",
"-D",
"clippy::disallowed_methods",
"-D",
"clippy::print_stdout",
"-D",
"clippy::print_stderr",
// Unsafe law (RULES.md Build gates): one documented operation per block.
"-D",
"clippy::undocumented_unsafe_blocks",
"-D",
"clippy::multiple_unsafe_ops_per_block",
"-D",
"clippy::missing_safety_doc",
"-D",
"unsafe_op_in_unsafe_fn",
// Memory law (RULES.md Memory): explicit sharing, no hidden copies.
"-D",
"clippy::clone_on_ref_ptr",
"-D",
"clippy::rc_buffer",
"-D",
"clippy::rc_mutex",
"-D",
"clippy::mutex_atomic",
"-D",
"clippy::mutex_integer",
"-D",
"clippy::redundant_clone",
"-D",
"clippy::implicit_clone",
"-D",
"clippy::inefficient_to_string",
"-D",
"clippy::large_stack_arrays",
"-D",
"clippy::large_types_passed_by_value",
"-D",
"clippy::let_underscore_must_use",
"-D",
"clippy::let_underscore_untyped",
"-D",
"clippy::trivially_copy_pass_by_ref",
"-D",
"clippy::needless_pass_by_value",
// Public docs required.
"-D",
"missing_docs",
];
/// Levels tests and benches, `smith-bench` included, add to
/// [`SHARED_LINTS`]: they may assert, panic, print, and skip docs, so
/// pedantic, nursery, restriction, memory-law, and doc lints stay at their
/// default, allow; the unsafe lints that warn by default are allowed, since
/// unsafe rules gate shipped code only.
const NON_PRODUCTION_LINTS: &[&str] = &[
"-A",
"clippy::disallowed_methods",
"-A",
"clippy::let_underscore_must_use",
"-A",
"clippy::let_underscore_untyped",
"-A",
"clippy::missing_assert_message",
"-A",
"unsafe_op_in_unsafe_fn",
"-A",
"clippy::missing_safety_doc",
];
/// The wasm guest crate, linted on its own target by [`clippy_guests`].
const GUEST: &str = "smith-plugin-echo";
/// Production targets carry the strict gate.
fn clippy_prod(extra: &[OsString]) -> ExitCode {
prod_targets(extra).map_or(ExitCode::SUCCESS, |targets| {
clippy_phase(&targets, &[SHARED_LINTS, PRODUCTION_LINTS], extra)
})
}
/// Target flags of the production phase: `--lib` and `--bins` of every
/// shipped host crate; `smith-bench` and `xtask` lint with tests as
/// non-production tooling. A scoped package gets the flags it actually has,
/// since cargo rejects a target flag the package cannot satisfy; `None` for
/// non-production tooling.
fn prod_targets(extra: &[OsString]) -> Option<Vec<&'static str>> {
let Some(package) = selected_package(extra) else {
return Some(vec![
"--workspace",
"--exclude",
"smith-bench",
"--exclude",
"xtask",
"--exclude",
GUEST,
"--lib",
"--bins",
]);
};
if matches!(package.as_str(), "smith-bench" | "xtask") {
return None;
}
let src = Path::new(&package).join("src");
let mut targets = Vec::new();
if src.join("lib.rs").is_file() {
targets.push("--lib");
}
if src.join("main.rs").is_file() || src.join("bin").is_dir() {
targets.push("--bins");
}
if targets.is_empty() {
targets = vec!["--lib", "--bins"];
}
Some(targets)
}
/// The first package the passthrough arguments select.
fn selected_package(extra: &[OsString]) -> Option<String> {
extra.iter().enumerate().find_map(|(at, arg)| {
let arg = arg.to_string_lossy();
match arg.strip_prefix("--package=") {
Some(name) => Some(name.to_string()),
None if arg == "-p" || arg == "--package" => extra
.get(at + 1)
.map(|name| name.to_string_lossy().into_owned()),
None => None,
}
})
}
/// Test and bench targets, the library of `smith-bench` with them, lint
/// at the non-production levels.
fn clippy_tests(extra: &[OsString]) -> ExitCode {
clippy_phase(
&["--tests", "--benches"],
&[SHARED_LINTS, NON_PRODUCTION_LINTS],
extra,
)
}
/// Guest crates stay strict, cross-checked on their wasm target; the
/// crate-level expectations on generated surfaces carry the exceptions.
fn clippy_guests(extra: &[OsString]) -> ExitCode {
if narrows(extra) {
return ExitCode::SUCCESS;
}
clippy_phase(
&["-p", GUEST, "--target", "wasm32-wasip2", "--lib"],
&[SHARED_LINTS, PRODUCTION_LINTS],
extra,
)
}
fn clippy_phase(targets: &[&str], lints: &[&[&str]], extra: &[OsString]) -> ExitCode {
let mut base = vec!["clippy", "--locked"];
base.extend_from_slice(targets);
base.push("--all-features");
let mut command = Command::new("cargo");
command
.env("CLIPPY_CONF_DIR", ".config")
.args(scoped(&base, extra))
.arg("--");
for levels in lints {
command.args(*levels);
}
match command.status() {
Ok(status) if status.success() => ExitCode::SUCCESS,
Ok(status) => fail(&format!("cargo clippy exited with {status}\n")),
Err(error) => fail(&format!("failed to run cargo clippy: {error}\n")),
}
}
fn cargo_nextest(extra: &[OsString]) -> ExitCode {
let args = scoped(&["--locked", "--workspace", "--all-features"], extra);
nextest_run_os(&args)
}
/// End-to-end tier: user-visible workflows through the built Smith binary; test
/// binaries named `e2e_*`, selected by the `e2e` nextest profile.
fn cargo_e2e(extra: &[OsString]) -> ExitCode {
let args = scoped(
&[
"--locked",
"--workspace",
"--all-features",
"--profile",
"e2e",
],
extra,
);
nextest_run_os(&args)
}
/// Coverage over every tier; informational, never a percentage gate.
/// Writes `target/coverage/lcov.info` and prints the per-file summary.
fn cargo_coverage(extra: &[OsString]) -> ExitCode {
let ran = coverage_tiers(extra);
if ran != ExitCode::SUCCESS {
return ran;
}
let lcov = target_dir().join("coverage").join("lcov.info");
let wrote = coverage_report(&["--lcov", "--output-path"], &lcov);
if wrote != ExitCode::SUCCESS {
return wrote;
}
run_os(
"cargo",
&["llvm-cov", "report", "--summary-only"].map(OsString::from),
)
}
/// Run both nextest tiers instrumented, accumulating one profile set.
fn coverage_tiers(extra: &[OsString]) -> ExitCode {
let installed = install_llvm_cov(&[]);
if installed != ExitCode::SUCCESS {
return installed;
}
if std::fs::create_dir_all(target_dir().join("coverage")).is_err() {
return fail("cannot create target/coverage\n");
}
let tiers: [&[&str]; 2] = [&[], &["--profile", "e2e"]];
let base = [
"llvm-cov",
"nextest",
"--no-report",
"--locked",
"--workspace",
"--all-features",
];
let clean = run_os(
"cargo",
&["llvm-cov", "clean", "--workspace"].map(OsString::from),
);
if clean != ExitCode::SUCCESS {
return clean;
}
for tier in tiers {
let mut args: Vec<&str> = base.to_vec();
args.extend_from_slice(tier);
// A tier with no matching tests is a report, not a failure.
args.push("--no-tests=warn");
let ran = run_os("cargo", &scoped(&args, extra));
if ran != ExitCode::SUCCESS {
return ran;
}
}
ExitCode::SUCCESS
}
/// One `cargo llvm-cov report` invocation with an output path.
fn coverage_report(flags: &[&str], output: &Path) -> ExitCode {
let mut args: Vec<OsString> = ["llvm-cov", "report"].map(OsString::from).to_vec();
args.extend(flags.iter().map(OsString::from));
args.push(output.as_os_str().to_owned());
run_os("cargo", &args)
}
/// Every tier's evidence in one bundle under `target/report/`: `JUnit` per
/// tier, lcov plus HTML coverage, the last mutation run, the last
/// instruction-budget table, hyperfine wall-clock rows, and an `index.md`
/// that summarizes them. Mutation and perf results are read, never run
/// here; `mutants` and `perf` run first. Passthrough scopes the test tiers.
fn report(extra: &[OsString]) -> ExitCode {
report_with_mutants(extra, &target_dir().join("mutants.out"))
}
/// Build the normal report with mutation evidence from the chosen run.
fn report_with_mutants(extra: &[OsString], mutants: &Path) -> ExitCode {
use std::fmt::Write as _;
let ran = coverage_tiers(extra);
if ran != ExitCode::SUCCESS {
return ran;
}
let out = target_dir().join("report");
// `cargo x perf` writes into the bundle directory; keep its table
// across the reset.
let perf = fs::read_to_string(out.join("perf.md")).ok();
let _ = std::fs::remove_dir_all(&out);
if std::fs::create_dir_all(out.join("junit")).is_err() {
return fail("cannot create target/report\n");
}
let lcov = coverage_report(&["--lcov", "--output-path"], &out.join("lcov.info"));
if lcov != ExitCode::SUCCESS {
return lcov;
}
let html = coverage_report(&["--html", "--output-dir"], &out.join("coverage"));
if html != ExitCode::SUCCESS {
return html;
}
let summary = Command::new("cargo")
.args(["llvm-cov", "report", "--summary-only"])
.output()
.map_or_else(
|error| format!("summary unavailable: {error}"),
|output| String::from_utf8_lossy(&output.stdout).into_owned(),
);
let mut index = String::from("# Smith test report\n\n");
index.push_str("## Tiers\n\n| tier | tests | failures | errors |\n|---|---|---|---|\n");
for tier in ["default", "e2e"] {
// nextest keeps its store under the workspace target dir even when
// llvm-cov redirects build output.
let source = target_dir().join("nextest").join(tier).join("junit.xml");
let copied = out.join("junit").join(format!("{tier}.xml"));
let row = std::fs::read_to_string(&source).map_or_else(
|_| format!("| {tier} | no junit.xml | | |\n"),
|xml| {
let _ = std::fs::write(&copied, &xml);
let [tests, failures, errors] = junit_counts(&xml);
format!("| {tier} | {tests} | {failures} | {errors} |\n")
},
);
index.push_str(&row);
}
index.push_str(
"\n## Coverage\n\nHTML: `coverage/html/index.html`; lcov: `lcov.info`.\n\n```text\n",
);
index.push_str(summary.trim_end());
index.push_str("\n```\n\n## Mutation\n\n");
match mutants_counts(mutants) {
Some([caught, missed, timeout, unviable]) => {
let _ = write!(
index,
"| caught | missed | timeout | unviable |\n|---|---|---|---|\n| {caught} | {missed} | {timeout} | {unviable} |\n"
);
for name in ["missed.txt", "caught.txt", "timeout.txt", "unviable.txt"] {
let _ = std::fs::copy(mutants.join(name), out.join(name));
}
if missed > 0 {
index.push_str("\nMissed mutants are the test backlog; see `missed.txt`.\n");
}
}
None => index.push_str("No mutation run found; run `cargo x mutants` first.\n"),
}
index.push_str("\n## Instructions\n\n");
match perf {
Some(table) => {
let _ = std::fs::write(out.join("perf.md"), &table);
index.push_str(&table);
}
None => index.push_str("No perf run found; run `cargo x perf` first.\n"),
}
index.push_str("\n## Wall clock\n\n");
index.push_str(&wall_clock(&out));
if std::fs::write(out.join("index.md"), index).is_err() {
return fail("cannot write target/report/index.md\n");
}
let _ignored = std::io::stdout().write_all(b"report: target/report/index.md\n");
ExitCode::SUCCESS
}
/// Wall-clock rows of the release binary through hyperfine, isolated like
/// the e2e tests. Informational only: the numbers hold for this machine
/// and this run, so a missing tool or a failed run is a note, never a
/// report failure.
fn wall_clock(out: &Path) -> String {
if !present("hyperfine") {
return "hyperfine not on PATH; no wall-clock rows.\n".to_string();
}
let Ok(smith) = release_smith() else {
return "release build of smith failed; no wall-clock rows.\n".to_string();
};
let home = target_dir().join("wall-clock-home");
let markdown = out.join("hyperfine.md");
let mut command = Command::new("hyperfine");
command
.args(["--warmup", "3", "--runs", "20", "--export-json"])
.arg(out.join("hyperfine.json"))
.arg("--export-markdown")
.arg(&markdown)
// A fresh profile per run: `eval` appends to its session otherwise.
.arg("--prepare")
.arg(format!("rm -rf '{}'", home.display()))
.args(["--command-name", "smith --help"])
.args(["--command-name", "smith eval --mock hello"])
.arg(format!("'{}' --help", smith.display()))
.arg(format!("'{}' eval --mock hello", smith.display()));
for (key, dir) in dev_home_dirs() {
command.env(key, home.join(dir));
}
for var in PROVIDER_VARS {
command.env_remove(var);
}
let ran = command.status().is_ok_and(|status| status.success());
match fs::read_to_string(&markdown) {
Ok(rows) if ran => format!(
"Informational, never a gate: {}-{}, {} hardware threads, this run only; raw data in `hyperfine.json`.\n\n{rows}",
env::consts::OS,
env::consts::ARCH,
std::thread::available_parallelism().map_or(0, std::num::NonZero::get),
),
_ => "hyperfine run failed; no wall-clock rows.\n".to_string(),
}
}
/// Whether `program --version` runs and succeeds.
fn present(program: &str) -> bool {
Command::new(program)
.arg("--version")
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()
.is_ok_and(|status| status.success())
}
/// `tests`, `failures`, `errors` from a `JUnit` `<testsuites>` root.
fn junit_counts(xml: &str) -> [u64; 3] {
let root = xml
.find("<testsuites")
.map(|at| &xml[at..])
.and_then(|rest| rest.find('>').map(|end| &rest[..end]))
.unwrap_or("");
["tests", "failures", "errors"].map(|attribute| {
let key = format!(" {attribute}=\"");
root.find(&key)
.map(|at| &root[at + key.len()..])
.and_then(|rest| rest.split('"').next())
.and_then(|value| value.parse().ok())
.unwrap_or(0)
})
}
/// Line counts of a cargo-mutants outcome directory: caught, missed,
/// timeout, unviable; `None` without a run.
fn mutants_counts(dir: &Path) -> Option<[usize; 4]> {
let count = |name: &str| {
std::fs::read_to_string(dir.join(name))
.map(|text| text.lines().filter(|line| !line.trim().is_empty()).count())
};
Some([
count("caught.txt").ok()?,
count("missed.txt").ok()?,
count("timeout.txt").unwrap_or(0),
count("unviable.txt").unwrap_or(0),
])
}
/// nextest has no warning-deny flag, so config-drift warnings are turned into
/// a gate failure by scanning its stderr.
fn nextest_run_os(args: &[OsString]) -> ExitCode {
let mut child = match Command::new("cargo")
.arg("nextest")
.arg("run")
.args(args)
.stdout(std::process::Stdio::inherit())
.stderr(std::process::Stdio::piped())
.spawn()
{
Ok(child) => child,
Err(error) => return fail(&format!("failed to run cargo nextest: {error}\n")),
};
let mut warned = false;
if let Some(stderr) = child.stderr.take() {
let reader = std::io::BufReader::new(stderr);
for line in reader.lines() {
let Ok(line) = line else {
break;
};
if line.contains("warning: in config file") {
warned = true;
}
let _ = std::io::stderr().write_all(format!("{line}\n").as_bytes());
}
}
match child.wait() {
Ok(status) if status.success() && !warned => ExitCode::SUCCESS,
Ok(status) if status.success() => fail("nextest produced config file warnings\n"),
Ok(status) => fail(&format!(
"cargo exited with exit status: {}\n",
status.code().unwrap_or_default()
)),
Err(error) => fail(&format!("failed to wait for cargo nextest: {error}\n")),
}
}
fn cargo_machete(_extra: &[OsString]) -> ExitCode {
let installed = tool_present_bin("cargo-machete", MACHETE_VERSION, || {
cargo(&[
"install",
"--locked",
"--version",
MACHETE_VERSION,
"cargo-machete",
])
});
if installed != ExitCode::SUCCESS {
return installed;
}
run("cargo-machete", &[])
}
/// Install the external gate tools that Cargo cannot ship.
///
/// `cargo-nextest` arrives as a prebuilt binary through `cargo-binstall`.
/// The step is idempotent.
/// Installs tools; scoping does not apply.
fn bootstrap(extra: &[OsString]) -> ExitCode {
if !extra.is_empty() {
return fail("bootstrap installs tools and admits no scoping\n");
}
let steps: [Step; 9] = [
install_binstall,
install_nextest,
install_machete,
install_deny,
install_audit,
install_mutants,
install_llvm_cov,
install_insta,
install_gungraun_runner,
];
sequence(&steps, extra)
}
fn install_machete(_extra: &[OsString]) -> ExitCode {
tool_present_bin("cargo-machete", MACHETE_VERSION, || {
cargo(&[
"install",
"--locked",
"--version",
MACHETE_VERSION,
"cargo-machete",
])
})
}
fn install_deny(_extra: &[OsString]) -> ExitCode {
binstall("cargo-deny", DENY_VERSION)
}
fn install_audit(_extra: &[OsString]) -> ExitCode {
binstall("cargo-audit", AUDIT_VERSION)
}
fn install_mutants(_extra: &[OsString]) -> ExitCode {
binstall("cargo-mutants", MUTANTS_VERSION)
}
fn install_gungraun_runner(_extra: &[OsString]) -> ExitCode {
binstall("gungraun-runner", GUNGRAUN_VERSION)
}
/// `cargo insta review` accepts changed snapshots; optional, like the rest.
fn install_insta(_extra: &[OsString]) -> ExitCode {
tool_present_bin("cargo-insta", INSTA_VERSION, || {
cargo(&[
"binstall",
"--no-confirm",
&format!("cargo-insta@{INSTA_VERSION}"),
])
})
}
fn install_llvm_cov(_extra: &[OsString]) -> ExitCode {
let tool = binstall("cargo-llvm-cov", LLVM_COV_VERSION);
if tool != ExitCode::SUCCESS {
return tool;
}
run_os(
"rustup",
&["component", "add", "llvm-tools"].map(OsString::from),
)
}
fn install_binstall(_extra: &[OsString]) -> ExitCode {
tool_present_bin("cargo-binstall", BINSTALL_VERSION, || {
cargo(&[
"install",
"cargo-binstall",
"--version",
BINSTALL_VERSION,
// Freeze binstall's own dependency graph for the same reason
// crates.io drift cannot break the gate.
"--locked",
])
})
}
fn install_nextest(_extra: &[OsString]) -> ExitCode {
binstall("cargo-nextest", NEXTEST_VERSION)
}
/// Install the prebuilt `tool` at `version` through `cargo-binstall`
/// unless that version is already on PATH.
fn binstall(tool: &str, version: &str) -> ExitCode {
tool_present_bin(tool, version, || {
cargo(&["binstall", "--no-confirm", &format!("{tool}@{version}")])
})
}
/// Run `probe` with cargo; when it fails, run `install` and return its code.
/// The probe pins the tool so a stale unpinned binary is replaced.
/// Probes an external binary directly and enforces the pinned version;
/// mismatch or absence runs the installer, so drift self-heals to the pin.
fn tool_present_bin(program: &str, version: &str, install: impl FnOnce() -> ExitCode) -> ExitCode {
let probe = Command::new(program)
.arg("--version")
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.output();
let pinned = probe.is_ok_and(|output| {
output.status.success() && String::from_utf8_lossy(&output.stdout).contains(version)
});
if pinned { ExitCode::SUCCESS } else { install() }
}
/// Rustdoc lint levels: zero warnings, crate-level docs required. Cargo
/// hands rustflags to rustc only, never to rustdoc; `RUSTDOCFLAGS`
/// replaces these.
const DOC_LINTS: &str =
r#"build.rustdocflags=["-D", "warnings", "-D", "rustdoc::missing_crate_level_docs"]"#;
fn cargo_doc(extra: &[OsString]) -> ExitCode {
let args = scoped(
&[
"doc",
"--locked",
"--workspace",
"--no-deps",
"--config",
DOC_LINTS,
],
extra,
);
run_os("cargo", &args)
}
fn cargo_runtime_duplicates(_extra: &[OsString]) -> ExitCode {
let output = match Command::new("cargo")
.args(RUNTIME_DUPLICATE_COMMAND)
.output()
{
Ok(output) => output,
Err(error) => return fail(&format!("failed to run cargo tree: {error}\n")),
};
if !output.status.success() {
let _ignored = std::io::stderr().write_all(&output.stdout);
let _ignored = std::io::stderr().write_all(&output.stderr);
return fail(&format!("cargo tree exited with {}\n", output.status));
}
if let Some(warning) = runtime_duplicate_warning(&String::from_utf8_lossy(&output.stdout)) {
note(&warning);
}
ExitCode::SUCCESS
}
fn runtime_duplicate_warning(tree: &str) -> Option<String> {
let mut versions = BTreeMap::<&str, BTreeSet<&str>>::new();
for (package, version) in tree.lines().filter_map(package_version) {
versions.entry(package).or_default().insert(version);
}
let duplicates: Vec<_> = versions
.into_iter()
.filter(|(_, versions)| versions.len() > 1)
.flat_map(|(package, versions)| versions.into_iter().map(move |version| (package, version)))
.collect();
(!duplicates.is_empty()).then(|| {
let mut warning = String::from("warning: runtime dependency duplicate versions:\n");
for (package, version) in duplicates {
warning.push_str(" ");
warning.push_str(package);
warning.push(' ');
warning.push_str(version);
warning.push('\n');
}
warning
})
}
fn package_version(line: &str) -> Option<(&str, &str)> {
let mut fields = line.split_ascii_whitespace();
let (package, version) = (fields.next()?, fields.next()?);
version
.strip_prefix('v')
.is_some_and(|version| version.starts_with(|character: char| character.is_ascii_digit()))
.then_some((package, version))
}
fn cargo_deny(_extra: &[OsString]) -> ExitCode {
cargo(FULL_DENY_COMMAND)
}
fn cargo_audit(_extra: &[OsString]) -> ExitCode {
cargo(&["audit", "--deny", "warnings"])
}
fn cargo(args: &[&str]) -> ExitCode {
run("cargo", args)
}
/// Builds the CLI, then launches it against an isolated dev home.
fn dev(args: &[OsString]) -> ExitCode {
let code = cargo(&["build", "--locked", "-p", "smith-cli", "--bin", "smith"]);
if code != ExitCode::SUCCESS {
return code;
}
let target = target_dir();
let binary = target.join("debug").join(binary_name());
if !binary.is_file() {
return fail(&format!("missing dev binary: {}\n", binary.display()));
}
let home = target.join("dev-home");
let mut command = Command::new(&binary);
for (key, dir) in dev_home_dirs() {
let path = home.join(dir);
if let Err(error) = fs::create_dir_all(&path) {
return fail(&format!("failed to create {}: {error}\n", path.display()));
}
command.env(key, &path);
}
command.args(args);
note(&format!(
"running {} (home {})\n",
binary.display(),
home.display()
));
match command.status() {
Ok(status) => match status.code() {
Some(0) => ExitCode::SUCCESS,
Some(value) => ExitCode::from(u8::try_from(value).unwrap_or(1)),
None => fail("smith terminated by signal\n"),
},
Err(error) => fail(&format!("failed to run {}: {error}\n", binary.display())),
}
}
/// Environment overrides that keep dev runs out of the real user profile.
const fn dev_home_dirs() -> &'static [(&'static str, &'static str)] {
&[
("XDG_CONFIG_HOME", "config"),
("XDG_DATA_HOME", "data"),
("XDG_STATE_HOME", "state"),
("XDG_CACHE_HOME", "cache"),
]
}
fn target_dir() -> PathBuf {
env::var_os("CARGO_TARGET_DIR").map_or_else(|| PathBuf::from("target"), PathBuf::from)
}
const fn binary_name() -> &'static str {
if cfg!(windows) { "smith.exe" } else { "smith" }
}
fn note(message: &str) {
let _ignored = std::io::stderr().write_all(message.as_bytes());
}
fn run_os(program: &str, args: &[OsString]) -> ExitCode {
run_command(Command::new(program).args(args))
}
fn run(program: &str, args: &[&str]) -> ExitCode {
run_command(Command::new(program).args(args))
}
fn run_command(command: &mut Command) -> ExitCode {
let program = command.get_program().to_string_lossy().into_owned();
match command.status() {
Ok(status) if status.success() => ExitCode::SUCCESS,
Ok(status) => fail(&format!("{program} exited with {status}\n")),
Err(error) => fail(&format!("failed to run {program}: {error}\n")),
}
}
fn arch(extra: &[OsString]) -> ExitCode {
let wanted: Vec<String> = extra
.iter()
.map(|arg| arg.to_string_lossy().into_owned())
.collect();
for crate_name in CRATES {
if !Path::new(crate_name).join("Cargo.toml").is_file() {
return fail(&format!("missing crate manifest: {crate_name}\n"));
}
}
for rule in DEPENDENCY_RULES {
if !wanted.is_empty() && !wanted.iter().any(|name| name == rule.crate_name) {
continue;
}
let manifest = match fs::read_to_string(Path::new(rule.crate_name).join("Cargo.toml")) {
Ok(value) => value,
Err(error) => {
return fail(&format!(
"failed to read {} Cargo.toml: {error}\n",
rule.crate_name
));
}
};
let denied = CRATES
.iter()
.filter(|name| **name != rule.crate_name && !rule.allowed.contains(name));
for denied in denied {
if has_dependency(&manifest, denied) {
return fail(&format!(
"{} must not depend on {denied}\n",
rule.crate_name
));
}
}
if rule.crate_name != "smith-bench" && names_dependency(&manifest, "smith-bench") {
return fail(&format!(
"{} must not depend on smith-bench, not even from tests\n",
rule.crate_name
));
}
if LIBRARY_CRATES.contains(&rule.crate_name)
&& let Some(offender) = mutex_in_production(Path::new(rule.crate_name))
{
return fail(&format!(
"{}: Mutex in library code (concurrency law): {}\n",
rule.crate_name,
offender.display()
));
}
}
let violations = unsafe_law_violations(Path::new(""));
if !violations.is_empty() {
return fail(&violations.concat());
}
let facade_violations: Vec<String> = CRATES
.iter()
.filter(|crate_name| !UNSHIPPED.contains(crate_name))
.flat_map(|crate_name| facade_violations_for_crate(Path::new(crate_name)))
.collect();
if !facade_violations.is_empty() {
return fail(&facade_violations.concat());
}
let naming_violations: Vec<String> = host_sources(Path::new(""))
.iter()
.flat_map(|(_, path, source)| naming_violations(path, source))
.collect();
if !naming_violations.is_empty() {
return fail(&naming_violations.concat());
}
if let Some(offender) = missing_spec_reference() {
return fail(&format!(
"{}: test code without an SMH-SPEC- citation in its first 20 lines or `//!` header (RULES.md Testing)\n",
offender.display()
));
}
ExitCode::SUCCESS
}
/// Workspace vocabulary is shared terminology, not an implementation boundary.
const VOCABULARY_DEPENDENCIES: &[&str] = &[
"serde",
"serde_json",
"futures",
"thiserror",
"tracing",
"url",
];
/// One non-vocabulary external dependency may occur in one meaningful source
/// module per crate.
///
/// The scanner reads normal manifest dependencies, strips test items with the
/// existing architecture helper, then recognizes imports and qualified paths.
/// It deliberately ignores comments and literals; macro expansion is outside
/// this source-level boundary. `wasm.rs` and `wasm/` are one module, so engine
/// internals remain together. Guest crates are isolated by the `smith:plugin`
/// WIT seam and are not host facades.
fn facade_violations_for_crate(crate_dir: &Path) -> Vec<String> {
let Ok(manifest) = fs::read_to_string(crate_dir.join("Cargo.toml")) else {
return Vec::new();
};
let dependencies = manifest_dependencies_with_workspace(crate_dir, &manifest);
let mut uses: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
let mut files = Vec::new();
collect_rs_files(&crate_dir.join("src"), &mut files);
for path in files {
let Ok(source) = fs::read_to_string(&path) else {
continue;
};
let module = facade_module(crate_dir, &path);
for dependency in &dependencies {
if source_names_dependency(&without_test_items(&source), dependency) {
uses.entry(dependency.name.clone())
.or_default()
.insert(module.clone());
}
}
}
uses.into_iter()
.filter_map(|(dependency, modules)| {
let paths: Vec<&str> = modules.iter().map(String::as_str).collect();
if modules.len() == 1 && paths[0] != "crate root" {
return None;
}
let where_used = paths.join(", ");
let cause = if paths.as_slice() == ["crate root"] {
"crate root is not a facade"
} else {
"dependency is named by multiple modules"
};
Some(format!(
"{}: external dependency `{dependency}` used in {where_used}: {cause}; move all `{dependency}` operations behind one facade module exposing Smith-shaped types\n",
crate_dir.display()
))
})
.collect()
}
/// External dependencies of this crate, including externally-owned workspace
/// dependencies inherited from the nearest workspace manifest.
fn manifest_dependencies_with_workspace(
crate_dir: &Path,
manifest: &str,
) -> Vec<ManifestDependency> {
let mut dependencies = manifest_dependencies(manifest);
let workspace = crate_dir
.parent()
.map_or_else(BTreeMap::new, workspace_dependencies);
for key in inherited_dependency_keys(manifest) {
if let Some(dependency) = workspace.get(&key) {
dependencies.push(dependency.clone());
}
}
dependencies.sort_by(|left, right| left.source_name.cmp(&right.source_name));
dependencies.dedup_by(|left, right| left.source_name == right.source_name);
dependencies
}
/// External workspace dependencies keyed by their member-manifest source name.
/// This reuses the normal dependency scanner after narrowing its one accepted
/// table name; workspace members and path dependencies remain excluded.
fn workspace_dependencies(crate_dir: &Path) -> BTreeMap<String, ManifestDependency> {
let Ok(manifest) = fs::read_to_string(crate_dir.join("Cargo.toml")) else {
return BTreeMap::new();
};
manifest_dependencies(&manifest.replace("workspace.dependencies", "dependencies"))
.into_iter()
.map(|dependency| (dependency.source_name.clone(), dependency))
.collect()
}
/// Member-manifest dependency keys whose values inherit from the workspace.
/// Supports inline, dotted-table, and target dependency tables; TOML syntax
/// beyond these dependency forms remains outside this source-level scanner.
fn inherited_dependency_keys(manifest: &str) -> BTreeSet<String> {
let mut keys = BTreeSet::new();
let mut inline_dependencies = false;
let mut table_dependency = None;
for line in manifest.lines().map(str::trim) {
if line.starts_with('[') && line.ends_with(']') {
let section = &line[1..line.len() - 1];
inline_dependencies = section == "dependencies"
|| (section.starts_with("target.") && section.ends_with(".dependencies"));
table_dependency = section
.strip_prefix("dependencies.")
.or_else(|| section.split_once(".dependencies.").map(|(_, name)| name))
.map(|name| name.trim_matches('\'').replace('-', "_"));
continue;
}
if !line.contains("workspace = true") {
continue;
}
if let Some(key) = &table_dependency {
keys.insert(key.clone());
} else if inline_dependencies && let Some((key, _)) = line.split_once('=') {
keys.insert(key.trim().replace('-', "_"));
}
}
keys
}
/// A normal, non-workspace third-party dependency from `[dependencies]`.
/// Inline package aliases are preserved so `foo = { package = "bar" }` scans
/// source as `foo`, while diagnostics name `bar`.
fn manifest_dependencies(manifest: &str) -> Vec<ManifestDependency> {
let mut dependencies = Vec::new();
let mut in_dependencies = false;
let mut dependency_table: Option<String> = None;
let mut pending: Option<(String, String)> = None;
for line in manifest.lines().map(str::trim) {
if line.starts_with('[') && line.ends_with(']') {
if let Some(name) = dependency_table.take() {
add_manifest_dependency(&mut dependencies, &name, "");
}
if let Some((key, value)) = pending.take() {
add_manifest_dependency(&mut dependencies, &key, &value);
}
let section = &line[1..line.len() - 1];
dependency_table = section
.strip_prefix("dependencies.")
.map(|name| name.trim_matches('\'').to_owned());
in_dependencies = section == "dependencies"
|| (section.starts_with("target.") && section.ends_with(".dependencies"));
continue;
}
if line.is_empty() || line.starts_with('#') {
continue;
}
if let Some(name) = &dependency_table {
if line.contains("workspace = true") || line.starts_with("path =") {
dependency_table = None;
} else if let Some((_, value)) = line.split_once('=')
&& line.starts_with("package")
&& let Some(package) = quoted_value(value)
{
dependencies.push(ManifestDependency {
name: package,
source_name: name.replace('-', "_"),
});
dependency_table = None;
}
continue;
}
if !in_dependencies {
continue;
}
if pending.is_none() {
let Some((key, value)) = line.split_once('=') else {
continue;
};
pending = Some((key.trim().to_owned(), value.trim().to_owned()));
} else if let Some((_, value)) = &mut pending {
value.push(' ');
value.push_str(line);
}
if let Some((_, value)) = &pending
&& (!value.contains('{') || value.matches('{').count() == value.matches('}').count())
{
let (key, value) = pending.take().expect("pending dependency exists");
add_manifest_dependency(&mut dependencies, &key, &value);
}
}
if let Some((key, value)) = pending {
add_manifest_dependency(&mut dependencies, &key, &value);
}
if let Some(name) = dependency_table {
add_manifest_dependency(&mut dependencies, &name, "");
}
dependencies
}
fn add_manifest_dependency(dependencies: &mut Vec<ManifestDependency>, key: &str, value: &str) {
if key.is_empty() || value.contains("path =") || value.contains("workspace = true") {
return;
}
let package = value
.split_once("package")
.and_then(|(_, tail)| tail.split_once('='))
.and_then(|(_, value)| quoted_value(value))
.unwrap_or_else(|| key.to_owned());
if !VOCABULARY_DEPENDENCIES.contains(&package.as_str()) {
dependencies.push(ManifestDependency {
name: package,
source_name: key.replace('-', "_"),
});
}
}
fn quoted_value(value: &str) -> Option<String> {
Some(
value
.trim()
.strip_prefix('"')?
.split_once('"')?
.0
.to_owned(),
)
}
#[derive(Clone)]
struct ManifestDependency {
name: String,
source_name: String,
}
/// Module identity relative to `src`; nested files group under their first
/// top-level module path, matching that module's `foo.rs` and `foo/` forms.
fn facade_module(crate_dir: &Path, path: &Path) -> String {
let source = crate_dir.join("src");
let relative = path.strip_prefix(&source).unwrap_or(path);
if relative == Path::new("lib.rs") || relative == Path::new("main.rs") {
return "crate root".to_owned();
}
let Some(first) = relative.components().next() else {
return "crate root".to_owned();
};
let module = if relative.components().count() > 1 {
Path::new(first.as_os_str())
.file_stem()
.unwrap_or(first.as_os_str())
} else {
relative.file_stem().unwrap_or_default()
};
format!("src/{}", module.to_string_lossy())
}
/// Whether production source imports or qualifies `dependency`, including a
/// local alias. Tokenization is intentionally small: identifiers and `::`
/// are enough for Rust paths after comments and literals are blanked.
fn source_names_dependency(source: &str, dependency: &ManifestDependency) -> bool {
let tokens = rust_path_tokens(&source_without_comments_and_literals(source));
let mut aliases = BTreeSet::new();
let mut direct = false;
for index in 0..tokens.len() {
if tokens[index] != "use"
|| tokens
.get(index + 1)
.is_none_or(|token| token != &dependency.source_name)
{
continue;
}
direct = true;
let end = tokens[index..]
.iter()
.position(|token| token == ";")
.map_or(tokens.len(), |offset| index + offset);
for pair in tokens[index..end].windows(2) {
if pair[0] == "as" {
aliases.insert(pair[1].clone());
}
}
}
direct
|| tokens
.windows(3)
.any(|path| path[0] == dependency.source_name && path[1] == ":" && path[2] == ":")
|| tokens
.windows(3)
.any(|path| aliases.contains(&path[0]) && path[1] == ":" && path[2] == ":")
}
/// Tokens relevant to path scanning after strings and comments become spaces.
fn rust_path_tokens(source: &str) -> Vec<String> {
let mut tokens = Vec::new();
let mut current = String::new();
for character in source.chars() {
if character.is_ascii_alphanumeric() || character == '_' {
current.push(character);
} else {
if !current.is_empty() {
tokens.push(std::mem::take(&mut current));
}
if character == ':' {
tokens.push(":".to_owned());
} else if character == ';' {
tokens.push(";".to_owned());
}
}
}
if !current.is_empty() {
tokens.push(current);
}
tokens
}
/// Replace comments and quoted literals with spaces, preserving code paths.
fn source_without_comments_and_literals(source: &str) -> String {
let bytes = source.as_bytes();
let mut output = vec![b' '; bytes.len()];
let mut index = 0;
while index < bytes.len() {
if bytes[index..].starts_with(b"//") {
while index < bytes.len() && bytes[index] != b'\n' {
index += 1;
}
} else if bytes[index..].starts_with(b"/*") {
index += 2;
while index + 1 < bytes.len() && !bytes[index..].starts_with(b"*/") {
index += 1;
}
index = (index + 2).min(bytes.len());
} else if bytes[index] == b'\''
&& bytes
.get(index + 1)
.is_some_and(|byte| byte.is_ascii_alphabetic() || *byte == b'_')
&& bytes.get(index + 2) != Some(&b'\'')
{
// A lifetime is code, not a character literal. Keeping it avoids
// swallowing all following source while looking for a quote.
output[index] = bytes[index];
index += 1;
} else if bytes[index] == b'"' || bytes[index] == b'\'' {
let quote = bytes[index];
index += 1;
while index < bytes.len() {
if bytes[index] == b'\\' {
index += 2;
continue;
}
if index < bytes.len() && bytes[index] == quote {
index += 1;
break;
}
index += 1;
}
} else {
output[index] = bytes[index];
index += 1;
}
}
String::from_utf8(output).unwrap_or_default()
}
/// Abbreviations banned in authored Smith declarations, with their preferred
/// words. Established protocol and encoding terms (`id`, `io`, `rpc`, `sse`,
/// `cbor`, `json`, `utf8`) are intentionally absent.
const BANNED_DECLARATION_WORDS: &[(&str, &str)] = &[
("req", "request"),
("res", "response"),
("resp", "response"),
("ctx", "context"),
("buf", "buffer"),
("msg", "message"),
("tmp", "temporary"),
("idx", "index"),
("cnt", "count"),
("err", "error"),
("val", "value"),
("num", "number"),
];
/// Naming violations in authored production declarations of one source file.
///
/// This deliberately remains a line-oriented source scanner: comments, quoted
/// literals, and `#[cfg(test)]` items are removed by the existing helpers;
/// macro invocation lines are skipped; macro expansion, nested block comments,
/// raw identifiers, complex patterns, and cfg forms other than `cfg(test)` are
/// outside this mechanical boundary. It recognizes declaration heads, named
/// parameters and fields, `let` patterns, and enum bodies, never paths or uses.
fn macro_invocation_line(line: &str) -> bool {
let line = line.trim_start();
if line.starts_with("macro_rules!") {
return true;
}
let bytes = line.as_bytes();
let mut index = 0;
while index < bytes.len() && (bytes[index].is_ascii_alphanumeric() || bytes[index] == b'_') {
index += 1;
}
if index == 0 {
return false;
}
while bytes.get(index..index + 2) == Some(b"::") {
index += 2;
let start = index;
while index < bytes.len() && (bytes[index].is_ascii_alphanumeric() || bytes[index] == b'_')
{
index += 1;
}
if index == start {
return false;
}
}
while bytes.get(index).is_some_and(u8::is_ascii_whitespace) {
index += 1;
}
if bytes.get(index) != Some(&b'!') {
return false;
}
index += 1;
while bytes.get(index).is_some_and(u8::is_ascii_whitespace) {
index += 1;
}
matches!(bytes.get(index), Some(b'(' | b'{' | b'['))
}
fn naming_violations(path: &Path, source: &str) -> Vec<String> {
let source = source_without_comments_and_literals(&without_test_items(source));
let mut violations = Vec::new();
let mut seen = BTreeSet::new();
let mut enclosing: Option<(&str, bool, isize)> = None;
let mut multiline_parameters = false;
let mut external_impl_depth = None;
for (line_index, line) in source.lines().enumerate() {
let trimmed = line.trim_start();
if macro_invocation_line(line) {
continue;
}
if trimmed.starts_with("impl ") && trimmed.contains("::") {
external_impl_depth = Some(0_isize);
}
let external_impl = external_impl_depth.is_some();
let tokens = declaration_tokens(line);
let mut names = Vec::new();
for index in 0..tokens.len().saturating_sub(1) {
if ["fn", "struct", "enum", "type", "mod", "const", "static"].contains(&tokens[index]) {
if !external_impl || !["fn", "type"].contains(&tokens[index]) {
names.push(tokens[index + 1]);
}
if ["struct", "enum"].contains(&tokens[index]) {
enclosing = Some((tokens[index + 1], tokens[index] == "enum", 0));
}
}
if tokens[index] == "let" {
for binding in tokens[index + 1..]
.iter()
.take_while(|name| **name != "=" && **name != ":")
.filter(|name| {
!["mut", "ref"].contains(name) && name.starts_with(char::is_lowercase)
})
{
names.push(binding);
}
}
}
if tokens.contains(&"struct") {
for index in 0..tokens.len().saturating_sub(1) {
if tokens[index + 1] == ":" && index > 0 && tokens[index - 1] != ":" {
names.push(tokens[index]);
}
}
}
let line_enclosing = enclosing;
if let Some((name, is_enum, depth)) = enclosing {
if is_enum {
let Some(body_start) = tokens.iter().position(|token| *token == "{") else {
continue;
};
let mut nested = 0_isize;
for index in body_start + 1..tokens.len() {
if (nested == 0 && [",", "{"].contains(&tokens[index - 1]))
|| (nested > 0 && tokens[index + 1..].first() == Some(&":"))
{
names.push(tokens[index]);
}
match tokens[index] {
"(" | "{" | "[" => nested += 1,
")" | "}" | "]" => nested -= 1,
_ => {}
}
}
} else {
for index in 0..tokens.len().saturating_sub(1) {
if tokens[index + 1] == ":"
&& tokens[index] != ":"
&& (index == 0 || tokens[index - 1] != ":")
&& (index == 0 || tokens[index - 1] != "struct")
&& (index == 0 || !["struct", "enum"].contains(&tokens[index - 1]))
{
names.push(tokens[index]);
}
}
}
let next_depth = depth + brace_balance(line);
enclosing = (next_depth > 0).then_some((name, is_enum, next_depth));
}
if !external_impl {
if let Some(fn_index) = tokens.iter().position(|token| *token == "fn") {
multiline_parameters = true;
for index in fn_index + 1..tokens.len() {
if tokens[index] == ":" && index > fn_index + 1 && tokens[index - 1] != ":" {
names.push(tokens[index - 1]);
}
}
if tokens.contains(&")") {
multiline_parameters = false;
}
} else if multiline_parameters {
for index in 1..tokens.len() {
if tokens[index] == ":" && tokens[index - 1] != ":" {
names.push(tokens[index - 1]);
}
}
if tokens.contains(&")") {
multiline_parameters = false;
}
}
}
if let Some(depth) = external_impl_depth {
let next_depth = depth + brace_balance(line);
external_impl_depth = (next_depth > 0).then_some(next_depth);
}
for name in names {
let label = line_enclosing.map_or_else(
|| name.to_owned(),
|(type_name, _, _)| format!("{type_name}::{name}"),
);
if !seen.insert((line_index, label.clone())) {
continue;
}
if let Some((word, replacement)) = banned_declaration_word(name) {
violations.push(format!(
"{}:{}: declaration `{label}` uses `{word}`; rename it to `{}`\n",
path.display(),
line_index + 1,
replace_declaration_word(name, word, replacement)
));
} else if let Some(suggestion) = qualifier_last_suggestion(name) {
violations.push(format!(
"{}:{}: declaration `{label}` puts `{}` first; rename it to `{suggestion}` so qualifier is last\n",
path.display(),
line_index + 1,
&name[..3]
));
}
}
}
violations
}
/// Identifier and punctuation tokens sufficient for declaration-shaped lines.
fn declaration_tokens(line: &str) -> Vec<&str> {
let mut tokens = Vec::new();
let mut start = None;
for (index, character) in line.char_indices() {
if character.is_ascii_alphanumeric() || character == '_' {
start.get_or_insert(index);
} else {
if let Some(start) = start.take() {
tokens.push(&line[start..index]);
}
if matches!(
character,
':' | '=' | ',' | '(' | ')' | '{' | '}' | '[' | ']'
) {
tokens.push(&line[index..index + character.len_utf8()]);
}
}
}
if let Some(start) = start {
tokens.push(&line[start..]);
}
tokens
}
/// Net braces on one line after comments and literals are blanked.
fn brace_balance(line: &str) -> isize {
line.bytes().filter(|byte| *byte == b'{').count() as isize
- line.bytes().filter(|byte| *byte == b'}').count() as isize
}
/// A banned complete snake-case or CamelCase segment, never a substring.
fn banned_declaration_word(name: &str) -> Option<(&'static str, &'static str)> {
declaration_segments(name).find_map(|segment| {
BANNED_DECLARATION_WORDS
.iter()
.find(|(word, _)| *word == segment)
.copied()
})
}
/// Lowercase snake-case and CamelCase segments of an identifier.
fn declaration_segments(name: &str) -> impl Iterator<Item = String> + '_ {
name.split('_').flat_map(|part| {
let mut segments = Vec::new();
let mut start = 0;
for (index, character) in part.char_indices().skip(1) {
if character.is_ascii_uppercase() {
segments.push(part[start..index].to_ascii_lowercase());
start = index;
}
}
if !part.is_empty() {
segments.push(part[start..].to_ascii_lowercase());
}
segments
})
}
/// Replace one complete declaration segment while retaining its surrounding name.
fn replace_declaration_word(name: &str, word: &str, replacement: &str) -> String {
let mut result = String::new();
for (part_index, part) in name.split('_').enumerate() {
if part_index > 0 {
result.push('_');
}
let mut start = 0;
for (index, character) in part.char_indices().skip(1) {
if character.is_ascii_uppercase() {
let segment = &part[start..index];
if segment.eq_ignore_ascii_case(word) {
let mut characters = replacement.chars();
result.extend(characters.next().into_iter().flat_map(char::to_uppercase));
result.extend(characters);
} else {
result.push_str(segment);
}
start = index;
}
}
let segment = &part[start..];
if segment.eq_ignore_ascii_case(word) {
if segment.starts_with(char::is_uppercase) {
let mut characters = replacement.chars();
result.extend(characters.next().into_iter().flat_map(char::to_uppercase));
result.extend(characters);
} else {
result.push_str(replacement);
}
} else {
result.push_str(segment);
}
}
result
}
/// `max_foo` and `min_foo` become `foo_max` and `foo_min`; units remain in `foo`.
fn qualifier_last_suggestion(name: &str) -> Option<String> {
for (prefix, suffix) in [
("max_", "_max"),
("min_", "_min"),
("MAX_", "_MAX"),
("MIN_", "_MIN"),
] {
if let Some(rest) = name.strip_prefix(prefix).filter(|rest| !rest.is_empty()) {
return Some(format!("{rest}{suffix}"));
}
}
for prefix in ["Max", "Min"] {
if let Some(rest) = name.strip_prefix(prefix).filter(|rest| !rest.is_empty()) {
let mut characters = rest.chars();
if let Some(first) = characters.next() {
return Some(format!(
"{}{prefix}",
first.to_lowercase().chain(characters).collect::<String>()
));
}
}
}
None
}
/// The first `.rs` file under any workspace crate that carries test code
/// (`#[test]` or a `tests/` directory) but cites no `SMH-SPEC-…` id in its
/// first 20 lines or its `//!` header block.
fn missing_spec_reference() -> Option<PathBuf> {
for crate_name in CRATES {
let mut files = Vec::new();
collect_rs_files(Path::new(crate_name), &mut files);
for path in files {
let Ok(source) = fs::read_to_string(&path) else {
continue;
};
if is_test_bearing(&path, &source) && !cites_a_spec(&source) {
return Some(path);
}
}
}
None
}
/// `.rs` files under `dir`, deterministically ordered, skipping `target`.
fn collect_rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(entries) = fs::read_dir(dir) else {
return;
};
let mut entries: Vec<PathBuf> = entries.flatten().map(|entry| entry.path()).collect();
entries.sort();
for path in entries {
if path.is_dir() {
if path.file_name().is_some_and(|name| name == "target") {
continue;
}
collect_rs_files(&path, out);
} else if path.extension().is_some_and(|ext| ext == "rs") {
out.push(path);
}
}
}
/// A file carries test code when it literally declares `#[test]` or lives
/// under a `tests/` directory (integration test crates have no attribute).
fn is_test_bearing(path: &Path, source: &str) -> bool {
source.contains("#[test]") || path.components().any(|part| part.as_os_str() == "tests")
}
/// Whether `SMH-SPEC-` appears in the first 20 lines or the leading `//!`
/// doc-comment block, whichever the file has.
fn cites_a_spec(source: &str) -> bool {
let first_twenty: Vec<&str> = source.lines().take(20).collect();
first_twenty.iter().any(|line| line.contains("SMH-SPEC-"))
|| leading_doc_block(source).contains("SMH-SPEC-")
}
/// The contiguous run of `//!` lines starting the file, attributes and
/// blank lines before it skipped, concatenated.
fn leading_doc_block(source: &str) -> String {
let mut block = String::new();
for line in source.lines() {
let trimmed = line.trim_start();
if trimmed.starts_with("//!") {
block.push_str(trimmed);
block.push('\n');
} else if !block.is_empty() {
break;
}
}
block
}
const ROOT_DENY: &str = "crate root lacks `#![deny(unsafe_code)]` (RULES.md Build gates)";
const ROOT_OPT_IN: &str =
"crate-wide unsafe_code opt-in; opt in per module or function (RULES.md Build gates)";
const OPT_IN_LEVEL: &str =
"unsafe_code only takes `deny`, or `expect` with a reason (RULES.md Build gates)";
const OPT_IN_REASON: &str =
"unsafe_code opt-in without a non-empty `reason = \"...\"` (RULES.md Build gates)";
const OPT_IN_TESTS: &str = "unsafe_code opt-in in a file without `#[test]` (RULES.md Build gates)";
const VISIBLE_UNSAFE_FN: &str = "`unsafe fn` with a visibility modifier leaks unsafety; keep it private behind a safe API (RULES.md Build gates)";
const UNSAFE_TRAIT: &str =
"unsafe trait of Smith's own; implement std unsafe traits only (RULES.md Build gates)";
/// Workspace crates that ship nothing: benches and automation, exempt
/// from the unsafe law (RULES.md Build gates).
const UNSHIPPED: &[&str] = &["smith-bench", "xtask"];
/// Every readable production `.rs` file of the host crates under `root`,
/// with its crate: `src/` of each crate but the [`UNSHIPPED`] ones, so
/// tests, benches, examples, `smith-bench`, and xtask stay exempt
/// (RULES.md Build gates). Guest crates under `plugins/` are not in
/// [`CRATES`]: their `unsafe` is wit-bindgen's macro expansion, dependency
/// code the same rule exempts.
fn host_sources(root: &Path) -> Vec<(&'static str, PathBuf, String)> {
let mut sources = Vec::new();
for crate_name in CRATES.iter().filter(|name| !UNSHIPPED.contains(name)) {
let mut files = Vec::new();
collect_rs_files(&root.join(crate_name).join("src"), &mut files);
for path in files {
if let Ok(source) = fs::read_to_string(&path) {
sources.push((*crate_name, path, source));
}
}
}
sources
}
/// Every unsafe-law violation of the host crates under `root`, as
/// `file:line: rule` lines; library and binary targets are crate roots.
fn unsafe_law_violations(root: &Path) -> Vec<String> {
host_sources(root)
.iter()
.flat_map(|(crate_name, path, source)| {
let dir = root.join(crate_name);
let crate_root = *path == dir.join("src/lib.rs")
|| *path == dir.join("src/main.rs")
|| path.parent() == Some(&dir.join("src/bin"));
unsafe_violations(path, source, crate_root)
})
.collect()
}
/// Host crates under `root` with an `unsafe_code` opt-in, in [`CRATES`] order.
fn unsafe_opt_in_crates(root: &Path) -> Vec<&'static str> {
let mut crates: Vec<&'static str> = host_sources(root)
.into_iter()
.filter(|(_, _, source)| opts_into_unsafe(source))
.map(|(crate_name, _, _)| crate_name)
.collect();
crates.dedup();
crates
}
/// The unsafe law of `.system/RULES.md` (Build gates) over one file:
/// crate roots deny `unsafe_code`; the lint takes no level but `deny` and
/// `expect`; each opt-in states a reason and shares its file with tests;
/// no `unsafe fn` carries a visibility modifier and no `unsafe trait` is
/// declared.
///
/// Scanning is line based like the other arch checks, over the file's
/// production lines ([`without_test_items`]): an attribute or declaration
/// counts when a trimmed line starts with it, so comment lines and string
/// literals that do not open a line are skipped. Not seen: items a macro
/// expands, lint levels under `cfg_attr`, declarations split across lines
/// before `fn` or `trait`; a string literal line that begins like code is
/// taken for code.
fn unsafe_violations(path: &Path, source: &str, root: bool) -> Vec<String> {
let at = |line: usize, rule: &str| format!("{}:{line}: {rule}\n", path.display());
let production = without_test_items(source);
let attributes = unsafe_code_attributes(&production);
let mut violations = Vec::new();
if root
&& !attributes
.iter()
.any(|attribute| attribute.inner && attribute.level == "deny")
{
violations.push(at(1, ROOT_DENY));
}
for attribute in &attributes {
let rule = match attribute.level {
"expect" if root && attribute.inner => Some(ROOT_OPT_IN),
"expect" if !attribute.reasoned => Some(OPT_IN_REASON),
"expect" if !is_test_bearing(path, source) => Some(OPT_IN_TESTS),
"deny" | "expect" => None,
_ => Some(OPT_IN_LEVEL),
};
violations.extend(rule.map(|rule| at(attribute.line, rule)));
}
for (number, line) in production.lines().enumerate() {
violations.extend(unsafe_declaration(line).map(|rule| at(number + 1, rule)));
}
violations
}
/// `source` with its test items blanked, line numbers kept. A trimmed line
/// starting `#[cfg(test)]` or `#[test]` opens a test item, which runs
/// through its attributes to the `;` or `,` ending a line at bracket depth
/// zero, or to the close of its first brace. Brackets count outside string
/// literals, which may span lines, and `//` comments. Limits: quotes and
/// brackets in char literals, escaped quotes, and `/* */` comments
/// miscount; `cfg(all(test, ...))` and `cfg_attr` count as production; so
/// does a module file a `#[cfg(test)] mod m;` pulls in.
fn without_test_items(source: &str) -> String {
let mut production = String::with_capacity(source.len());
let (mut depth, mut braced, mut in_test, mut in_string) = (0_usize, false, false, false);
for line in source.lines() {
let trimmed = line.trim_start();
if !in_test && (trimmed.starts_with("#[cfg(test)]") || trimmed.starts_with("#[test]")) {
(depth, braced, in_test, in_string) = (0, false, true, false);
}
if in_test {
// Last code character outside strings and comments.
let mut last = None;
let mut chars = line.chars().peekable();
while let Some(c) = chars.next() {
match c {
'"' => in_string = !in_string,
_ if in_string => {}
'/' if chars.peek() == Some(&'/') => break,
'(' | '[' | '{' => {
depth += 1;
braced |= c == '{';
}
')' | ']' | '}' => depth = depth.saturating_sub(1),
_ => {}
}
if !in_string && c != '"' && !c.is_whitespace() {
last = Some(c);
}
}
in_test = in_string || depth > 0 || !(braced || matches!(last, Some(';' | ',')));
} else {
production.push_str(line);
}
production.push('\n');
}
production
}
/// Whether `source` opts any production item into `unsafe_code`.
fn opts_into_unsafe(source: &str) -> bool {
unsafe_code_attributes(&without_test_items(source))
.iter()
.any(|attribute| attribute.level == "expect")
}
/// A lint-level attribute naming `unsafe_code`.
struct UnsafeCodeAttribute {
/// 1-based line the attribute opens on.
line: usize,
/// `#![...]` rather than `#[...]`.
inner: bool,
/// `allow`, `warn`, `deny`, `forbid`, or `expect`.
level: &'static str,
/// Carries a non-blank `reason` string.
reasoned: bool,
}
/// Lint-level attributes of `source` that name `unsafe_code`; rustfmt's
/// multi-line form is joined up to the closing bracket.
fn unsafe_code_attributes(source: &str) -> Vec<UnsafeCodeAttribute> {
let lines: Vec<&str> = source.lines().collect();
let mut attributes = Vec::new();
for (start, line) in lines.iter().enumerate() {
let line = line.trim_start();
let (inner, head) = match (line.strip_prefix("#!["), line.strip_prefix("#[")) {
(Some(head), _) => (true, head),
(None, Some(head)) => (false, head),
(None, None) => continue,
};
let mut text = head.to_string();
let mut next = start + 1;
let end = loop {
if let Some(end) = attribute_end(&text) {
break end;
}
let Some(more) = lines.get(next) else {
break text.len();
};
text.push(' ');
text.push_str(more.trim());
next += 1;
};
let Some((level, args)) = text[..end].split_once('(') else {
continue;
};
let Some(level) = ["allow", "warn", "deny", "forbid", "expect"]
.into_iter()
.find(|known| *known == level.trim())
else {
continue;
};
let args = args.trim_end().strip_suffix(')').unwrap_or(args);
let mut parts = Vec::new();
let mut from = 0;
for (at, _) in code_chars(args).filter(|(_, c)| *c == ',') {
parts.push(args[from..at].trim());
from = at + 1;
}
parts.push(args[from..].trim());
if !parts.contains(&"unsafe_code") {
continue;
}
let reasoned = parts.iter().any(|part| {
part.strip_prefix("reason")
.and_then(|value| value.trim_start().strip_prefix('='))
.is_some_and(|value| !value.trim().trim_matches('"').trim().is_empty())
});
attributes.push(UnsafeCodeAttribute {
line: start + 1,
inner,
level,
reasoned,
});
}
attributes
}
/// Byte offset of the first `]` outside string literals: the end of an
/// attribute whose `#[` is consumed; lint attributes nest no brackets.
fn attribute_end(text: &str) -> Option<usize> {
code_chars(text).find_map(|(at, c)| (c == ']').then_some(at))
}
/// Characters of `text` outside `"..."` string literals, with offsets;
/// the quotes themselves are dropped. Limit: an escaped quote ends the
/// literal early, so a reason spelling `\", unsafe_code,` reads as a lint.
fn code_chars(text: &str) -> impl Iterator<Item = (usize, char)> + '_ {
let mut in_string = false;
text.char_indices().filter(move |&(_, c)| {
if c == '"' {
in_string = !in_string;
}
!in_string && c != '"'
})
}
/// The rule a declaration line breaks: an `unsafe fn` with any visibility
/// modifier, or any `unsafe trait`. `unsafe impl` and private `unsafe fn`
/// pass.
fn unsafe_declaration(line: &str) -> Option<&'static str> {
let line = line.trim_start();
let (visible, rest) = match line.strip_prefix("pub") {
Some(rest) if rest.starts_with('(') => (true, rest.split_once(')')?.1),
Some(rest) if rest.starts_with(char::is_whitespace) => (true, rest),
_ => (false, line),
};
let (mut unsafe_seen, mut after_extern) = (false, false);
for token in rest.split_whitespace() {
match token {
"unsafe" => unsafe_seen = true,
"const" | "async" | "safe" | "default" | "auto" => {}
"extern" => {
after_extern = true;
continue;
}
_ if after_extern && token.starts_with('"') => {}
"fn" if unsafe_seen && visible => return Some(VISIBLE_UNSAFE_FN),
"trait" if unsafe_seen => return Some(UNSAFE_TRAIT),
_ => return None,
}
after_extern = false;
}
None
}
/// Crates under the "no Mutex in library crates" law; binaries are exempt.
const LIBRARY_CRATES: &[&str] = &[
"smith",
"smith-core",
"smith-ai",
"smith-ui",
"smith-tui",
"smith-rpc",
"smith-harness",
"smith-alloc",
];
/// The first `.rs` file under `crate_dir/src` whose production lines
/// ([`without_test_items`]) mention `Mutex`; tests may lock freely.
fn mutex_in_production(crate_dir: &Path) -> Option<PathBuf> {
let mut pending = vec![crate_dir.join("src")];
while let Some(dir) = pending.pop() {
let Ok(entries) = fs::read_dir(&dir) else {
continue;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
pending.push(path);
} else if path.extension().is_some_and(|ext| ext == "rs")
&& fs::read_to_string(&path)
.is_ok_and(|source| without_test_items(&source).contains("Mutex"))
{
return Some(path);
}
}
}
None
}
/// Library edges a crate may have; every other workspace crate is denied.
struct DependencyRule {
crate_name: &'static str,
allowed: &'static [&'static str],
}
/// The allowed-deps table of `.system/RULES.md`, architecture section.
const DEPENDENCY_RULES: &[DependencyRule] = &[
DependencyRule {
crate_name: "smith",
allowed: &[],
},
DependencyRule {
crate_name: "smith-ui",
allowed: &["smith"],
},
DependencyRule {
crate_name: "smith-core",
allowed: &["smith"],
},
DependencyRule {
crate_name: "smith-ai",
allowed: &["smith"],
},
DependencyRule {
crate_name: "smith-tui",
allowed: &["smith", "smith-ui"],
},
DependencyRule {
crate_name: "smith-rpc",
allowed: &["smith", "smith-core"],
},
DependencyRule {
crate_name: "smith-harness",
allowed: &[
"smith",
"smith-core",
"smith-ai",
"smith-tui",
"smith-ui",
"smith-alloc",
],
},
DependencyRule {
crate_name: "smith-cli",
allowed: &["smith", "smith-harness", "smith-rpc", "smith-alloc"],
},
DependencyRule {
crate_name: "smith-alloc",
allowed: &[],
},
DependencyRule {
crate_name: "smith-bench",
allowed: &[
"smith",
"smith-core",
"smith-ai",
"smith-ui",
"smith-tui",
"smith-rpc",
"smith-harness",
"smith-cli",
"smith-alloc",
"xtask",
],
},
DependencyRule {
crate_name: "xtask",
allowed: &[],
},
];
fn has_dependency(manifest: &str, crate_name: &str) -> bool {
// Library edges only: the manifest up to the dev-dependency table.
let library = manifest
.split("[dev-dependencies]")
.next()
.unwrap_or(manifest);
let table = format!("[dependencies.{crate_name}]");
let assignment = format!("{crate_name} =");
library.lines().any(|line| {
let trimmed = line.trim_start();
trimmed.starts_with(&table) || trimmed.starts_with(&assignment)
})
}
/// Whether any dependency table of `manifest`, dev and target tables
/// included, names `crate_name`.
fn names_dependency(manifest: &str, crate_name: &str) -> bool {
let assignment = format!("{crate_name} =");
let table = format!(".{crate_name}]");
manifest.lines().any(|line| {
let trimmed = line.trim();
trimmed.starts_with(&assignment) || (trimmed.starts_with('[') && trimmed.ends_with(&table))
})
}
fn fail(message: &str) -> ExitCode {
let _ignored = std::io::stderr().write_all(message.as_bytes());
ExitCode::FAILURE
}
#[cfg(test)]
mod tests {
#[test]
fn runtime_duplicate_report_includes_underscore_package_names() {
assert_eq!(
super::runtime_duplicate_warning(concat!(
"foo_bar v1.0.0 (registry+https://example.invalid)\n",
"foo_bar v1.0.0 (registry+https://example.invalid)\n",
"foo_bar v2.0.0-rc.1 (registry+https://example.invalid)\n",
)),
Some("warning: runtime dependency duplicate versions:\n foo_bar v1.0.0\n foo_bar v2.0.0-rc.1\n".into())
);
}
#[test]
fn runtime_duplicate_report_ignores_build_proc_macro_dev_and_benchmark_edges() {
assert_eq!(
super::RUNTIME_DUPLICATE_COMMAND,
[
"tree",
"--locked",
"--workspace",
"--exclude",
"smith-bench",
"--exclude",
"xtask",
"--target",
"all",
"--edges",
"normal,no-proc-macro",
"--duplicates",
"--depth",
"0",
"--prefix",
"none",
"--color",
"never",
]
);
let runtime_tree = "shared v1.0.0\nshared v2.0.0\nshared v2.0.0\nsingleton v1.0.0\n";
assert_eq!(
super::runtime_duplicate_warning(runtime_tree),
Some("warning: runtime dependency duplicate versions:\n shared v1.0.0\n shared v2.0.0\n".into())
);
assert_eq!(super::runtime_duplicate_warning("singleton v1.0.0\n"), None);
let non_runtime_tree = concat!(
"build-only v1.0.0\nbuild-only v2.0.0\n",
"proc-macro-chain v1.0.0\nproc-macro-chain v2.0.0\n",
"dev-only v1.0.0\ndev-only v2.0.0\n",
"benchmark-root v1.0.0\nbenchmark-root v2.0.0\n",
);
assert!(super::runtime_duplicate_warning(non_runtime_tree).is_some());
let config = include_str!("../../.config/deny.toml");
assert!(config.contains("multiple-versions = \"warn\""));
assert!(config.contains("wildcards = \"deny\""));
assert!(!config.contains("multiple-versions = \"allow\""));
}
#[test]
fn mutants_refuse_to_run_unscoped() {
let scoped = |args: &[&str]| {
let args: Vec<std::ffi::OsString> = args.iter().map(std::ffi::OsString::from).collect();
super::mutants_scoped(&args)
};
assert!(scoped(&["-f", "smith-core/src/session.rs"]));
assert!(scoped(&["--shard=0/8"]));
assert!(scoped(&["-D", "/tmp/d.patch", "-j2"]));
assert!(scoped(&["--list"]));
assert!(!scoped(&[]));
assert!(!scoped(&["-j4", "--baseline", "run"]));
}
#[test]
fn local_mutants_reject_a_job_count() {
let flag = |arg: &str| super::jobs_flag(&std::ffi::OsString::from(arg));
assert!(flag("-j"));
assert!(flag("-j4"));
assert!(flag("--jobs"));
assert!(flag("--jobs=2"));
assert!(!flag("-jx"));
assert!(!flag("--shard"));
}
#[test]
fn junit_counts_read_the_testsuites_root() {
let xml = r#"<?xml version="1.0"?><testsuites name="nextest-run" tests="19" failures="1" errors="0"><testsuite tests="7"/></testsuites>"#;
assert_eq!(super::junit_counts(xml), [19, 1, 0]);
assert_eq!(super::junit_counts("<nothing/>"), [0, 0, 0]);
}
#[test]
fn mutants_counts_need_a_run() {
let dir = std::env::temp_dir().join(format!("smith_mutants_probe_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
assert_eq!(super::mutants_counts(&dir), None);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("caught.txt"), "a\nb\n").unwrap();
std::fs::write(dir.join("missed.txt"), "c\n").unwrap();
assert_eq!(super::mutants_counts(&dir), Some([2, 1, 0, 0]));
let _ = std::fs::remove_dir_all(&dir);
}
fn os(args: &[&str]) -> Vec<std::ffi::OsString> {
args.iter().map(std::ffi::OsString::from).collect()
}
#[test]
fn package_scope_drops_workspace_and_its_excludes() {
let base = ["clippy", "--workspace", "--exclude", "a", "--lib"];
assert_eq!(
super::scoped(&base, &os(&["-p", "b"])),
os(&["clippy", "--lib", "-p", "b"])
);
assert_eq!(
super::scoped(&base, &os(&["--tests"])),
os(&[
"clippy",
"--workspace",
"--exclude",
"a",
"--lib",
"--tests"
])
);
}
#[test]
fn production_lints_skip_non_production_tooling() {
let targets = |extra: &[&str]| super::prod_targets(&os(extra));
let workspace = targets(&[]).unwrap();
assert!(
workspace
.windows(2)
.any(|pair| pair == ["--exclude", "smith-bench"])
);
assert!(
workspace
.windows(2)
.any(|pair| pair == ["--exclude", "xtask"])
);
assert!(
workspace
.windows(2)
.any(|pair| pair == ["--exclude", super::GUEST])
);
for scope in [
&["-p", "smith-bench"][..],
&["--package", "smith-bench"],
&["--package=smith-bench"],
&["-p", "xtask"],
&["--package", "xtask"],
&["--package=xtask"],
] {
assert_eq!(targets(scope), None, "{scope:?}");
}
// Tests run in `xtask/`; a package directory is resolved from there.
assert_eq!(targets(&["-p", "../smith-core"]), Some(vec!["--lib"]));
assert_eq!(targets(&["-p", "../smith-cli"]), Some(vec!["--bins"]));
assert_eq!(
targets(&["-p", "../no-such-crate"]),
Some(vec!["--lib", "--bins"])
);
assert_eq!(
super::selected_package(&os(&["--tests", "-p", "x", "-p", "y"])).as_deref(),
Some("x")
);
assert_eq!(super::selected_package(&os(&["-p"])), None);
}
#[test]
fn lint_levels_scope_through_xtask_not_rustflags() {
let config = include_str!("../../.cargo/config.toml");
for level in ["\"-D\"", "\"-W\"", "\"-A\""] {
assert!(!config.contains(level), "{level} in .cargo/config.toml");
}
let levels =
|lints: &'static [&'static str]| lints.chunks(2).map(|pair| (pair[0], pair[1]));
for (level, lint) in levels(super::NON_PRODUCTION_LINTS) {
assert_eq!(level, "-A", "{lint}");
assert!(
levels(super::PRODUCTION_LINTS).any(|strict| strict == ("-D", lint)),
"{lint}"
);
}
for lint in [
"clippy::pedantic",
"clippy::let_underscore_must_use",
"clippy::let_underscore_untyped",
"clippy::unwrap_used",
"clippy::print_stdout",
"clippy::undocumented_unsafe_blocks",
"clippy::clone_on_ref_ptr",
"clippy::disallowed_methods",
"clippy::missing_assert_message",
"missing_docs",
] {
assert!(
levels(super::PRODUCTION_LINTS).any(|strict| strict == ("-D", lint)),
"{lint}"
);
}
assert!(levels(super::SHARED_LINTS).all(|(level, _)| level == "-D"));
for lint in [
"clippy::let_underscore_must_use",
"clippy::let_underscore_untyped",
] {
assert!(
levels(super::NON_PRODUCTION_LINTS)
.any(|(level, configured)| { level == "-A" && configured == lint }),
"{lint} must be relaxed for tests and tooling"
);
}
}
#[test]
fn test_items_end_at_real_code_brackets_and_terminators() {
let source = concat!(
"#[cfg(test)]\n",
"mod tests {\n",
" const S: &str = \"}\";\n",
" // comment {\n",
" const D: [u8; 4 / 2] = [0; 2];\n",
" const J: &str = r#\"{\n",
" \"#;\n",
" static M: Mutex<u8> = Mutex::new(0);\n",
"}\n",
"#[cfg(test)]\n",
"const A: u8 = 1; \n",
"fn production() {}\n",
);
assert_eq!(
super::without_test_items(source),
format!("{}fn production() {{}}\n", "\n".repeat(11))
);
}
#[test]
fn mutex_law_sees_production_code_after_an_early_test_item() {
let root = std::env::temp_dir().join(format!("smith_mutex_law_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
let src = root.join("src");
std::fs::create_dir_all(&src).unwrap();
let tests_only = "use std::sync::Arc;\n#[cfg(test)]\nmod tests { use std::sync::Mutex; }\n";
std::fs::write(src.join("lib.rs"), tests_only).unwrap();
let multi_line_string = "#[cfg(test)]\nmod tests {\n const J: &str = r#\"{\n \"a\": 1\n }\"#;\n static M: std::sync::Mutex<u8> = std::sync::Mutex::new(0);\n}\n";
std::fs::write(src.join("json.rs"), multi_line_string).unwrap();
assert_eq!(super::mutex_in_production(&root), None);
let late = "#[cfg(test)]\nfn probe() {}\nstatic M: std::sync::Mutex<u8> = std::sync::Mutex::new(0);\n";
std::fs::write(src.join("late.rs"), late).unwrap();
assert_eq!(super::mutex_in_production(&root), Some(src.join("late.rs")));
let _ = std::fs::remove_dir_all(&root);
}
use super::{
cites_a_spec, facade_module, facade_violations_for_crate, is_test_bearing,
manifest_dependencies,
};
use std::path::Path;
#[test]
fn test_bearing_by_attribute_or_by_directory() {
assert!(is_test_bearing(
Path::new("smith-core/src/agent.rs"),
"mod tests {\n#[test]\nfn it_works() {}\n}"
));
assert!(is_test_bearing(
Path::new("smith-cli/tests/e2e_eval.rs"),
"fn helper() {}"
));
assert!(!is_test_bearing(
Path::new("smith-core/src/lib.rs"),
"fn helper() {}"
));
}
#[test]
fn facade_gate_requires_one_non_root_owner_per_dependency() {
let root = std::env::temp_dir().join(format!("smith_facades_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
let write = |file: &str, text: &str| {
let path = root.join(file);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, text).unwrap();
};
write(
"Cargo.toml",
"[dependencies]\nengine = \"1\"\nserde = \"1\"\nsmith = { path = \"..\" }\n",
);
write(
"src/engine.rs",
"use engine::Client;\nfn run(_: Client) {}\n",
);
assert!(facade_violations_for_crate(&root).is_empty());
write("src/other.rs", "fn run() { engine::Client::new(); }\n");
let siblings = facade_violations_for_crate(&root).concat();
assert!(siblings.contains("engine"));
assert!(siblings.contains("src/engine"));
assert!(siblings.contains("src/other"));
assert!(siblings.contains("move all `engine` operations behind one facade module"));
std::fs::remove_file(root.join("src/other.rs")).unwrap();
write(
"src/engine/client.rs",
"fn run() { engine::Client::new(); }\n",
);
assert!(facade_violations_for_crate(&root).is_empty());
std::fs::remove_file(root.join("src/engine.rs")).unwrap();
std::fs::remove_file(root.join("src/engine/client.rs")).unwrap();
write("src/lib.rs", "fn run() { engine::Client::new(); }\n");
let root_use = facade_violations_for_crate(&root).concat();
assert!(root_use.contains("crate root is not a facade"));
write(
"src/tests.rs",
"#[cfg(test)]\nmod tests { fn run() { engine::Client::new(); } }\n",
);
write(
"src/strings.rs",
"const TEXT: &str = \"engine::Client\"; // engine::Client\n",
);
std::fs::remove_file(root.join("src/lib.rs")).unwrap();
assert!(facade_violations_for_crate(&root).is_empty());
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn naming_scan_rejects_authored_declarations_and_ignores_non_declarations() {
let source = concat!(
"fn send_req(ctx: &str, idx: usize) { let (tmp, value) = (ctx, idx); }\n",
"struct Msg { res: String, value: String }\n",
"enum Reply { Resp, Complete }\n",
"const max_items: usize = 4;\n",
"use external::req;\n",
"let request = \"req ctx max_items\";\n",
"macro_rules! names { () => { fn req() {} }; }\n",
"names! { fn expanded_req() {} }\n",
"fn assert_req(req: &str) { assert!(true); }\n",
"impl std::fmt::Display for Msg { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, \"\") } }\n",
"impl std::str::FromStr for Msg { type Err = String; fn from_str(buf: &str) -> Result<Self, Self::Err> { unimplemented!() } }\n",
"impl external::Trait for Msg { fn val(&self) {} }\n",
"enum Request { Path(std::path::PathBuf), Named { buf: String } }\n",
"struct Fields { buf: String }\n",
"trait Own { fn fetch(req: &str); }\n",
"impl Own for Msg { fn fetch(req: &str) {} }\n",
"impl Own for Msg {\n fn submit(cnt: &str) {}\n}\n",
"fn multiline(\n req: &str,\n buffer: &str,\n) { let temporary = req; }\n",
"const MIN_ITEMS: usize = 1;\nconst MAX_ITEMS: usize = 4;\nconst MaxItems: usize = 4;\n",
"struct ErrThing;\n",
"#[cfg(test)]\nmod tests { fn req() {} }\n",
);
let violations = super::naming_violations(Path::new("src/probe.rs"), source).concat();
for declaration in [
"send_req",
"ctx",
"idx",
"tmp",
"Msg::res",
"Reply::Resp",
"max_items",
"Fields::buf",
"declaration `req`",
"declaration `cnt`",
"MIN_ITEMS",
"MAX_ITEMS",
"declaration `max_items`",
"itemsMax",
"ErrorThing",
] {
assert!(
violations.contains(declaration),
"{declaration}: {violations}"
);
}
for ignored in [
"external::req",
"declaration `request`",
"fn req() {}",
"declaration `fmt`",
"declaration `Err`",
"Request::PathBuf",
"declaration `buf`",
"declaration `buffer`",
"declaration `from_str`",
"declaration `PathBuf`",
"declaration `val`",
"declaration `expanded_req`",
"declaration `expanded_request`",
] {
assert!(!violations.contains(ignored), "{ignored}: {violations}");
}
}
#[test]
fn facade_gate_catches_inherited_external_dependency_in_two_modules() {
let root =
std::env::temp_dir().join(format!("smith_inherited_facades_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
let member = root.join("member");
std::fs::create_dir_all(member.join("src")).unwrap();
std::fs::write(
root.join("Cargo.toml"),
"[workspace]\nmembers = [\"member\"]\n[workspace.dependencies]\nclap = \"4\"\ncli_alias = { package = \"clap-alias-external\", version = \"4\" }\ntarget_clap = \"4\"\n[workspace.dependencies.table_clap]\npackage = \"table-clap-external\"\nversion = \"4\"\n",
)
.unwrap();
std::fs::write(
member.join("Cargo.toml"),
"[package]\nname = \"member\"\nversion = \"0.1.0\"\n[dependencies]\nclap = { workspace = true }\ncli_alias = { workspace = true }\n[target.'cfg(unix)'.dependencies]\ntarget_clap = { workspace = true }\n[dependencies.table_clap]\nworkspace = true\n",
)
.unwrap();
std::fs::write(
member.join("src/one.rs"),
"use clap::Command;\nuse cli_alias::Command as AliasCommand;\nuse target_clap::Command as TargetCommand;\nuse table_clap::Command as TableCommand;\n",
)
.unwrap();
std::fs::write(
member.join("src/two.rs"),
"use clap::Arg;\nuse cli_alias::Arg as AliasArg;\nuse target_clap::Arg as TargetArg;\nuse table_clap::Arg as TableArg;\n",
)
.unwrap();
let violations = facade_violations_for_crate(&member).concat();
for dependency in [
"clap",
"clap-alias-external",
"target_clap",
"table-clap-external",
] {
assert!(
violations.contains(&format!("external dependency `{dependency}`")),
"{violations}"
);
}
assert!(violations.contains("src/one, src/two"), "{violations}");
let _ = std::fs::remove_dir_all(&root);
}
#[test]
fn manifest_facade_scan_reads_table_dependencies_but_skips_workspace_aliases() {
let manifest = r#"
[dependencies]
serde = { workspace = true }
engine = { package = "engine-crate", version = "1" }
multiline = {
package = "multiline-engine",
version = "1",
}
local = { path = "../local" }
[target.'cfg(unix)'.dependencies]
target_engine = { package = "target-crate", version = "1" }
[dependencies.table_alias]
package = "table-crate"
version = "1"
[dependencies.workspace_alias]
workspace = true
[dependencies.direct_table]
version = "1"
"#;
let dependencies = manifest_dependencies(manifest);
assert_eq!(
dependencies
.iter()
.map(|dependency| (dependency.name.as_str(), dependency.source_name.as_str()))
.collect::<Vec<_>>(),
[
("engine-crate", "engine"),
("multiline-engine", "multiline"),
("target-crate", "target_engine"),
("table-crate", "table_alias"),
("direct_table", "direct_table"),
]
);
}
#[test]
fn facade_identity_groups_nested_modules_under_top_level_path() {
let root = Path::new("/tmp/smith_facade_identity");
assert_eq!(
facade_module(root, &root.join("src/wasm/runtime/engine.rs")),
"src/wasm"
);
assert_eq!(facade_module(root, &root.join("src/wasm.rs")), "src/wasm");
}
#[test]
fn spec_citation_in_first_twenty_lines_or_doc_block() {
assert!(cites_a_spec("//! docs (`SMH-SPEC-SPEC0001`, Tools).\n"));
let padded = format!("{}fn f() {{}}\n", "//! line\n".repeat(19));
assert!(!cites_a_spec(&padded));
let past_twenty = format!(
"{}//! cites `SMH-SPEC-SPEC0001` here\nfn f() {{}}\n",
"//! line\n".repeat(25)
);
assert!(cites_a_spec(&past_twenty));
assert!(!cites_a_spec("// no doc block\nfn f() {}\n"));
}
use super::{CRATES, DEPENDENCY_RULES, binary_name, dev_home_dirs, has_dependency, target_dir};
use std::path::PathBuf;
#[test]
fn every_crate_has_one_rule_over_known_crates() {
for name in CRATES {
let rules = DEPENDENCY_RULES
.iter()
.filter(|rule| rule.crate_name == *name);
assert_eq!(rules.count(), 1, "{name}");
}
assert_eq!(DEPENDENCY_RULES.len(), CRATES.len());
for rule in DEPENDENCY_RULES {
assert!(
rule.allowed
.iter()
.all(|dep| CRATES.contains(dep) && *dep != rule.crate_name)
);
}
}
#[test]
fn dev_home_covers_all_xdg_roots() {
let keys: Vec<&str> = dev_home_dirs().iter().map(|(key, _)| *key).collect();
assert_eq!(
keys,
vec![
"XDG_CONFIG_HOME",
"XDG_DATA_HOME",
"XDG_STATE_HOME",
"XDG_CACHE_HOME"
]
);
}
#[test]
fn dev_binary_path_is_platform_correct() {
let path = target_dir().join("debug").join(binary_name());
assert!(path.ends_with(if cfg!(windows) { "smith.exe" } else { "smith" }));
assert!(path.starts_with(env_target()));
}
fn env_target() -> PathBuf {
std::env::var_os("CARGO_TARGET_DIR").map_or_else(|| PathBuf::from("target"), PathBuf::from)
}
use super::{
BTreeMap, BudgetFile, bench_ir, budget_violations, delta, names_dependency,
parse_budget_file, parse_budgets, ratio, render_budgets, without_separator,
write_perf_report,
};
const FIRST_RUN: &str = r#"{"baselines":[null,null],"details":"(linear(1000))","function_name":"encode","id":"linear_1000","kind":"LibraryBenchmark","module_path":"instructions::session::encode","profiles":[{"summaries":{"parts":[{"details":{"Left":{"pid":1,"thread":1}},"metrics_summary":{"Callgrind":{"Ir":{"diffs":null,"metrics":{"Left":{"Int":999}}}}}}],"total":{"regressions":[],"summary":{"Callgrind":{"Ir":{"diffs":null,"metrics":{"Left":{"Int":5736448}}}}}}},"tool":"Callgrind"}],"version":"6"}"#;
const VERSION_SEVEN: &str = r#"{"id":"linear_1000","module_path":"instructions::session::encode_preallocated","profiles":[{"data":{"total":{"metrics":{"Ir":{"values":{"new":814084,"old":819094}}}}},"tool":"Cachegrind"},{"data":{"total":{"metrics":{"Ir":{"values":{"new":5736448}}}}},"tool":"Callgrind"}],"version":"7"}"#;
#[test]
fn pinned_platform_reads_the_header() {
let text = "# x\n# Pinned with rustc 1.98.1 (a 2026) | glibc 2.41.\n[budgets]\n";
assert_eq!(
super::pinned_platform(text).as_deref(),
Some("rustc 1.98.1 (a 2026) | glibc 2.41")
);
assert_eq!(super::pinned_platform("[budgets]\n"), None);
}
#[test]
fn bench_ir_reads_callgrind_version_seven_values_not_cachegrind() {
assert_eq!(
bench_ir(VERSION_SEVEN),
Some((
"instructions::session::encode_preallocated::linear_1000".to_string(),
5_736_448
))
);
let repeated = VERSION_SEVEN.replace(
r#""values":{"new":5736448}"#,
r#""values":{"new":5736500,"old":5736448}"#,
);
assert_eq!(bench_ir(&repeated).map(|(_, ir)| ir), Some(5_736_500));
let missing_ir = VERSION_SEVEN.replace(
r#""metrics":{"Ir":{"values":{"new":5736448}}}"#,
r#""metrics":{}"#,
);
assert_eq!(bench_ir(&missing_ir), None);
}
#[test]
fn bench_ir_reads_the_total_of_first_and_repeated_runs() {
assert_eq!(
bench_ir(FIRST_RUN),
Some((
"instructions::session::encode::linear_1000".to_string(),
5_736_448
))
);
let repeated = FIRST_RUN.replace(
r#""total":{"regressions":[],"summary":{"Callgrind":{"Ir":{"diffs":null,"metrics":{"Left":{"Int":5736448}}}}}}"#,
r#""total":{"regressions":[],"summary":{"Callgrind":{"Ir":{"diffs":{"diff_pct":0.1},"metrics":{"Both":[{"Int":5736500},{"Int":5736448}]}}}}}"#,
);
assert_eq!(bench_ir(&repeated).map(|(_, ir)| ir), Some(5_736_500));
assert_eq!(bench_ir(r#"{"module_path":"a","id":"b"}"#), None);
}
#[test]
fn budgets_round_trip_sorted_and_reject_other_shapes() {
let pins = BTreeMap::from([
("b::g::f::x".to_string(), 7),
("a::g::f::y".to_string(), 42),
]);
let text = render_budgets(
&BudgetFile {
pins: pins.clone(),
floors: BTreeMap::from([("a::g::f::y".to_string(), "floor".to_string())]),
gaps: BTreeMap::from([(
"a::g::f::y".to_string(),
"judgment with \"quote\"".to_string(),
)]),
},
"rustc 1.0.0",
);
assert!(text.find("a::g::f::y").unwrap() < text.find("b::g::f::x").unwrap());
assert_eq!(parse_budgets(&text).unwrap(), pins);
let metadata = parse_budget_file(&text).unwrap();
assert_eq!(metadata.floors["a::g::f::y"], "floor");
assert_eq!(metadata.gaps["a::g::f::y"], "judgment with \"quote\"");
assert!(
parse_budgets("\"a\" = { ir = 1 }\n").is_err(),
"outside [budgets]"
);
assert!(parse_budgets("[budgets]\n\"a\" = 1\n").is_err());
assert!(parse_budgets("[budgets]\n\"a\" = { ir = 1 }\n\"a\" = { ir = 2 }\n").is_err());
}
#[test]
fn violations_cover_both_directions_missing_and_stale_pins() {
let pins = BTreeMap::from([
("low".to_string(), 1000),
("edge".to_string(), 1000),
("high".to_string(), 1000),
("stale".to_string(), 5),
]);
let measured = BTreeMap::from([
("low".to_string(), 979),
("edge".to_string(), 1020),
("high".to_string(), 1021),
("new".to_string(), 3),
]);
let found = budget_violations(&measured, &pins, true).concat();
for bench in ["low:", "high:", "new:", "stale:"] {
assert!(found.contains(bench), "{bench} in {found}");
}
assert!(!found.contains("edge:"), "{found}");
let filtered = budget_violations(&measured, &pins, false).concat();
assert!(!filtered.contains("stale:"), "{filtered}");
}
#[test]
fn delta_keeps_the_sign_below_one_percent() {
assert_eq!(delta(1000, 995), "-0.50 %");
assert_eq!(delta(1000, 1025), "+2.50 %");
assert_eq!(delta(0, 5), "n/a");
}
#[test]
fn floor_metadata_rejects_self_duplicate_and_missing_judgment() {
for text in [
"[floors]\n\"a\" = \"a\"\n[gaps]\n\"a\" = \"x\"\n",
"[floors]\n\"a\" = \"floor\"\n\"b\" = \"floor\"\n[gaps]\n\"a\" = \"x\"\n\"b\" = \"x\"\n",
"[floors]\n\"a\" = \"floor\"\n",
"[gaps]\n\"a\" = \" \"\n",
] {
assert!(parse_budget_file(text).is_err(), "{text}");
}
}
#[test]
fn report_renders_ratio_and_unmeasured_floor_stably() {
let measured = BTreeMap::from([
("hot".to_string(), 200),
("floor".to_string(), 50),
("zero".to_string(), 1),
]);
let budgets = BudgetFile {
pins: BTreeMap::from([("hot".to_string(), 200)]),
floors: BTreeMap::from([
("hot".to_string(), "floor".to_string()),
("zero".to_string(), "absent".to_string()),
]),
gaps: BTreeMap::from([
("hot".to_string(), "tree walk".to_string()),
("zero".to_string(), "copy".to_string()),
]),
};
assert_eq!(ratio(200, 50), "4.00×");
assert_eq!(ratio(1, 0), "n/a (zero floor)");
let _ = write_perf_report(&measured, &budgets, false, "test");
let report = std::fs::read_to_string(super::target_dir().join("report/perf.md")).unwrap();
assert!(report.contains("4.00×"), "{report}");
assert!(report.contains("not measured"), "{report}");
}
#[test]
fn separator_is_stripped_once() {
let args = ["--", "--", "x"].map(std::ffi::OsString::from);
assert_eq!(without_separator(&args), &args[1..]);
assert_eq!(without_separator(&args[2..]), &args[2..]);
}
#[test]
fn names_dependency_sees_dev_and_target_tables() {
assert!(names_dependency(
"[dev-dependencies]\nsmith-bench = { path = \"..\" }",
"smith-bench"
));
assert!(names_dependency(
"[target.'cfg(unix)'.dev-dependencies.smith-bench]",
"smith-bench"
));
assert!(!names_dependency("name = \"smith-bench\"", "smith-bench"));
}
#[test]
fn detects_inline_and_table_dependencies() {
assert!(has_dependency(
"smith-core = { path = \"../smith-core\" }",
"smith-core"
));
assert!(has_dependency(
"[dependencies.smith-ai]\npath = \"../smith-ai\"",
"smith-ai"
));
}
#[test]
fn ignores_non_dependency_mentions() {
assert!(!has_dependency("# smith-core = forbidden", "smith-core"));
assert!(!has_dependency("smith_core = \"0.1\"", "smith-core"));
}
use super::{
OPT_IN_LEVEL, OPT_IN_REASON, OPT_IN_TESTS, ROOT_DENY, ROOT_OPT_IN, UB_INSTALL,
UNSAFE_TRAIT, VISIBLE_UNSAFE_FN, opts_into_unsafe, ub_test_args, unsafe_opt_in_crates,
unsafe_violations,
};
fn violations(path: &str, root: bool, source: &str) -> Vec<String> {
unsafe_violations(Path::new(path), source, root)
}
fn at(path: &str, line: usize, rule: &str) -> String {
format!("{path}:{line}: {rule}\n")
}
#[test]
fn crate_roots_deny_unsafe_code_and_never_opt_in_whole() {
let root = "a/src/lib.rs";
assert_eq!(
violations(root, true, "//! docs\nfn f() {}\n"),
[at(root, 1, ROOT_DENY)]
);
assert!(violations(root, true, "//! docs\n\n#![deny(unsafe_code)]\n").is_empty());
assert!(violations("a/src/m.rs", false, "fn f() {}\n").is_empty());
let whole =
"#![deny(unsafe_code)]\n#![expect(unsafe_code, reason = \"x\")]\n#[test]\nfn t() {}\n";
assert_eq!(violations(root, true, whole), [at(root, 2, ROOT_OPT_IN)]);
assert!(violations("a/src/raw.rs", false, whole).is_empty());
}
#[test]
fn only_deny_and_expect_may_name_unsafe_code() {
let file = "a/src/m.rs";
for level in ["forbid", "allow", "warn"] {
let source = format!("fn f() {{}}\n#[{level}(dead_code, unsafe_code)]\nmod m {{}}\n");
assert_eq!(
violations(file, false, &source),
[at(file, 2, OPT_IN_LEVEL)]
);
}
let root = "a/src/main.rs";
assert_eq!(
violations(root, true, "#![forbid(unsafe_code)]\n"),
[at(root, 1, ROOT_DENY), at(root, 1, OPT_IN_LEVEL)]
);
assert!(violations(file, false, "#[allow(dead_code)]\n// #[allow(unsafe_code)]\nlet s = \"#[allow(unsafe_code)]\";\n").is_empty());
}
#[test]
fn unsafe_opt_in_states_a_reason() {
let file = "a/src/m.rs";
for attribute in [
"#[expect(unsafe_code)]",
"#[expect(unsafe_code, reason = \"\")]",
"#[expect(unsafe_code, reason = \" \")]",
] {
let source = format!("#[test]\nfn t() {{}}\n{attribute}\nmod raw {{}}\n");
assert_eq!(
violations(file, false, &source),
[at(file, 3, OPT_IN_REASON)],
"{attribute}"
);
}
let rustfmt = "#[test]\nfn t() {}\n#[expect(\n unsafe_code,\n reason = \"no safe API, see [docs], (really)\"\n)]\nmod raw {}\n";
assert!(violations(file, false, rustfmt).is_empty());
let unreasoned = "#[test]\nfn t() {}\n#[expect(\n unsafe_code,\n)]\nmod raw {}\n";
assert_eq!(
violations(file, false, unreasoned),
[at(file, 3, OPT_IN_REASON)]
);
for reason in ["not unsafe_code", "not, unsafe_code, here"] {
let outer =
format!("#[test]\n#[allow(clippy::x, reason = \"{reason}\")]\nfn t() {{}}\n");
assert!(violations(file, false, &outer).is_empty(), "{reason}");
}
}
#[test]
fn unsafe_opt_in_lives_with_its_tests() {
let opt_in = "#[expect(unsafe_code, reason = \"no safe API\")]\nmod raw {}\n";
assert_eq!(
violations("a/src/raw.rs", false, opt_in),
[at("a/src/raw.rs", 1, OPT_IN_TESTS)]
);
assert!(
violations(
"a/src/raw.rs",
false,
&format!("{opt_in}#[test]\nfn t() {{}}\n")
)
.is_empty()
);
assert!(violations("a/tests/raw.rs", false, opt_in).is_empty());
}
#[test]
fn visible_unsafe_fns_and_unsafe_traits_are_rejected() {
let file = "a/src/m.rs";
for (line, rule) in [
("pub unsafe fn f() {}", VISIBLE_UNSAFE_FN),
(" pub(crate) unsafe fn f() {}", VISIBLE_UNSAFE_FN),
("pub(in crate::a) unsafe fn f() {}", VISIBLE_UNSAFE_FN),
("pub const unsafe fn f() {}", VISIBLE_UNSAFE_FN),
("pub unsafe extern \"C\" fn f() {}", VISIBLE_UNSAFE_FN),
("pub unsafe extern fn f() {}", VISIBLE_UNSAFE_FN),
("unsafe trait T {}", UNSAFE_TRAIT),
("pub unsafe trait T {}", UNSAFE_TRAIT),
("pub(crate) unsafe auto trait T {}", UNSAFE_TRAIT),
] {
let source = format!("fn g() {{}}\n{line}\n");
assert_eq!(
violations(file, false, &source),
[at(file, 2, rule)],
"{line}"
);
}
for line in [
"unsafe fn f() {}",
"const unsafe fn f() {}",
"unsafe impl Send for S {}",
"unsafe impl GlobalAlloc for S {",
" unsafe fn alloc(&self, layout: Layout) -> *mut u8 {",
"unsafe { f() };",
"// pub unsafe fn f() {}",
"\" pub unsafe trait T\",",
"pub fn unsafe_free() {}",
"pub trait T {}",
"trait T {}",
"pubunsafe fn f() {}",
] {
assert!(violations(file, false, line).is_empty(), "{line}");
}
}
#[test]
fn unsafe_law_skips_test_items_in_production_files() {
let file = "a/src/m.rs";
let test_items = concat!(
"fn g() {}\n",
"#[cfg(test)]\n",
"#[allow(unsafe_code)] // {\n",
"mod tests {\n",
" #[expect(unsafe_code)]\n",
" pub unsafe fn f() { \"}\"; }\n",
"}\n",
"#[test]\n",
"#[expect(\n",
" unsafe_code,\n",
")]\n",
"fn t() { unsafe trait T {} }\n",
);
assert!(violations(file, false, test_items).is_empty());
assert!(!opts_into_unsafe(test_items));
let after = concat!(
"struct S {\n",
" #[cfg(test)]\n",
" probe: u8,\n",
"}\n",
"#[cfg(test)]\n",
"use x;\n",
"pub unsafe fn f() {}\n",
"#[cfg(test)]\n",
"mod tests {}\n",
"#[expect(unsafe_code)]\n",
"mod raw {}\n",
);
assert_eq!(
violations(file, false, after),
[at(file, 10, OPT_IN_REASON), at(file, 7, VISIBLE_UNSAFE_FN)]
);
assert!(opts_into_unsafe(after));
}
#[test]
fn ub_scope_is_the_crates_that_opt_in() {
assert!(opts_into_unsafe(
"#![deny(unsafe_code)]\n#[expect(unsafe_code, reason = \"x\")]\nmod raw {}\n"
));
assert!(!opts_into_unsafe(
"#![deny(unsafe_code)]\n// #[expect(unsafe_code)]\n"
));
let args = |discovered: &[&str], extra: &[&str]| {
let extra: Vec<std::ffi::OsString> =
extra.iter().map(std::ffi::OsString::from).collect();
ub_test_args(discovered, &extra)
};
let base = ["+nightly", "miri", "test", "--locked"];
assert_eq!(
args(&["a", "b"], &["--", "--nocapture"]).unwrap(),
[&base[..], &["-p", "a", "-p", "b", "--", "--nocapture"]].concat()
);
for scope in [&["-p", "c"][..], &["--package", "c"], &["--package=c"]] {
assert_eq!(
args(&["a"], scope).unwrap(),
[&base[..], scope].concat(),
"{scope:?}"
);
assert!(args(&[], scope).is_some(), "{scope:?}");
}
assert_eq!(args(&[], &["--", "--nocapture"]), None);
assert_eq!(unsafe_opt_in_crates(Path::new("..")), ["smith-alloc"]);
}
#[test]
fn unsafe_law_walks_production_sources_only() {
let root = std::env::temp_dir().join(format!("smith_unsafe_law_{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
let write = |file: &str, source: &str| {
let path = root.join(file);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, source).unwrap();
};
write("smith/src/lib.rs", "//! docs\n");
write("smith/src/m.rs", "fn f() {}\n");
write("smith/src/bin/tool.rs", "fn main() {}\n");
let exempt = "#[expect(unsafe_code)]\nmod raw {}\npub unsafe fn f() {}\n";
for file in [
"smith/tests/t.rs",
"smith/benches/b.rs",
"smith/examples/e.rs",
"xtask/src/main.rs",
"smith-bench/src/lib.rs",
"plugins/echo/src/lib.rs",
] {
write(file, exempt);
}
write("smith-core/src/lib.rs", "#![deny(unsafe_code)]\n");
write(
"smith-core/src/raw.rs",
"#[expect(unsafe_code, reason = \"x\")]\nmod raw {}\n#[test]\nfn t() {}\n",
);
let crate_path = |file: &str| root.join(file).display().to_string();
let at = |file: &str, line: usize, rule: &str| at(&crate_path(file), line, rule);
assert_eq!(
super::unsafe_law_violations(&root),
[
at("smith/src/bin/tool.rs", 1, ROOT_DENY),
at("smith/src/lib.rs", 1, ROOT_DENY),
]
);
assert_eq!(unsafe_opt_in_crates(&root), ["smith-core"]);
assert!(super::unsafe_law_violations(Path::new("..")).is_empty());
let _ = std::fs::remove_dir_all(&root);
}
#[cfg(unix)]
#[test]
fn run_command_maps_the_exit_status() {
use std::process::{Command, ExitCode};
assert_eq!(
super::run_command(&mut Command::new("true")),
ExitCode::SUCCESS
);
assert_eq!(
super::run_command(&mut Command::new("false")),
ExitCode::FAILURE
);
assert_eq!(
super::run_command(&mut Command::new("smith-no-such-program")),
ExitCode::FAILURE
);
}
#[test]
fn ub_installs_the_newest_nightly_with_miri_into_the_users_rustup() {
assert_eq!(
UB_INSTALL,
[
"toolchain",
"install",
"nightly",
"--profile",
"minimal",
"--allow-downgrade",
"--no-self-update",
"--component",
"miri,rust-src",
]
);
let production = super::without_test_items(include_str!("main.rs"));
// Only the CI container gets its own homes; host runs keep the user's.
let redirecting: Vec<&str> = production
.split("\nfn ")
.skip(1)
.filter(|item| {
["RUSTUP_HOME", "CARGO_HOME"]
.iter()
.any(|home| item.contains(home))
})
.map(|item| item.split('(').next().unwrap_or(item))
.collect();
assert_eq!(redirecting, ["container_args"]);
assert!(!production.contains("MIRI_SYSROOT"));
assert!(super::HELP.contains("cargo x ub [args...]"));
assert!(!super::HELP.contains("x miri"));
}
#[test]
fn ub_checks_tree_borrows_and_keeps_user_flags() {
assert_eq!(super::miri_flags(None), "-Zmiri-tree-borrows");
assert_eq!(
super::miri_flags(Some("-Zmiri-backtrace=full".into())),
"-Zmiri-tree-borrows -Zmiri-backtrace=full"
);
}
#[test]
fn nightly_serves_ub_alone() {
let production = super::without_test_items(include_str!("main.rs"));
let users: Vec<&str> = production
.split("\nfn ")
.skip(1)
.filter(|item| item.contains("UB_TOOLCHAIN") || item.contains("UB_INSTALL"))
.map(|item| item.split('(').next().unwrap_or(item))
.collect();
assert_eq!(users, ["ub", "ub_test_args"]);
assert!(!production.contains("\"+nightly"));
assert!(include_str!("../../rust-toolchain.toml").contains("channel = \"stable\""));
assert!(!include_str!("../../.cargo/config.toml").contains("nightly"));
}
#[test]
fn ci_image_runs_named_gates_and_refuses_host_only_ones() {
let gates = |args: &[&str]| {
let args = os(args);
super::ci_image_gates(&args).map(|(gates, rest)| (gates, rest.to_vec()))
};
assert_eq!(gates(&[]), Ok((vec!["check", "e2e", "perf"], vec![])));
assert_eq!(
gates(&["--", "-p", "smith"]),
Ok((vec!["check", "e2e", "perf"], os(&["-p", "smith"])))
);
assert_eq!(
gates(&["test", "audit", "--", "--", "x"]),
Ok((vec!["test", "audit"], os(&["--", "x"])))
);
for refused in ["ub", "dev", "release", "ci-image", "bogus", "-p"] {
let error = gates(&["check", refused]).expect_err(refused);
assert!(error.contains(&format!("`{refused}`")), "{error}");
}
let runnable: Vec<&str> = super::COMMANDS
.iter()
.map(|(name, _)| *name)
.filter(|name| gates(&[name]).is_ok())
.collect();
assert_eq!(
runnable,
[
"check",
"lint",
"fmt",
"test",
"e2e",
"coverage",
"report",
"doc",
"arch",
"mutants",
"perf",
"bench",
"audit",
"deps",
"ci",
"bootstrap"
]
);
}
#[test]
fn ci_image_is_the_image_every_ci_job_names() {
let perf = include_str!("../../.ci/perf.kdl");
assert_eq!(super::CI_IMAGE_SOURCE, ".ci/perf.kdl");
assert_eq!(
super::ci_image_of(perf),
Some("docker.io/library/rust:latest")
);
assert_eq!(
super::ci_image_of("job \"x\" {\n run \"image\"\n image \"a:1\"\n}\n"),
Some("a:1")
);
assert_eq!(super::ci_image_of("job \"x\" {\n run \"true\"\n}\n"), None);
for job in [
include_str!("../../.ci/check.kdl"),
include_str!("../../.ci/e2e.kdl"),
include_str!("../../.ci/nightly.kdl"),
include_str!("../../.ci/report.kdl"),
] {
assert_eq!(super::ci_image_of(job), super::ci_image_of(perf));
}
}
#[test]
fn ci_image_builds_the_valgrind_of_the_ci_jobs_at_a_stable_prefix() {
let setup = super::CI_IMAGE_SETUP;
let url = setup
.split_whitespace()
.find(|word| word.starts_with("https://") && word.ends_with(".tar.bz2"))
.expect("valgrind tarball");
assert!(url.ends_with("/valgrind-3.25.1.tar.bz2"), "{url}");
assert!(include_str!("../../.ci/perf.kdl").contains(url));
assert!(include_str!("../../.ci/nightly.kdl").contains(url));
assert!(setup.contains("--prefix=/ci/valgrind "));
assert!(super::CI_IMAGE_PREFIX.contains("PATH=/ci/valgrind/bin:/ci/cargo-home/bin:$PATH"));
// No toolchain override by variable: rustup resolves the image's own.
assert!(!setup.contains("RUSTUP_TOOLCHAIN"));
assert!(setup.contains("rustup override set \"$tc\" --path /ci/work"));
assert!(setup.contains("cargo x bootstrap"));
}
#[test]
fn ci_image_container_keeps_the_callers_ids_and_mounts_the_scratch() {
use super::{Engine, container_args};
let scratch = std::path::Path::new("/repo/target/ci-image");
let args = |engine| {
container_args(
engine,
scratch,
"img:1",
"exec cargo x \"$@\"",
&os(&["perf", "--pin"]),
)
};
let tail = |ids: &[&str]| {
let mut expected = os(&[
"run",
"--rm",
"--pull=always",
"--security-opt",
"label=disable",
]);
expected.extend(os(ids));
expected.extend(os(&[
"--volume=/repo/target/ci-image:/ci",
// The default target dir, like CI: tests find artifacts under `target/`.
"--volume=/repo/target/ci-image/target:/ci/work/target",
"--workdir=/ci/work",
"--env=CARGO_HOME=/ci/cargo-home",
"--env=RUSTUP_HOME=/ci/rustup",
"--env=VALGRIND_LIB=/ci/valgrind/libexec/valgrind",
"--env=HOME=/tmp",
"img:1",
"sh",
"-c",
&format!("{}exec cargo x \"$@\"", super::CI_IMAGE_PREFIX),
"sh",
"perf",
"--pin",
]));
expected
};
assert_eq!(args(Engine::Podman), tail(&["--userns=keep-id"]));
assert_eq!(
args(Engine::Docker {
uid: 1000,
gid: 100
}),
tail(&["--user", "1000:100"])
);
assert_eq!(Engine::Podman.program(), "podman");
assert_eq!(Engine::Docker { uid: 0, gid: 0 }.program(), "docker");
}
#[test]
fn ci_image_sync_mirrors_the_source_without_build_state() {
let work = std::path::Path::new("/repo/target/ci-image/work");
let base = [
"-a",
"--checksum",
"--delete",
"--omit-dir-times",
"--exclude=target/",
"--exclude=mutants.out*",
"--exclude=/.git/",
"--exclude=/.jj/",
"--exclude=/.cargo-home/",
];
let mut sync = os(&base);
sync.extend(os(&["./", "/repo/target/ci-image/work/"]));
assert_eq!(super::sync_args(false, work), sync);
let mut proof = os(&base);
proof.extend(os(&[
"--dry-run",
"--itemize-changes",
"./",
"/repo/target/ci-image/work/",
]));
assert_eq!(super::sync_args(true, work), proof);
}
#[test]
fn ci_image_proof_names_every_difference() {
assert_eq!(
super::source_proof(""),
"work matches source: rsync --checksum dry run lists no differences\n"
);
assert_eq!(
super::source_proof(">fc.T...... smith/src/lib.rs\n"),
"work differs from source after the run:\n>fc.T...... smith/src/lib.rs\n"
);
}
#[cfg(unix)]
#[test]
fn logged_copies_output_to_the_log_and_keeps_the_status() {
let log = std::env::temp_dir().join(format!("xtask-logged-{}.log", std::process::id()));
let status = super::logged(
std::process::Command::new("sh").args(["-c", "printf 'one\\ntwo\\n'; exit 3"]),
&log,
)
.unwrap();
assert_eq!(status.code(), Some(3));
assert_eq!(std::fs::read_to_string(&log).unwrap(), "one\ntwo\n");
let _ = std::fs::remove_file(&log);
}
#[cfg(unix)]
#[test]
fn present_needs_a_program_that_runs_and_succeeds() {
assert!(super::present("true"));
assert!(!super::present("false"));
assert!(!super::present("smith-no-such-program"));
}
#[cfg(unix)]
#[test]
fn owner_is_the_caller_that_creates_files() {
use std::os::unix::fs::MetadataExt as _;
let file = std::env::temp_dir().join(format!("xtask-owner-{}", std::process::id()));
std::fs::write(&file, b"").unwrap();
let meta = std::fs::metadata(&file).unwrap();
assert_eq!(super::owner(), Some((meta.uid(), meta.gid())));
let _ = std::fs::remove_file(&file);
}
}