use std::collections::BTreeSet;
use std::fs;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Declared {
name: String,
version: String,
}
impl Declared {
fn line(&self) -> String {
self.version
.split('.')
.take(2)
.collect::<Vec<_>>()
.join(".")
}
}
const FAMILIES: &[(&str, &str, &[&str])] = &[
(
"the chia 0.36 line (chia-sdk-* + the chia/clvm primitives)",
"0.36",
&[
"chia-bls",
"chia-protocol",
"chia-puzzle-types",
"chia-sdk-driver",
"chia-sdk-test",
"chia-sdk-types",
"chia-traits",
"clvm-traits",
"clvm-utils",
],
),
("the chia-puzzles line", "0.20", &["chia-puzzles"]),
("the clvmr line", "0.16", &["clvmr"]),
];
fn declared_chia_deps() -> Vec<Declared> {
let manifest = fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/Cargo.toml"))
.expect("the crate's own Cargo.toml is readable");
let mut section = String::new();
let mut found = Vec::new();
for raw in manifest.lines() {
let line = raw.trim();
if line.starts_with('#') || line.is_empty() {
continue;
}
if line.starts_with('[') {
section = line.to_string();
continue;
}
if section != "[dependencies]" && section != "[dev-dependencies]" {
continue;
}
let Some((name, rest)) = line.split_once('=') else {
continue;
};
let name = name.trim();
if !(name.starts_with("chia") || name.starts_with("clvm")) {
continue;
}
let version = extract_version(rest.trim()).unwrap_or_else(|| {
panic!("dependency `{name}` declares no literal version this guard can read: {line}")
});
found.push(Declared {
name: name.to_string(),
version,
});
}
found
}
fn extract_version(rest: &str) -> Option<String> {
let quoted = if rest.starts_with('{') {
let at = rest.find("version")?;
&rest[at..]
} else {
rest
};
let mut parts = quoted.split('"');
parts.next()?;
parts.next().map(str::to_string)
}
#[test]
fn every_chia_family_declares_a_single_minor_line() {
let declared = declared_chia_deps();
for (family, expected_line, members) in FAMILIES {
for member in *members {
let Some(dep) = declared.iter().find(|d| d.name == *member) else {
continue; };
assert_eq!(
dep.line(),
*expected_line,
"#5 REGRESSION: `{}` is declared at {} but {family} is pinned to {expected_line}.x. \
Two minor lines of one chia family in one manifest is the internal split that \
shipped as 0.3.1. Move the whole family together, or re-state the family's line here.",
dep.name,
dep.version,
);
}
}
}
#[test]
fn every_declared_chia_dep_belongs_to_a_classified_family() {
let classified: BTreeSet<&str> = FAMILIES
.iter()
.flat_map(|(_, _, members)| members.iter().copied())
.collect();
for dep in declared_chia_deps() {
assert!(
classified.contains(dep.name.as_str()),
"`{}` is a chia/clvm dependency this coherence guard does not classify. A new chia \
dependency can silently arrive on a foreign version line, which is exactly how the \
0.3.1 split went unnoticed. Add it to the family it belongs to in FAMILIES.",
dep.name,
);
}
}
#[test]
fn the_declared_chia_dependency_set_is_exactly_the_expected_one() {
let expected: BTreeSet<&str> = BTreeSet::from([
"chia-bls",
"chia-protocol",
"chia-puzzle-types",
"chia-puzzles",
"chia-sdk-driver",
"chia-sdk-test",
"chia-sdk-types",
"chia-traits",
"clvm-traits",
"clvm-utils",
"clvmr",
]);
let actual: BTreeSet<String> = declared_chia_deps().into_iter().map(|d| d.name).collect();
let actual: BTreeSet<&str> = actual.iter().map(String::as_str).collect();
assert_eq!(
actual, expected,
"the crate's chia/clvm dependency set changed. Confirm the new set is on ONE line per \
family, then update this expectation in the same commit.",
);
}