harn-cli 0.10.168

CLI for the Harn programming language — run, test, REPL, format, and lint
use super::*;
use std::process::Command;

struct GitFixture {
    temp: tempfile::TempDir,
    repository: PathBuf,
    lane: PathBuf,
}

impl GitFixture {
    fn command(&self, program: &str) -> Command {
        let mut command = Command::new(program);
        command
            .env_remove("GIT_DIR")
            .env_remove("GIT_COMMON_DIR")
            .env_remove("GIT_WORK_TREE")
            .env_remove("GIT_INDEX_FILE")
            .env_remove("GIT_CONFIG_NOSYSTEM")
            .env(
                "GIT_CONFIG_SYSTEM",
                self.temp.path().join("empty.gitconfig"),
            )
            .env(
                "GIT_CONFIG_GLOBAL",
                self.temp.path().join("empty.gitconfig"),
            );
        command
    }

    fn git(&self, directory: &Path, args: &[&str]) -> String {
        let output = self
            .command("git")
            .arg("-C")
            .arg(directory)
            .args([
                "-c",
                "user.name=Freshness fixture",
                "-c",
                "user.email=fixture@example.invalid",
                "-c",
                "commit.gpgsign=false",
            ])
            .args(args)
            .output()
            .unwrap();
        assert!(
            output.status.success(),
            "git {args:?}: {}",
            String::from_utf8_lossy(&output.stderr)
        );
        String::from_utf8(output.stdout).unwrap().trim().to_owned()
    }

    fn new() -> Self {
        let temp = tempfile::tempdir().unwrap();
        fs::write(temp.path().join("empty.gitconfig"), []).unwrap();
        let repository = temp.path().join("repository");
        fs::create_dir(&repository).unwrap();
        let fixture = Self {
            lane: temp.path().join("lane"),
            repository,
            temp,
        };
        fixture.git(&fixture.repository, &["init", "--initial-branch=main"]);
        fs::write(fixture.repository.join("input.harn"), b"source").unwrap();
        fixture.git(&fixture.repository, &["add", "input.harn"]);
        fixture.git(&fixture.repository, &["commit", "-m", "baseline"]);
        fixture.git(
            &fixture.repository,
            &[
                "worktree",
                "add",
                "-b",
                "lane",
                fixture.lane.to_str().unwrap(),
            ],
        );
        fixture.git(&fixture.repository, &["pack-refs", "--all"]);
        fixture
    }
}

#[test]
fn linked_worktree_freshness_tracks_selected_revision_and_source() {
    let fixture = GitFixture::new();
    let baseline = fixture.git(&fixture.lane, &["rev-parse", "HEAD"]);
    assert_eq!(baseline.len(), 40);
    let authorities = fixture.temp.path().join("authorities");
    let project = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();
    let output = fixture
        .command("bash")
        .arg(project.join("scripts/tests/fixtures/freshness-authorities.sh"))
        .arg(project.join("scripts/lib/harn_bin_freshness.sh"))
        .arg(&fixture.lane)
        .arg(&authorities)
        .output()
        .unwrap();
    assert!(
        output.status.success(),
        "authority writer exited {}: {}",
        output.status,
        String::from_utf8_lossy(&output.stderr)
    );
    let dep_info = fixture.temp.path().join("harn.d");
    fs::write(&dep_info, b"target:\n").unwrap();
    let manifest = fixture.temp.path().join("manifest");
    write_manifest(
        &manifest,
        &fixture.lane,
        &BTreeSet::from([PathBuf::from("input.harn")]),
        &dep_info,
        &[],
        &authorities,
    )
    .unwrap();
    assert_eq!(verify_manifest(&manifest).unwrap(), Verification::Fresh);

    // The shared ref store really changes while this checkout's commit does not.
    let packed_refs = PathBuf::from(fixture.git(
        &fixture.repository,
        &[
            "rev-parse",
            "--path-format=absolute",
            "--git-path",
            "packed-refs",
        ],
    ));
    let packed_before = fs::read(&packed_refs).unwrap();
    fixture.git(
        &fixture.repository,
        &["commit", "--allow-empty", "-m", "unrelated checkout"],
    );
    let other = fixture.git(&fixture.repository, &["rev-parse", "HEAD"]);
    assert_ne!(baseline, other);
    fixture.git(&fixture.repository, &["pack-refs", "--all"]);
    assert_ne!(packed_before, fs::read(&packed_refs).unwrap());
    assert_eq!(fixture.git(&fixture.lane, &["rev-parse", "HEAD"]), baseline);
    assert_eq!(verify_manifest(&manifest).unwrap(), Verification::Fresh);

    fixture.git(&fixture.lane, &["update-ref", "refs/heads/lane", &other]);
    assert!(verify_manifest(&manifest)
        .unwrap_err()
        .contains("content changed"));
    fixture.git(&fixture.lane, &["update-ref", "refs/heads/lane", &baseline]);
    fixture.git(&fixture.lane, &["checkout", "--detach", &baseline]);
    assert!(verify_manifest(&manifest)
        .unwrap_err()
        .contains("content changed"));
    fixture.git(&fixture.lane, &["checkout", "lane"]);
    assert_eq!(verify_manifest(&manifest).unwrap(), Verification::Fresh);

    let input = fixture.lane.join("input.harn");
    fs::write(&input, b"mutated").unwrap();
    assert!(verify_manifest(&manifest)
        .unwrap_err()
        .contains("content changed"));
    fs::write(input, b"source").unwrap();
    assert_eq!(verify_manifest(&manifest).unwrap(), Verification::Fresh);

    let config = PathBuf::from(fixture.git(
        &fixture.lane,
        &[
            "rev-parse",
            "--path-format=absolute",
            "--git-path",
            "config",
        ],
    ));
    let config_before = fs::read(&config).unwrap();
    fixture.git(
        &fixture.lane,
        &["config", "core.excludesfile", "/unrelated-inventory-ignore"],
    );
    assert!(verify_manifest(&manifest)
        .unwrap_err()
        .contains("content changed"));
    fs::write(config, config_before).unwrap();
    assert_eq!(verify_manifest(&manifest).unwrap(), Verification::Fresh);

    let head = PathBuf::from(fixture.git(
        &fixture.lane,
        &["rev-parse", "--path-format=absolute", "--git-path", "HEAD"],
    ));
    fs::write(&head, b"invalid HEAD\n").unwrap();
    assert!(verify_manifest(&manifest)
        .unwrap_err()
        .contains("cannot resolve"));
    fs::remove_file(head).unwrap();
    assert!(verify_manifest(&manifest)
        .unwrap_err()
        .contains("type changed"));
}