qrab 0.1.4

A readable quantum-circuit compiler for TikZ, Typst, SVG, and Quirk
Documentation
use std::fs;

use assert_cmd::cargo::cargo_bin_cmd;
use predicates::prelude::*;

#[test]
fn clap_handles_help_errors_delimiters_and_conflicts() {
    cargo_bin_cmd!("qrab")
        .arg("--help")
        .assert()
        .success()
        .stdout(predicate::str::contains("Usage: qrab <COMMAND>"));
    cargo_bin_cmd!("qrab")
        .args(["check", "--version"])
        .assert()
        .success()
        .stdout(predicate::str::contains(env!("CARGO_PKG_VERSION")));
    cargo_bin_cmd!("qrab")
        .args(["compil", "missing.qrab"])
        .assert()
        .code(2)
        .stderr(predicate::str::contains("compile"));
    cargo_bin_cmd!("qrab")
        .args(["compile", "missing.qrab", "-o", "--target"])
        .assert()
        .code(2)
        .stderr(predicate::str::contains("--output <OUTPUT>"));
    cargo_bin_cmd!("qrab")
        .args(["compile", "missing.qrab", "-t", "latex", "-t", "typst"])
        .assert()
        .code(2)
        .stderr(predicate::str::contains("cannot be used multiple times"));
    cargo_bin_cmd!("qrab")
        .args([
            "compile",
            "missing.qrab",
            "-o",
            "output.tex",
            "--target",
            "all",
        ])
        .assert()
        .code(2)
        .stderr(predicate::str::contains(
            "cannot be used with '--target all'",
        ));

    let directory =
        std::env::temp_dir().join(format!("qrab-cli-{}-{}", std::process::id(), line!()));
    fs::create_dir(&directory).expect("create CLI test directory");
    let source = directory.join("input.qrab");
    fs::write(&source, "circuit cli { qubit q; h q }\n").expect("write CLI test source");
    cargo_bin_cmd!("qrab")
        .args(["compile", "--"])
        .arg(&source)
        .assert()
        .success();
    assert!(source.with_extension("tex").is_file());
    assert!(source.with_extension("typ").is_file());
    assert!(source.with_extension("svg").is_file());
    assert!(source.with_extension("url").is_file());
    assert!(
        fs::read_to_string(source.with_extension("url"))
            .expect("read Quirk URL")
            .starts_with("https://algassert.com/quirk#circuit=")
    );
    fs::remove_dir_all(directory).expect("remove CLI test directory");

    let missing = std::env::temp_dir().join(format!(
        "qrab-missing-source-{}-{}.qrab",
        std::process::id(),
        line!()
    ));
    cargo_bin_cmd!("qrab")
        .arg("check")
        .arg(missing)
        .assert()
        .code(1)
        .stderr(predicate::str::contains("cannot read"))
        .stderr(predicate::str::contains("Usage:").not());
}

#[test]
fn installs_agent_skill_without_overwriting_changes() {
    let directory =
        std::env::temp_dir().join(format!("qrab-skill-{}-{}", std::process::id(), line!()));
    fs::create_dir(&directory).expect("create skill test directory");
    let skill = directory.join(".agents/skills/qrab/SKILL.md");

    cargo_bin_cmd!("qrab")
        .current_dir(&directory)
        .arg("install-skill")
        .assert()
        .success()
        .stdout(predicate::str::contains(
            "installed .agents/skills/qrab/SKILL.md",
        ));
    assert_eq!(
        fs::read_to_string(&skill).expect("read installed skill"),
        include_str!("../.agents/skills/qrab/SKILL.md")
    );

    cargo_bin_cmd!("qrab")
        .current_dir(&directory)
        .arg("install-skill")
        .assert()
        .success()
        .stdout(predicate::str::contains("already installed"));

    fs::write(&skill, "local changes\n").expect("customize installed skill");
    cargo_bin_cmd!("qrab")
        .current_dir(&directory)
        .arg("install-skill")
        .assert()
        .failure()
        .stderr(predicate::str::contains("refusing to overwrite"));
    assert_eq!(
        fs::read_to_string(skill).expect("read customized skill"),
        "local changes\n"
    );
    fs::remove_dir_all(directory).expect("remove skill test directory");
}

/// The man pages ship in every release archive, and their version comes from
/// whichever crate compiled `cli.rs`. That is `xtask`, not this one, so the
/// override in `xtask` is the only thing keeping them from advertising a
/// version that was never released.
#[test]
fn shipped_man_pages_carry_this_crate_version() {
    let version = env!("CARGO_PKG_VERSION");
    for page in [
        "qrab.1",
        "qrab-check.1",
        "qrab-compile.1",
        "qrab-install-skill.1",
    ] {
        let path = concat!(env!("CARGO_MANIFEST_DIR"), "/assets/");
        let text = fs::read_to_string(format!("{path}{page}"))
            .unwrap_or_else(|error| panic!("reading assets/{page}: {error}"));
        assert!(
            text.contains(version),
            "assets/{page} does not mention version {version}; run `just gen-assets`"
        );
    }
}

#[test]
fn miette_reports_imported_source_help_and_multiple_errors() {
    let directory = std::env::temp_dir().join(format!(
        "qrab-diagnostics-{}-{}",
        std::process::id(),
        line!()
    ));
    fs::create_dir(&directory).expect("create diagnostic test directory");
    fs::write(
        directory.join("gates.qrab"),
        "fn broken(a) { // context before\n\th missing\n} // context after\n",
    )
    .expect("write imported source");
    let imported = directory.join("imported.qrab");
    fs::write(
        &imported,
        "import \"gates.qrab\"\ncircuit imported { qubit q; broken(q) }\n",
    )
    .expect("write importing source");
    cargo_bin_cmd!("qrab")
        .env("NO_COLOR", "1")
        .arg("check")
        .arg(&imported)
        .assert()
        .code(1)
        .stderr(predicate::str::contains("gates.qrab:2:4"))
        .stderr(predicate::str::contains("context before"))
        .stderr(predicate::str::contains("context after"))
        .stderr(predicate::str::contains("h missing"))
        .stderr(predicate::str::contains(
            "declare it before use or enable `autowires`",
        ));

    let multiple = directory.join("multiple.qrab");
    fs::write(
        &multiple,
        "circuit bad {\n\tqubit q\n\th missing\n\tx absent\n}\n",
    )
    .expect("write multiple-error source");
    cargo_bin_cmd!("qrab")
        .env("NO_COLOR", "1")
        .arg("check")
        .arg(&multiple)
        .assert()
        .code(1)
        .stderr(predicate::str::contains("2 errors found"))
        .stderr(predicate::str::contains("unknown wire `missing`"))
        .stderr(predicate::str::contains("unknown wire `absent`"));

    fs::remove_dir_all(directory).expect("remove diagnostic test directory");
}