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")
}
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");
}
}
}
}
struct Root {
at: PathBuf,
}
impl Root {
fn new(label: &str) -> Root {
let at = std::env::temp_dir().join(format!(
"headwater-cli-classify-{}-{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("taxonomy-source/headwater-standard"),
&at.join(".headwater/packages/headwater-standard"),
);
repoint_bundles(&at.join(".headwater/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");
}
let declaration = at.join(".headwater/taxonomy.yml");
let text = std::fs::read_to_string(&declaration).expect("the declaration reads");
let from = " exclude:\n";
assert!(
text.contains(from),
"the copied declaration still declares `exclude:` at two spaces"
);
let to = " exclude:\n - path: docs/excluded/*.md\n reason: >-\n a fixture for #319: `*` inside one segment for `Pattern`, any run of\n characters for `fnmatch`, so the two disagree about a path four\n segments deep against this three-segment pattern.\n";
let patched = text.replacen(from, to, 1);
std::fs::write(&declaration, patched).expect("the declaration writes");
let root = Root { at };
let resolved = root.run(&["taxonomy", "resolve"]);
assert_eq!(
resolved.code,
Some(0),
"the fixture resolves\n{}{}",
resolved.out,
resolved.err
);
root
}
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(),
}
}
}
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,
}
#[test]
fn a_path_four_segments_deep_is_corpus_content_under_the_segment_aware_matcher() {
let root = Root::new("four-segments");
let explained = root.run(&["explain", "docs/excluded/sub/dir.md"]);
assert_eq!(
explained.code,
Some(1),
"a target with no document: {explained:?}"
);
assert!(
explained.out.is_empty(),
"no document to print: {explained:?}"
);
assert!(
explained
.err
.contains("is a path of this corpus, with no document written there yet"),
"`fnmatch` would have called this excluded; `Pattern` does not, \
because `*` does not cross the `/` before `sub`: {explained:?}"
);
}
#[test]
fn a_path_one_segment_deep_is_excluded_under_either_matcher() {
let root = Root::new("one-segment");
let explained = root.run(&["explain", "docs/excluded/dir.md"]);
assert_eq!(
explained.code,
Some(1),
"a target with no document: {explained:?}"
);
assert!(
explained
.err
.contains("is excluded by `docs/excluded/*.md`"),
"both matchers exclude a path this shallow: {explained:?}"
);
}
#[test]
fn an_identifier_shaped_target_refuses_in_the_words_of_an_identifier_and_not_a_path() {
let root = Root::new("identifier-shaped");
for target in ["HW-DR-004", "HW-DR-9999"] {
let explained = root.run(&["explain", target]);
assert_eq!(
explained.code,
Some(1),
"a target no document carries: {explained:?}"
);
assert!(
explained
.err
.contains("is shaped like an identifier of this corpus, and no document declares it"),
"{target} opens on `HW-DR-`, the fixed prefix `{{namespace}}-DR-{{seq:04d}}` declares: {explained:?}"
);
assert!(
!explained.err.contains("is outside every corpus root"),
"the path sentence must not print for an identifier-shaped target: {explained:?}"
);
}
}
#[test]
fn a_path_shaped_target_with_no_document_still_reads_the_path_states() {
let root = Root::new("path-shaped-control");
let explained = root.run(&["explain", "engine/nowhere/at-all.rs"]);
assert_eq!(
explained.code,
Some(1),
"a target no document carries: {explained:?}"
);
assert!(
explained
.err
.contains("is outside every corpus root this repository declares"),
"a path outside the corpus root still reads the path sentence: {explained:?}"
);
}
fn repoint_bundles(package: &std::path::Path) {
let up = "../".repeat(headwater_resolve::package::PACKAGES.split('/').count() + 1);
let manifest = package.join(headwater_resolve::package::MANIFEST);
let text = std::fs::read_to_string(&manifest).expect("the scratch manifest reads");
let from = " bundles: ../../docs/taxonomies";
assert!(text.contains(from), "the authored manifest states `{from}`");
let to = format!(" bundles: {up}docs/taxonomies");
std::fs::write(&manifest, text.replace(from, &to)).expect("the scratch manifest writes");
}
const PLANTED: &str = "\nWe will colour this entry in a later release, and it doesn't matter\nhow the next line starts, because this block is wrapped by hand.\n\nThis sentence runs on with many more words than the house profile admits, so that the count of its words goes well past the limit of twenty five words that the regime states. It is a load-bearing claim.\n";
fn findings(out: &str) -> Vec<(String, String)> {
let lines: Vec<&str> = out.lines().collect();
let mut pairs = Vec::new();
for (at, line) in lines.iter().enumerate() {
let Some(rest) = line.strip_prefix(" ") else {
continue;
};
if rest.starts_with(' ') || !rest.starts_with("docs/") {
continue;
}
let Some((path, _)) = rest.split_once(':') else {
continue;
};
let Some(next) = lines.get(at + 1) else {
continue;
};
let rule = next.trim_start().split([' ', ':']).next().unwrap_or("");
pairs.push((path.to_owned(), rule.to_owned()));
}
pairs
}
#[test]
fn the_library_doctrine_is_checked_and_a_fixture_corpus_under_it_is_not() {
let root = Root::new("doctrine-governed");
let doctrine = "docs/taxonomies/design-spec/doctrine.md";
let fixture = "docs/taxonomies/design-spec/fixtures/corpus/docs/planted-350.md";
let path = root.at.join(doctrine);
let mut text = std::fs::read_to_string(&path).expect("the doctrine reads");
text.push_str(PLANTED);
std::fs::write(&path, text).expect("the doctrine writes");
let planted = root.at.join(fixture);
std::fs::create_dir_all(planted.parent().expect("it has a parent"))
.expect("the fixture directory is there");
std::fs::write(&planted, format!("# A planted fixture page\n{PLANTED}"))
.expect("the fixture page writes");
let explained = root.run(&["explain", doctrine]);
assert_eq!(
explained.code,
Some(0),
"the doctrine is a document: {explained:?}"
);
assert!(
explained.out.contains("library_doctrine"),
"the doctrine page is typed `library_doctrine`: {explained:?}"
);
let excluded = root.run(&["explain", fixture]);
assert!(
excluded
.out
.contains("no kind, so nothing is required of it")
&& excluded.out.contains(" docs/taxonomies/*/fixtures/**\n"),
"a fixture corpus page is excluded by the fixture rule and no other: {excluded:?}"
);
let checked = root.run(&["check", "--strict"]);
assert_ne!(
checked.code,
Some(0),
"the planted errors fail a strict run: {checked:?}"
);
let pairs = findings(&checked.out);
for rule in [
"language.source_form.not_met",
"language.controlled.not_met",
"language.retired_term.used",
"voice.forbidden_construction",
] {
assert!(
pairs.iter().any(|(p, r)| p == doctrine && r == rule),
"{rule} names {doctrine}: {pairs:?}\n{}",
checked.out
);
}
assert!(
!pairs
.iter()
.any(|(p, _)| p.starts_with("docs/taxonomies/design-spec/fixtures/")),
"no finding names a fixture corpus page: {pairs:?}"
);
assert!(
checked.out.contains(&format!(
" {fixture}\n excluded: docs/taxonomies/*/fixtures/**\n"
)),
"the census row says the fixture rule excludes the page: {}",
checked.out
);
assert_eq!(
checked.out.matches("planted-350.md").count(),
1,
"the planted fixture page appears in its census row and nowhere else: {}",
checked.out
);
}