fairlead 0.15.2

Guardrails and a guided path for coding agents: rules enforced as the agent works, tests scoped to what a change can reach, capped project memory, and KPIs.
//! `fairlead ci workflow`: the staged workflow made from a config, the `if:`
//! lines for a workflow a team wrote, and `--write` replacing only a file
//! the command wrote itself.

use std::path::{Path, PathBuf};
use std::process::Command;

const CONFIG: &str = r#"
[stages]
environments = ["main", "release/*"]

[[tests.runners]]
id = "unit"
match = ["test/**"]
command = ["sh", "-c", "exit 0", "sh", "{files}"]
from = "draft"

[[tests.runners]]
id = "e2e"
match = ["e2e/**"]
command = ["sh", "-c", "exit 0", "sh", "{files}"]
from = "merge"

[[checks]]
id = "desktop-build"
command = ["sh", "-c", "exit 0"]
paths = ["src/**"]
from = "merge"
"#;

const WORKFLOW: &str = ".github/workflows/fairlead.yml";

fn fixture() -> String {
    let path =
        Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/ci-workflow/fairlead.yml");
    // A Windows checkout gives the fixture CRLF; the generator writes LF everywhere.
    std::fs::read_to_string(path).unwrap().replace("\r\n", "\n")
}

fn repo(name: &str) -> PathBuf {
    let dir = std::env::temp_dir().join(format!("fairlead-workflow-{name}-{}", std::process::id()));
    let _ = std::fs::remove_dir_all(&dir);
    std::fs::create_dir_all(&dir).unwrap();
    let git = Command::new("git")
        .args(["init", "-q", "-b", "main"])
        .current_dir(&dir)
        .env("GIT_CONFIG_NOSYSTEM", "1")
        .output()
        .unwrap();
    assert!(git.status.success());
    std::fs::write(dir.join("fairlead.toml"), CONFIG).unwrap();
    dir
}

fn workflow(dir: &Path, args: &[&str]) -> (i32, String, String) {
    let mut cmd = Command::new(env!("CARGO_BIN_EXE_fairlead"));
    cmd.args(["ci", "workflow"])
        .args(args)
        .current_dir(dir)
        .env_clear();
    if let Some(path) = std::env::var_os("PATH") {
        cmd.env("PATH", path);
    }
    let out = cmd.env("HOME", dir).output().unwrap();
    (
        out.status.code().unwrap_or(-1),
        String::from_utf8_lossy(&out.stdout).into_owned(),
        String::from_utf8_lossy(&out.stderr).into_owned(),
    )
}

/// The release a generated file installs, as the fixture spells it.
fn unversioned(text: &str) -> String {
    let pinned = format!("version: v{}\n", env!("CARGO_PKG_VERSION"));
    text.replace(&pinned, "version: vX.Y.Z\n")
}

#[test]
fn the_workflow_for_a_staged_config_is_the_fixture_and_names_each_gated_jobs_if_line() {
    let dir = repo("print");
    let (code, out, err) = workflow(&dir, &[]);
    assert_eq!(code, 0, "{err}");
    assert_eq!(unversioned(&out), fixture());
    assert!(
        err.contains("  if: needs.plan.outputs.run_e2e == 'true'   # e2e\n"),
        "{err}"
    );
    assert!(
        err.contains("  if: needs.plan.outputs.run_desktop_build == 'true'   # desktop-build\n"),
        "{err}"
    );
    assert!(!err.contains("run_unit"), "{err}");
    assert!(!dir.join(WORKFLOW).exists());
}

#[test]
fn write_refuses_a_hand_written_file_and_rewrites_its_own() {
    let dir = repo("write");
    let path = dir.join(WORKFLOW);
    std::fs::create_dir_all(path.parent().unwrap()).unwrap();
    std::fs::write(&path, "name: ours\n").unwrap();
    let (code, _, err) = workflow(&dir, &["--write"]);
    assert_eq!(code, 2, "{err}");
    assert!(
        err.contains(".github/workflows/fairlead.yml wasn't written by `fairlead ci workflow`"),
        "{err}"
    );
    assert_eq!(std::fs::read_to_string(&path).unwrap(), "name: ours\n");

    let stale = "# Generated by `fairlead ci workflow` from the Fairlead config.\nname: old\n";
    std::fs::write(&path, stale).unwrap();
    let (code, out, err) = workflow(&dir, &["--write"]);
    assert_eq!(code, 0, "{err}");
    assert!(
        out.starts_with("fairlead: rewrote .github/workflows/fairlead.yml\n"),
        "{out}"
    );
    assert!(out.contains("run_e2e == 'true'"), "{out}");
    assert_eq!(
        unversioned(&std::fs::read_to_string(&path).unwrap()),
        fixture()
    );

    let (code, out, _) = workflow(&dir, &["--write"]);
    assert_eq!(code, 0);
    assert!(out.starts_with("fairlead: unchanged"), "{out}");
}

#[test]
fn write_creates_the_workflow_where_there_is_none() {
    let dir = repo("fresh");
    let (code, out, err) = workflow(&dir, &["--write"]);
    assert_eq!(code, 0, "{err}");
    assert!(
        out.starts_with("fairlead: wrote .github/workflows/fairlead.yml\n"),
        "{out}"
    );
    let text = std::fs::read_to_string(dir.join(WORKFLOW)).unwrap();
    assert_eq!(unversioned(&text), fixture());
}

#[test]
fn the_workflow_pins_actions_as_this_repositorys_own_workflows_do() {
    let workflows = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../.github/workflows");
    let ours: String = std::fs::read_dir(&workflows)
        .unwrap()
        .flatten()
        .map(|e| std::fs::read_to_string(e.path()).unwrap_or_default())
        .collect();
    let text = fixture();
    let pins: Vec<&str> = text
        .lines()
        .filter_map(|l| l.trim().strip_prefix("- uses: "))
        .filter_map(|u| u.split_whitespace().next())
        .collect();
    assert!(pins.len() >= 3, "{pins:?}");
    for pin in pins {
        let (_, sha) = pin.split_once('@').unwrap();
        assert_eq!(sha.len(), 40, "{pin} isn't pinned to a commit");
        assert!(
            ours.contains(pin),
            "{pin} differs from this repository's own pin"
        );
    }
}

#[test]
fn the_insights_workflow_runs_weekly_runs_no_tests_and_is_written_beside_the_test_workflow() {
    let dir = repo("insights");
    let (code, text, err) = workflow(&dir, &["--insights"]);
    assert_eq!(code, 0, "{err}");
    assert!(
        text.starts_with("# Generated by `fairlead ci workflow` --insights"),
        "{text}"
    );
    assert!(
        text.contains("  schedule:\n    - cron: \"17 5 * * 1\""),
        "{text}"
    );
    for scope in ["contents", "actions", "pull-requests", "checks"] {
        assert!(
            text.contains(&format!("  {scope}: read")),
            "replay fetch needs {scope}: {text}"
        );
    }
    assert!(
        !text.contains("fairlead ci run") && !text.contains("pull_request"),
        "a schedule here starts no test run: {text}"
    );
    assert!(text
        .contains("fairlead replay fetch --repo \"$GITHUB_REPOSITORY\" --workflow fairlead.yml"));
    assert!(text.contains("fairlead insights --since"));
    // The replay line, run as written, gets past argument parsing: it names every flag `replay run` requires.
    let line = text
        .lines()
        .map(str::trim)
        .find(|l| l.starts_with("fairlead replay run "))
        .expect("the job replays");
    assert!(line.contains("--config fairlead.toml"), "{line}");
    let args: Vec<String> = line
        .split_whitespace()
        .skip(1)
        .take_while(|a| *a != ">")
        .map(|a| {
            a.trim_matches('"')
                .replace("$RUNNER_TEMP", &dir.display().to_string())
        })
        .collect();
    let out = std::process::Command::new(env!("CARGO_BIN_EXE_fairlead"))
        .args(&args)
        .current_dir(&dir)
        .output()
        .unwrap();
    let err = String::from_utf8_lossy(&out.stderr);
    assert!(
        !err.contains("required arguments were not provided"),
        "{err}"
    );
    assert!(!err.contains("unexpected argument"), "{err}");
    let (code, out, _) = workflow(&dir, &["--insights", "--write"]);
    assert_eq!(code, 0);
    assert!(
        out.contains("wrote .github/workflows/fairlead-insights.yml"),
        "{out}"
    );
    assert!(
        !dir.join(".github/workflows/fairlead.yml").exists(),
        "the test workflow is a separate file"
    );
    let (_, out, _) = workflow(&dir, &["--insights", "--write"]);
    assert!(out.contains("unchanged"), "{out}");
    std::fs::write(
        dir.join(".github/workflows/fairlead-insights.yml"),
        "name: ours\n",
    )
    .unwrap();
    let (code, _, err) = workflow(&dir, &["--insights", "--write"]);
    assert_eq!(code, 2);
    assert!(
        err.contains("wasn't written by `fairlead ci workflow`"),
        "{err}"
    );
}