use indoc::indoc;
use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
use verctl::config::Config;
use verctl::{assets, ci, pins};
fn example() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("examples/verctl.toml")
}
#[allow(clippy::expect_used)]
fn planted() -> TempDir {
let root = TempDir::new().expect("tmp");
fs::copy(example(), root.path().join("verctl.toml")).expect("copy");
fs::write(
root.path().join("Cargo.toml"),
indoc! {r#"
[package]
name = "verctl"
version = "4.5.6"
"#},
)
.expect("cargo");
fs::write(
root.path().join("package.json"),
indoc! {r#"
{ "name": "@org/pkg", "version": "4.5.6" }
"#},
)
.expect("npm");
fs::write(root.path().join("VERSION"), "4.5.6\n").expect("plain");
root
}
#[test]
fn the_example_config_parses() {
let config = Config::load(&example()).expect("the shipped example must parse");
let names: Vec<&str> = config.packages.iter().map(|p| p.name.as_str()).collect();
assert_eq!(names, ["verctl", "@org/pkg", "py-tool"]);
assert_eq!(config.runners.len(), 2, "two machines are declared");
assert_eq!(config.publishers.len(), 1, "twine is declared");
assert_eq!(
config.pins.len(),
3,
"one structural pin and two textual ones"
);
assert_eq!(config.patterns.len(), 2, "two spellings, named once each");
}
#[test]
fn the_example_declares_nothing_a_package_does_not_use() {
let config = Config::load(&example()).expect("parse");
let claimed: Vec<&str> = config
.packages
.iter()
.filter_map(|package| package.driver.as_deref())
.collect();
for name in config.drivers.keys() {
assert!(
matches!(name.as_str(), "cargo" | "npm") || claimed.contains(&name.as_str()),
"[drivers.{name}] is declared but no package names it, so nothing renders it"
);
}
let claimed: Vec<&str> = config
.packages
.iter()
.filter_map(|package| package.publisher.as_deref())
.collect();
for name in config.publishers.keys() {
assert!(
matches!(name.as_str(), "cargo" | "bun") || claimed.contains(&name.as_str()),
"[publishers.{name}] is declared but no package names it"
);
}
}
#[test]
fn the_example_resolves_every_driver_and_publisher_it_declares() {
let root = planted();
let config = Config::load(&root.path().join("verctl.toml")).expect("parse");
let plan = verctl::publish::plan(&config, root.path()).expect("plan the publish");
let resolved: Vec<(&str, &str, &str, Vec<&str>)> = plan
.packages
.iter()
.map(|entry| {
(
entry.name.as_str(),
entry.version.as_str(),
entry.noun.as_str(),
entry.argv.iter().map(String::as_str).collect(),
)
})
.collect();
assert_eq!(
resolved,
[
(
"verctl",
"4.5.6",
"crate",
vec![
"cargo",
"publish",
"--locked",
"--manifest-path",
root.path().join("Cargo.toml").to_str().expect("utf-8"),
],
),
(
"@org/pkg",
"4.5.6",
"package",
vec![
"bun",
"publish",
"--tolerate-republish",
"--cwd",
root.path().to_str().expect("utf-8"),
"--registry",
"https://npm.pkg.github.com",
],
),
("py-tool", "4.5.6", "package", vec!["uv", "publish"]),
]
);
}
#[test]
fn the_example_command_driver_reads_and_writes_through_stdio() {
let config = Config::load(&example()).expect("parse");
let spec = config.drivers["plain"].clone();
let driver = spec.into_driver("plain").expect("resolve");
assert_eq!(
driver.read("4.5.6\n").expect("read the manifest on stdin"),
"4.5.6"
);
assert_eq!(
driver
.write("4.5.6\n", "5.0.0")
.expect("write the whole new file to stdout"),
"5.0.0\n"
);
}
#[test]
fn the_example_ci_table_plans_the_checks_it_documents() {
let config = Config::load(&example()).expect("parse");
let plan = ci::plan(&config).expect("plan");
let checks: Vec<(&str, String)> = plan
.matrix
.include
.iter()
.map(|job| (job.name.as_str(), job.labels.join(", ")))
.collect();
assert_eq!(
checks,
[
("audit", "nscloud-ubuntu-24.04-amd64-4x8".to_owned()),
("verify (ns)", "nscloud-ubuntu-24.04-amd64-4x8".to_owned()),
("verify (big)", "self-hosted, linux, x64".to_owned()),
],
"the comments above [ci] promise exactly these checks"
);
}
#[test]
fn the_example_assets_table_plans_the_targets_it_documents() {
let root = planted();
let config = Config::load(&root.path().join("verctl.toml")).expect("parse");
let plan = assets::plan(&config, root.path()).expect("plan");
let targets: Vec<(&str, String, &str, &str)> = plan
.matrix
.include
.iter()
.map(|target| {
(
target.id.as_str(),
target.labels.join(", "),
target.asset.as_str(),
target.triple.as_str(),
)
})
.collect();
assert_eq!(
targets,
[
(
"darwin-arm64",
"macos-latest".to_owned(),
"verctl_4.5.6_macos_arm64.tar.gz",
"aarch64-apple-darwin"
),
(
"linux-arm64",
"nscloud-ubuntu-24.04-amd64-4x8".to_owned(),
"verctl_4.5.6_linux_arm64.tar.gz",
"aarch64-unknown-linux-gnu"
),
(
"linux-x64",
"self-hosted, linux, x64".to_owned(),
"verctl_4.5.6_linux_x64.tar.gz",
"x86_64-unknown-linux-gnu"
),
],
"a built-in machine, a declared one, and a user-defined platform"
);
}
#[test]
fn the_example_pin_rewrites_the_file_it_names() {
let root = planted();
let config = Config::load(&root.path().join("verctl.toml")).expect("parse");
fs::create_dir_all(root.path().join("examples")).expect("examples dir");
fs::write(
root.path().join("examples/mise.toml"),
indoc! {r#"
[tools]
"github:victor-software-house/verctl" = "0.0.1"
[task_config]
includes = [
"git::https://github.com/victor-software-house/verctl.git//tasks/ver?ref=v0.0.1",
]
"#},
)
.expect("pin file");
fs::write(
root.path().join("README.md"),
indoc! {"
Install: mise x github:victor-software-house/verctl@0.0.1 -- verctl
Pin verctl@0.0.1 in CI, and verctl@0.0.1 locally.
"},
)
.expect("doc");
fs::create_dir_all(root.path().join("docs")).expect("docs dir");
fs::write(
root.path().join("docs/install.md"),
indoc! {"
One line: github:victor-software-house/verctl@0.0.1
"},
)
.expect("second doc");
let versions = pins::current_versions(root.path(), &config).expect("read versions");
let written = pins::write(root.path(), &config.pins, &versions).expect("rewrite the pins");
assert_eq!(
written,
[
root.path().join("examples/mise.toml"),
root.path().join("README.md"),
root.path().join("docs/install.md"),
]
);
assert_eq!(
fs::read_to_string(root.path().join("README.md")).expect("reread"),
indoc! {"
Install: mise x github:victor-software-house/verctl@4.5.6 -- verctl
Pin verctl@4.5.6 in CI, and verctl@4.5.6 locally.
"},
"every spelling this file lists moves, at the arity the pattern declares"
);
assert_eq!(
fs::read_to_string(root.path().join("docs/install.md")).expect("reread"),
indoc! {"
One line: github:victor-software-house/verctl@4.5.6
"},
"the shared pattern moves in the second file that lists it too"
);
let body = fs::read_to_string(root.path().join("examples/mise.toml")).expect("reread");
assert_eq!(
body,
indoc! {r#"
[tools]
"github:victor-software-house/verctl" = "4.5.6"
[task_config]
includes = [
"git::https://github.com/victor-software-house/verctl.git//tasks/ver?ref=v4.5.6",
]
"#},
"the tool pin and the ?ref= move together, and nothing else moves"
);
}