mod common;
use common::{Root, DECLARES, FOUNDS};
struct Pair {
resolves: bool,
current: String,
report: String,
refusal: String,
}
impl Pair {
fn over(root: &Root) -> Pair {
let checked = root.run(&["taxonomy", "resolve", "--check"]);
assert!(
matches!(checked.code, Some(0 | 1)),
"`taxonomy resolve --check` reaches a verdict: {checked:?}"
);
let ran = root.run(&["conformance"]);
let current = ran
.out
.lines()
.find_map(|line| line.strip_prefix(" lock.current "))
.unwrap_or_else(|| panic!("the report names `lock.current`: {ran:?}"))
.trim()
.to_string();
Pair {
resolves: checked.code == Some(0),
current,
report: ran.out,
refusal: checked.err,
}
}
fn corresponds(&self, label: &str) -> &Pair {
match self.resolves {
true => assert_eq!(
self.current, "met",
"over `{label}`, `headwater taxonomy resolve --check` accepts the committed lock \
and `lock.current` does not report it met. An adopter reading this report is \
told about a lock this same engine holds a different opinion of one command \
later:\n{}",
self.report
),
false => assert_eq!(
self.current, "gap",
"over `{label}`, `headwater taxonomy resolve --check` refuses the committed lock \
and `lock.current` does not report a gap. That is issue #648: the rule reports \
met over a lock the resolver refuses, so the adopter meets the refusal in their \
commit gate instead:\n{}",
self.report
),
}
self
}
}
#[test]
fn a_lock_the_resolver_just_wrote_is_current_to_both_verbs() {
let root = Root::founding("conformance-lock-clean");
Pair::over(&root).corresponds("the lock as `taxonomy resolve` wrote it");
}
#[test]
fn a_lock_whose_founding_record_was_deleted_by_hand_is_a_gap_to_both_verbs() {
let root = Root::founding("conformance-lock-stripped");
let path = root.at.join(".headwater/taxonomy.lock");
let text = std::fs::read_to_string(&path).expect("the lock reads");
assert!(
text.contains("\nfounded:\n"),
"this root founds a kind, so its lock records one: {text}"
);
let (before, rest) = text.split_once("\nfounded:\n").expect("the block is there");
let (_, after) = rest
.split_once("\n\n")
.expect("a blank line ends the block");
std::fs::write(&path, format!("{before}\n{after}")).expect("the lock writes");
Pair::over(&root).corresponds("the `founded:` block deleted, nothing re-resolved");
}
#[test]
fn a_bundle_edited_under_the_lock_is_a_gap_that_names_the_bundle() {
let root = Root::founding("conformance-lock-edited");
let bundle = root.at.join("docs/taxonomies/zz-a/bundle.yml");
let text = std::fs::read_to_string(&bundle).expect("the bundle reads");
std::fs::write(&bundle, format!("{text}# edited under the lock\n")).expect("the bundle writes");
let pair = Pair::over(&root);
pair.corresponds("a bundle edited, nothing re-resolved");
assert!(
pair.report.contains("docs/taxonomies/zz-a/bundle.yml"),
"the gap names the source that moved, because that is the file an author edits back \
or resolves over:\n{}",
pair.report
);
}
#[test]
fn a_re_resolved_lock_with_no_founding_left_is_current_to_both_verbs() {
let root = Root::founding("conformance-lock-swapped");
common::write_bundle(&root.at, "zz-a", DECLARES);
common::write_bundle(&root.at, "zz-b", FOUNDS);
let resolved = root.run(&["taxonomy", "resolve"]);
assert_eq!(
resolved.code,
Some(0),
"the swapped root resolves: {resolved:?}"
);
let text =
std::fs::read_to_string(root.at.join(".headwater/taxonomy.lock")).expect("the lock reads");
assert!(
!text.contains("\nfounded:\n"),
"the swap leaves nothing founded, which is what makes this case worth running: {text}"
);
Pair::over(&root).corresponds("the two `add:` blocks swapped, and re-resolved");
}
#[test]
fn an_adoption_block_hand_edited_into_another_form_is_a_gap_to_both_verbs() {
let root = Root::founding("conformance-lock-adoption");
let path = root.at.join(".headwater/taxonomy.lock");
const TRAILER: &str =
"\n# The resolved taxonomy. The digest above is over this text with the two\n";
const BLOCK: &str = "\nadoption:\n tasks:\n - id: AD-1\n statement: \"a scratch \
task, so this lock has an authored block to carry\"\n owner: \"the \
fixture\"\n until: 2027-06-30\n pairs:\n - path: \
docs/spec/01-overview.md\n rule: language.controlled.not_met\n";
let text = std::fs::read_to_string(&path).expect("the lock reads");
let (before, after) = text
.split_once(TRAILER)
.expect("the lock carries its trailer");
std::fs::write(&path, format!("{before}{BLOCK}{TRAILER}{after}")).expect("the lock writes");
let resolved = root.run(&["taxonomy", "resolve"]);
assert_eq!(
resolved.code,
Some(0),
"a resolve carries an authored block through: {resolved:?}"
);
let text = std::fs::read_to_string(&path).expect("the lock reads");
const CANONICAL: &str = " until: 2027-06-30\n";
assert!(
text.contains("\nadoption:\n") && text.contains(CANONICAL),
"the resolver kept the block and wrote this scalar unquoted, which is the form the \
perturbation below departs from: {text}"
);
std::fs::write(
&path,
text.replacen(CANONICAL, " until: \"2027-06-30\"\n", 1),
)
.expect("the lock writes");
let pair = Pair::over(&root);
assert!(
pair.refusal.contains("is not written in the form"),
"this perturbation has to reach the resolver as a difference of form and not of \
content, or the row holds nothing about the arm it exists for:\n{}",
pair.refusal
);
pair.corresponds("the `adoption` block requoted, nothing re-resolved");
}
#[test]
fn the_table_discriminates() {
let clean = Root::founding("conformance-lock-discriminates-clean");
let stale = Root::founding("conformance-lock-discriminates-stale");
let bundle = stale.at.join("docs/taxonomies/zz-a/bundle.yml");
let text = std::fs::read_to_string(&bundle).expect("the bundle reads");
std::fs::write(&bundle, format!("{text}# edited under the lock\n")).expect("the bundle writes");
let accepted = clean.run(&["taxonomy", "resolve", "--check"]);
let refused = stale.run(&["taxonomy", "resolve", "--check"]);
assert_eq!(
accepted.code,
Some(0),
"a root nothing perturbed has a current lock: {accepted:?}"
);
assert_eq!(
refused.code,
Some(1),
"a root whose source moved under its lock does not: {refused:?}"
);
}