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("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:?}"
);
}