use std::path::{Path, PathBuf};
use std::process::Command;
fn repository() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../../..")
.canonicalize()
.expect("the repository root resolves")
}
struct Root {
at: PathBuf,
}
impl Root {
fn new(label: &str) -> Root {
let at = std::env::temp_dir().join(format!(
"headwater-cli-validate-{}-{label}",
std::process::id()
));
let _ = std::fs::remove_dir_all(&at);
std::fs::create_dir_all(&at).expect("the root is made");
let repository = repository();
copy(
&repository.join("packages/headwater-standard"),
&at.join("packages/headwater-standard"),
);
copy(
&repository.join("docs/taxonomies"),
&at.join("docs/taxonomies"),
);
for name in ["taxonomy.yml", "overlay.yml"] {
let to = at.join(".headwater").join(name);
std::fs::create_dir_all(to.parent().expect("it has a parent"))
.expect("the declaration directory is there");
std::fs::copy(repository.join(".headwater").join(name), to)
.expect("the declaration copies");
}
Root { at }
}
fn run(&self, arguments: &[&str]) -> Ran {
let output = Command::new(env!("CARGO_BIN_EXE_headwater"))
.args(arguments)
.arg("--root")
.arg(&self.at)
.output()
.expect("the binary runs");
Ran {
code: output.status.code(),
out: String::from_utf8_lossy(&output.stdout).into_owned(),
err: String::from_utf8_lossy(&output.stderr).into_owned(),
}
}
fn founds_a_kind(&self) {
let overlay = self.at.join(".headwater/overlay.yml");
let text = std::fs::read_to_string(&overlay).expect("the overlay reads");
let anchor = " shelves.tutorials:\n";
assert!(
text.contains(anchor),
"the overlay still declares a tutorials shelf"
);
let added = format!(
" kinds.playbook.is_a: governed_document\n kinds.playbook.purpose: \
behavior\n shelves.playbooks: {{path: docs/playbooks/**, homogeneous: true, kind: \
playbook}}\n\n{anchor}"
);
std::fs::write(&overlay, text.replacen(anchor, &added, 1)).expect("the overlay writes");
}
fn drops_a_display_name(&self) {
let overlay = self.at.join(".headwater/overlay.yml");
let text = std::fs::read_to_string(&overlay).expect("the overlay reads");
let anchor = " title: Tutorials\n";
assert!(
text.contains(anchor),
"the overlay still gives the tutorials shelf a display name"
);
std::fs::write(&overlay, text.replacen(anchor, "", 1)).expect("the overlay writes");
}
fn drops_the_bundle_the_others_read(&self) -> Vec<String> {
let path = self.at.join(".headwater/taxonomy.yml");
let text = std::fs::read_to_string(&path).expect("the consumer declaration reads");
let line = text
.lines()
.find(|line| line.trim_start().starts_with("bundles:"))
.expect("the consumer declares `bundles:`")
.to_string();
let inside = line
.split_once('[')
.and_then(|(_, rest)| rest.split_once(']'))
.map(|(inside, _)| inside)
.expect("`bundles:` is a flow sequence");
let held: Vec<String> = inside
.split(',')
.map(|name| name.trim().to_string())
.filter(|name| !name.is_empty())
.collect();
assert!(
held.iter().any(|name| name == DROPPED),
"the shipped selection carries `{DROPPED}`, and these cases are about removing it: \
{line}"
);
let kept: Vec<String> = held.into_iter().filter(|name| name != DROPPED).collect();
let indent = &line[..line.len() - line.trim_start().len()];
let replaced = format!("{indent}bundles: [{}]", kept.join(", "));
std::fs::write(&path, text.replacen(&line, &replaced, 1))
.expect("the consumer declaration writes");
kept
}
}
const DROPPED: &str = "evidence-and-obligation";
const ADVICE: &str = "this bundle selection is incomplete";
impl Drop for Root {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.at);
}
}
#[derive(Debug)]
struct Ran {
code: Option<i32>,
out: String,
err: String,
}
fn copy(from: &Path, to: &Path) {
std::fs::create_dir_all(to).expect("the directory is there");
for entry in std::fs::read_dir(from).expect("the fixture directory reads") {
let entry = entry.expect("the entry reads");
let target = to.join(entry.file_name());
match entry.file_type().expect("the file type reads").is_dir() {
true => copy(&entry.path(), &target),
false => {
std::fs::copy(entry.path(), &target).expect("the fixture copies");
}
}
}
}
fn line_of(text: &str, prefix: &str) -> usize {
let found: Vec<usize> = text
.lines()
.enumerate()
.filter(|(_, line)| line.starts_with(prefix))
.map(|(index, _)| index)
.collect();
assert_eq!(found.len(), 1, "one line starts with `{prefix}`:\n{text}");
found[0]
}
#[test]
fn validate_states_that_no_operation_makes_what_it_addresses() {
let root = Root::new("no-founding");
let ran = root.run(&["taxonomy", "validate"]);
assert_eq!(
ran.code,
Some(0),
"the copy of this repository is valid: {ran:?}"
);
assert!(
ran.out
.contains("operations that make what they address: 0"),
"the block prints at zero: {ran:?}"
);
let sources = line_of(&ran.out, "sources, in application order");
let block = line_of(&ran.out, "operations that make what they address:");
let rules = line_of(&ran.out, "rules");
assert!(
sources < block && block < rules,
"the block sits between the sources and the rules: {ran:?}"
);
assert!(
!ran.out.contains("Nothing here refuses a founding"),
"the note prints only beside a founding: {ran:?}"
);
}
#[test]
fn validate_states_that_every_shelf_carries_a_display_name() {
let root = Root::new("no-bare-shelf");
let ran = root.run(&["taxonomy", "validate"]);
assert_eq!(
ran.code,
Some(0),
"the copy of this repository is valid: {ran:?}"
);
assert!(
ran.out
.contains("shelves that print their key for want of a display name: 0"),
"the block prints at zero: {ran:?}"
);
assert!(
!ran.out.contains("Nothing here refuses. `shelves."),
"the note prints only beside a name: {ran:?}"
);
}
#[test]
fn a_shelf_with_no_display_name_is_named_and_not_refused() {
let root = Root::new("one-bare-shelf");
root.drops_a_display_name();
let ran = root.run(&["taxonomy", "validate"]);
assert_eq!(
ran.code,
Some(0),
"a shelf with no display name refuses nothing: {ran:?}"
);
assert!(
ran.out
.contains("shelves that print their key for want of a display name: 1"),
"the count is on the heading: {ran:?}"
);
assert!(
ran.out.contains("shelves.tutorials"),
"the shelf is named: {ran:?}"
);
assert!(
ran.out.contains("falls through to the key"),
"and the reading is stated where the name is: {ran:?}"
);
}
#[test]
fn an_overlay_that_makes_what_it_addresses_is_named_and_not_refused() {
let root = Root::new("one-founding");
root.founds_a_kind();
let ran = root.run(&["taxonomy", "validate"]);
assert_eq!(ran.code, Some(0), "a founding refuses nothing: {ran:?}");
assert!(
ran.out
.contains("operations that make what they address: 1"),
"the count is on the heading: {ran:?}"
);
assert!(
ran.out.contains("add.kinds.playbook.is_a"),
"the operation is named as an overlay writes it: {ran:?}"
);
assert!(
ran.out.contains("makes `kinds.playbook`"),
"the shallowest key it makes is named: {ran:?}"
);
assert!(
ran.out.contains(".headwater/overlay.yml"),
"the source that carries it is named: {ran:?}"
);
assert!(
ran.out.contains("Nothing here refuses a founding"),
"the reason prints beside the founding: {ran:?}"
);
assert!(
!ran.out.contains("add.kinds.playbook.purpose"),
"only the operation that makes the key is reported: {ran:?}"
);
}
#[test]
fn resolve_reports_a_founding_on_standard_error_and_still_writes_the_lock() {
let root = Root::new("resolve-founding");
root.founds_a_kind();
let ran = root.run(&["taxonomy", "resolve"]);
assert_eq!(ran.code, Some(0), "the lock is written: {ran:?}");
assert!(
ran.err
.contains("operations that make what they address: 1"),
"the record is on standard error: {ran:?}"
);
assert!(
!ran.out.contains("operations that make what they address"),
"and not on standard output: {ran:?}"
);
assert!(
root.at.join(".headwater/taxonomy.lock").is_file(),
"the lock is on disk: {ran:?}"
);
let checked = root.run(&["taxonomy", "resolve", "--check"]);
assert_eq!(checked.code, Some(0), "the lock is current: {checked:?}");
assert!(
checked
.err
.contains("operations that make what they address: 1"),
"`--check` reports it too: {checked:?}"
);
assert_eq!(
checked.out.lines().count(),
1,
"the verdict is still one line: {checked:?}"
);
}
#[test]
fn resolve_names_the_bundle_an_incomplete_selection_left_out() {
let root = Root::new("resolve-incomplete");
let kept = root.drops_the_bundle_the_others_read();
let ran = root.run(&["taxonomy", "resolve"]);
assert_eq!(
ran.code,
Some(1),
"an incomplete selection is refused: {ran:?}"
);
assert!(
ran.err.contains("referential integrity"),
"the refusal that reports it is the one it always was: {ran:?}"
);
assert!(
ran.err.contains(ADVICE),
"the advice reaches a reader: {ran:?}"
);
assert!(
ran.err.contains(&format!("`{DROPPED}` declares")),
"and it names the bundle to add, with what that bundle supplies: {ran:?}"
);
assert!(
ran.err.contains(".headwater/taxonomy.yml"),
"and the file to edit: {ran:?}"
);
for name in shipped_bundles() {
if name == DROPPED || kept.contains(&name) {
continue;
}
assert!(
!ran.err.contains(&format!("`{name}`")),
"the message names no bundle that does not help (`{name}`): {ran:?}"
);
}
assert!(
!ran.out.contains(ADVICE),
"the advice is on standard error and never on standard output: {ran:?}"
);
assert!(
!root.at.join(".headwater/taxonomy.lock").is_file(),
"a refused taxonomy writes no lock: {ran:?}"
);
}
#[test]
fn validate_names_the_bundle_an_incomplete_selection_left_out() {
let root = Root::new("validate-incomplete");
root.drops_the_bundle_the_others_read();
let ran = root.run(&["taxonomy", "validate"]);
assert_eq!(ran.code, Some(1), "the taxonomy is not valid: {ran:?}");
assert!(
ran.out.contains("headwater/standard is not valid"),
"the verdict is still on standard output: {ran:?}"
);
assert!(
ran.err.contains("referential integrity"),
"under the refusal that reports it: {ran:?}"
);
assert!(
ran.err.contains(ADVICE) && ran.err.contains(&format!("`{DROPPED}` declares")),
"the advice names the bundle to add: {ran:?}"
);
assert!(
!ran.out.contains(ADVICE),
"on standard error and never on standard output: {ran:?}"
);
}
#[test]
fn a_selection_whose_closure_is_complete_is_told_nothing_about_bundles() {
let root = Root::new("complete-selection");
let ran = root.run(&["taxonomy", "validate"]);
assert_eq!(ran.code, Some(0), "the shipped selection is valid: {ran:?}");
assert!(
!ran.err.contains(ADVICE) && !ran.out.contains(ADVICE),
"nothing is advised about a selection that resolves: {ran:?}"
);
let resolved = root.run(&["taxonomy", "resolve"]);
assert_eq!(resolved.code, Some(0), "and it resolves: {resolved:?}");
assert!(
!resolved.err.contains(ADVICE) && !resolved.out.contains(ADVICE),
"at the other verb as well: {resolved:?}"
);
}
fn shipped_bundles() -> Vec<String> {
let at = repository().join("packages/headwater-standard/bundles");
let mut names: Vec<String> = std::fs::read_dir(&at)
.expect("the bundle root reads")
.filter_map(|entry| entry.ok())
.filter(|entry| at.join(entry.file_name()).join("bundle.yml").is_file())
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.collect();
names.sort();
assert!(names.len() > 1, "the package ships bundles: {names:?}");
names
}