drep-ai 2.5.0

A local commit gate: runs the linters your repo configures, and sends changed code to an LLM for review
Documentation
//! What a release ships, checked against what this repository actually is.
//!
//! `dist-workspace.toml` and `.github/workflows/release.yml` are generated by
//! `dist`, while `.github/workflows/rust.yml` is shared by two forges with
//! different runners. Nothing in the Rust suite otherwise reads those delivery
//! contracts, and the first sign of a mistake is a release that already happened
//! or a job that never runs. As in `published_hooks.rs`, these assertions are
//! textual: adding TOML and YAML parsers to state a few exact facts would buy
//! nothing. `Cargo.toml` is the other case - this project hand-edits it - so the
//! one assertion over it parses.

mod common;

fn dist_config() -> String {
    common::without_comments("dist-workspace.toml")
}

fn rust_workflow() -> String {
    common::without_comments(".github/workflows/rust.yml")
}

fn release_workflow() -> String {
    common::without_comments(".github/workflows/release.yml")
}

fn workflow_job<'a>(workflow: &'a str, name: &str) -> &'a str {
    let marker = format!("\n  {name}:\n");
    let start = workflow
        .find(&marker)
        .unwrap_or_else(|| panic!("workflow must declare a {name} job"))
        + marker.len();
    let tail = &workflow[start..];
    let end = tail
        .match_indices('\n')
        .find_map(|(offset, _)| {
            let next_line = &tail[offset + 1..];
            (next_line.starts_with("  ") && !next_line.starts_with("   ")).then_some(offset)
        })
        .unwrap_or(tail.len());
    &tail[..end]
}

/// Family Gitea has a Linux runner and GitHub has the hosted macOS runner.
///
/// Keeping both platforms in one matrix asks Gitea to dispatch a
/// `macos-latest` task that no family runner can claim, so the workflow stays
/// pending forever even after every runnable job finishes. Gitea matches a
/// runner before evaluating the guard, so the macOS job uses a claimable Linux
/// label there and skips its steps; GitHub retains the native macOS lane.
#[test]
fn family_ci_runs_linux_without_queueing_hosted_macos() {
    let workflow = rust_workflow();
    let linux = workflow_job(&workflow, "test-linux");
    assert!(
        linux.contains("runs-on: ubuntu-latest"),
        "the family runner must retain the Linux test lane"
    );
    assert!(
        linux.contains("cargo test --all-targets --all-features"),
        "the Linux lane must run the complete test suite"
    );

    let macos = workflow_job(&workflow, "test-macos");
    assert!(
        macos.contains("if: ${{ github.server_url == 'https://github.com' }}"),
        "the hosted macOS lane must not execute on Gitea"
    );
    assert!(
        macos.contains(
            "runs-on: ${{ github.server_url == 'https://github.com' && 'macos-latest' || 'ubuntu-latest' }}"
        ),
        "GitHub must use native macOS while Gitea gets a claimable label for its guarded skip"
    );
}

/// The prebuilt mutation tool and its Linux userspace move together.
///
/// Strix maps `ubuntu-latest` to Debian 12, whose glibc is older than the one
/// cargo-mutants 27.1.0 requires. A per-job Debian 13 container fixes that
/// compatibility boundary without changing every family-runner workload, and
/// pinning the tool prevents an unrelated latest release moving it again.
#[test]
fn mutation_ci_pins_a_compatible_container_and_tool() {
    let workflow = rust_workflow();
    let mutants = workflow_job(&workflow, "mutants");
    assert!(
        mutants.contains("container: node:22-trixie"),
        "cargo-mutants needs the Debian 13 glibc supplied by node:22-trixie"
    );
    assert!(
        mutants.contains("tool: cargo-mutants@27.1.0"),
        "the mutation gate must pin the binary whose glibc contract was verified"
    );
}

/// A no-argument full sweep must remain a genuinely empty cargo-mutants scope.
///
/// `printf '%q' "$@"` prints `''` when the argument list is empty. Embedded
/// directly in the remote command, that becomes one empty argument and makes
/// cargo-mutants reject the invocation before its baseline runs.
#[test]
fn remote_full_mutation_sweep_passes_no_phantom_argument() {
    let script = common::without_comments("scripts/mutants-remote.sh");

    assert!(
        script.contains("if [ \"$#\" -gt 0 ]; then")
            && script.contains("printf -v REMOTE_ARGS ' %q' \"$@\"")
            && script.contains("./scripts/mutants-run.sh$REMOTE_ARGS"),
        "the remote wrapper must append quoted arguments only when at least one exists"
    );
    assert!(
        !script.contains("$(printf '%q ' \"$@\")"),
        "empty positional parameters must not be formatted into a literal empty argument"
    );
}

/// The platforms a release builds for.
///
/// A target that falls out of this list does not fail anything: the installer
/// keeps working everywhere else and tells that one user "unsupported
/// platform". Both Linux triples are built on native runners, so dropping one
/// saves nothing that would justify it.
#[test]
fn every_supported_platform_is_built() {
    let config = dist_config();
    for target in [
        "aarch64-apple-darwin",
        "x86_64-apple-darwin",
        "x86_64-unknown-linux-gnu",
        "aarch64-unknown-linux-gnu",
    ] {
        assert!(
            config.contains(target),
            "a release no longer builds for {target}"
        );
    }
}

/// The Homebrew formula needs all three of these keys, and two of them are
/// silent when missing.
///
/// `installers` without `tap` fails at generate time, which is loud. But a
/// `tap` without `homebrew` in `publish-jobs` builds the formula into the
/// GitHub release and never pushes it to the tap, so `brew install` keeps
/// serving whatever version was last pushed by hand. `formula` is what the
/// user types: the crate carries the `-ai` suffix only because `drep` is taken
/// on crates.io, and a tap is already namespaced by its owner.
#[test]
fn the_homebrew_formula_is_pushed_to_the_tap() {
    let config = dist_config();
    assert!(
        config.contains(r#"installers = ["shell", "homebrew"]"#),
        "a release must publish both the shell installer and the formula"
    );
    assert!(
        config.contains(r#"tap = "slb350/homebrew-tap""#),
        "the formula has no tap to be pushed to"
    );
    assert!(
        config.contains(r#"publish-jobs = ["homebrew"]"#),
        "without the homebrew publish job the formula is built and never pushed"
    );
    assert!(
        config.contains(r#"formula = "drep""#),
        "the formula must be named for the binary, not for the crate"
    );
}

/// The pinned version and the version CI installs are the same one.
///
/// `cargo-dist-version` decides which `dist` the release workflow downloads,
/// and the workflow is generated from it. Editing the config without running
/// `dist init` leaves the two disagreeing, and the release is then planned by a
/// version of `dist` that never saw the config change.
#[test]
fn the_pinned_dist_version_is_the_one_ci_installs() {
    let config = dist_config();
    let pinned = config
        .lines()
        .find_map(|line| line.strip_prefix("cargo-dist-version = "))
        .expect("dist-workspace.toml must pin a dist version")
        .trim()
        .trim_matches('"');
    let workflow = release_workflow();
    assert!(
        workflow.contains(&format!("cargo-dist/releases/download/v{pinned}/")),
        "release.yml installs a different dist than the config pins ({pinned}) - run `dist init`"
    );
}

/// Gitea 1.25.1 and the family runner implement the v4 artifact protocol.
///
/// Newer generated action revisions send fields that Gitea does not understand,
/// so the release fails in `plan` before `dist` can hand work to any native
/// build runner. Pin every upload and download step together: mixing protocols
/// can upload successfully and then leave a later job unable to fetch its input.
#[test]
fn release_artifact_actions_use_the_compatible_v4_protocol() {
    let config = dist_config();
    let workflow = release_workflow();
    let artifact_actions = workflow
        .lines()
        .map(str::trim)
        .filter_map(|line| line.strip_prefix("uses: actions/"))
        .filter(|action| {
            action.starts_with("upload-artifact@") || action.starts_with("download-artifact@")
        })
        .collect::<Vec<_>>();

    assert!(
        artifact_actions
            .iter()
            .any(|action| action.starts_with("upload-artifact@")),
        "release.yml must upload build artifacts"
    );
    assert!(
        artifact_actions
            .iter()
            .any(|action| action.starts_with("download-artifact@")),
        "release.yml must download artifacts between jobs"
    );
    assert!(
        artifact_actions
            .iter()
            .all(|action| action.ends_with("@v4")),
        "all release artifact actions must use v4, found: {artifact_actions:?}"
    );
    assert!(
        config.contains(r#"allow-dirty = ["ci"]"#),
        "cargo-dist must permit the tested release.yml compatibility override"
    );
}

/// Released binaries are built with the profile this crate tuned.
///
/// `dist init` writes `[profile.dist]` as `inherits = "release"` plus
/// `lto = "thin"`, which is a build-time default and not a decision about this
/// crate. `[profile.release]` here sets fat LTO, one codegen unit, `strip` and
/// `panic = "abort"`, so any key added under `[profile.dist]` is one of those
/// choices being reverted for the only binaries users ever run. The assertion
/// is over the parsed keys rather than the text: `Cargo.toml` is hand-edited,
/// and reformatting the line is not the mistake being guarded against.
#[test]
fn released_binaries_inherit_the_tuned_release_profile() {
    let path = concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml");
    let raw = std::fs::read_to_string(path).expect("Cargo.toml must be readable");
    let manifest: toml::Table = toml::from_str(&raw).expect("Cargo.toml must parse");
    let profile = manifest["profile"]
        .get("dist")
        .and_then(toml::Value::as_table)
        .expect("Cargo.toml must declare the profile dist builds with");
    assert_eq!(
        profile.keys().map(String::as_str).collect::<Vec<_>>(),
        ["inherits"],
        "[profile.dist] must add nothing to [profile.release]"
    );
    assert_eq!(profile["inherits"].as_str(), Some("release"));
}