#![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 \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 [-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 [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 = 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 ` pairs /// rather than conflicting. fn scoped(base: &[&str], extra: &[OsString]) -> Vec { 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 ` 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`, `.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::>().join(" ") ) }) }) .collect::, _>>()?; if gates.is_empty() { gates = CI_IMAGE_DEFAULT.to_vec(); } Ok((gates, passthrough)) } /// The first `image ""` 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 { let mut args: Vec = ["-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 { let mut run: Vec = [ "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 { 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) -> 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 one file or directory, e.g. -f smith-core/src/session.rs -D mutants on changed lines only (jj diff --git > /tmp/d.patch) -p one crate --shard k/n one of n equal shards, e.g. --shard 0/8 -F/-E 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> { if discovered.is_empty() && !narrows(extra) { return None; } let toolchain = format!("+{UB_TOOLCHAIN}"); let mut args: Vec = [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 { 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 = 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 `. fn bench(extra: &[OsString]) -> ExitCode { let mut args: Vec = [ "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 { 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, 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) } /// `(::, 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 { 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, floors: BTreeMap, gaps: BTreeMap, } /// 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 { 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 = }}", 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, 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 { 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, 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 { 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 { 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) { 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, pins: &BTreeMap, complete: bool, ) -> Vec { 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, 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 ` 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> { 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 { 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 = ["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` `` root. fn junit_counts(xml: &str) -> [u64; 3] { let root = xml .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 { 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 = 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 = 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 = 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 { 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> = 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 { 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 { 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 { 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 { let mut dependencies = Vec::new(); let mut in_dependencies = false; let mut dependency_table: Option = 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, 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 { 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 { 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 { 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 + '_ { 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 { 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::() )); } } } 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 { 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) { let Ok(entries) = fs::read_dir(dir) else { return; }; let mut entries: Vec = 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 { 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 { 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 { 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 { 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 + '_ { 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 { 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 = 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#""#; assert_eq!(super::junit_counts(xml), [19, 1, 0]); assert_eq!(super::junit_counts(""), [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 { 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 = 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 = 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 = 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 { 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::>(), [ ("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 { 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 = 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); } }