use std::fs;
use std::path::{Path, PathBuf};
use schematron::Schema;
fn markdown_files() -> Vec<PathBuf> {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let mut files = vec![root.join("README.md")];
let mut specs: Vec<PathBuf> = fs::read_dir(root.join("spec"))
.expect("spec/ should exist")
.filter_map(std::result::Result::ok)
.map(|entry| entry.path())
.filter(|path| path.extension().is_some_and(|e| e == "md"))
.collect();
specs.sort();
files.extend(specs);
files
}
fn xml_blocks(source: &str) -> Vec<(usize, String)> {
let mut blocks = Vec::new();
let mut current: Option<(usize, Vec<&str>)> = None;
for (index, line) in source.lines().enumerate() {
let trimmed = line.trim();
match &mut current {
None => {
if trimmed == "```xml" {
current = Some((index + 1, Vec::new()));
}
}
Some((start, lines)) => {
if trimmed == "```" {
blocks.push((*start, lines.join("\n")));
current = None;
} else {
lines.push(line);
}
}
}
}
blocks
}
fn is_whole_schema(block: &str) -> bool {
let mut body = block.trim_start();
if let Some(rest) = body.strip_prefix("<?xml") {
body = rest.split_once("?>").map_or(rest, |(_, tail)| tail).trim_start();
}
while let Some(rest) = body.strip_prefix("<!--") {
let Some((_, tail)) = rest.split_once("-->") else {
return false;
};
body = tail.trim_start();
}
body.starts_with("<schema")
}
#[test]
fn every_documented_schema_compiles() {
let mut checked = 0;
let mut failures = Vec::new();
for file in markdown_files() {
let source = fs::read_to_string(&file).expect("markdown file should be readable");
let name = file
.strip_prefix(env!("CARGO_MANIFEST_DIR"))
.unwrap_or(&file)
.display()
.to_string();
for (line, block) in xml_blocks(&source) {
if !is_whole_schema(&block) {
continue;
}
checked += 1;
if let Err(error) = Schema::from_str(&block) {
failures.push(format!("{name}:{line}\n {error}\n"));
}
}
}
assert!(checked > 0, "no documented schemas were found to check");
assert!(
failures.is_empty(),
"{} of {checked} documented schemas do not compile:\n\n{}",
failures.len(),
failures.join("\n")
);
}
#[test]
fn the_example_schema_files_compile() {
let examples = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples");
let mut checked = 0;
for entry in fs::read_dir(&examples).expect("examples/ should exist") {
let path = entry.expect("directory entry").path();
if path.extension().is_some_and(|e| e == "sch") {
Schema::from_path(&path)
.unwrap_or_else(|e| panic!("{} does not compile: {e}", path.display()));
checked += 1;
}
}
assert!(checked > 0, "no example schemas were found");
}
#[test]
fn every_spec_document_is_linked_from_the_index() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let index = fs::read_to_string(root.join("spec/index.md")).expect("spec/index.md");
for entry in fs::read_dir(root.join("spec")).expect("spec/ should exist") {
let path = entry.expect("directory entry").path();
if !path.is_dir() {
continue;
}
let name = path.file_name().unwrap().to_string_lossy().to_string();
assert!(
index.contains(&format!("({name}/index.md)")),
"spec/{name}/index.md is not linked from spec/index.md"
);
}
}
fn declared_msrv() -> String {
let manifest = fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("Cargo.toml"))
.expect("Cargo.toml should be readable");
manifest
.lines()
.find_map(|line| {
let rest = line.trim().strip_prefix("rust-version")?;
let value = rest.split_once('=')?.1;
let value = value.split('"').nth(1)?;
Some(value.to_string())
})
.expect("Cargo.toml should declare rust-version")
}
#[test]
fn the_msrv_spec_agrees_with_cargo_toml() {
let msrv = declared_msrv();
let spec = fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR")).join("spec/rust-msrv-n-minus-2/index.md"),
)
.expect("spec/rust-msrv-n-minus-2/index.md should exist");
assert!(
spec.contains(&format!("rust-version = \"{msrv}\"")),
"spec/rust-msrv-n-minus-2/index.md does not show rust-version = {msrv:?}, \
which is what Cargo.toml declares"
);
assert!(
spec.contains(&format!("**{msrv}**")),
"spec/rust-msrv-n-minus-2/index.md does not name {msrv:?} as the current MSRV \
in its value table"
);
assert!(
spec.contains(&format!("cargo +{msrv} test")),
"spec/rust-msrv-n-minus-2/index.md does not show how to verify {msrv:?}"
);
let testing = fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("spec/testing/index.md"))
.expect("spec/testing/index.md should exist");
assert!(
testing.contains(&format!("cargo +{msrv} test")),
"spec/testing/index.md does not list the MSRV check for {msrv:?}"
);
}
#[test]
fn every_documented_toolchain_pin_is_the_declared_msrv() {
let msrv = declared_msrv();
let mut stale = Vec::new();
for file in documentation_files() {
let source = fs::read_to_string(&file).expect("documentation should be readable");
for (index, line) in source.lines().enumerate() {
let mut rest = line;
while let Some(at) = rest.find("cargo +") {
let after = &rest[at + "cargo +".len()..];
let end = after
.find(|c: char| !c.is_ascii_digit() && c != '.')
.unwrap_or(after.len());
let version = &after[..end];
if !version.is_empty() && version != msrv {
stale.push(format!(
"{}:{}: pins {version}, but Cargo.toml declares {msrv}",
file.display(),
index + 1
));
}
rest = &after[end..];
}
}
}
assert!(
stale.is_empty(),
"stale toolchain pin(s) in documentation:\n {}",
stale.join("\n ")
);
}
const MAX_DOC_BYTES: u64 = 40_000;
fn documentation_files() -> Vec<PathBuf> {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let mut files = Vec::new();
for name in ["README.md", "index.md", "AGENTS.md", "CLAUDE.md"] {
let path = root.join(name);
if path.exists() {
files.push(path);
}
}
for directory in ["spec", "agents"] {
let directory = root.join(directory);
if !directory.is_dir() {
continue;
}
let mut found = Vec::new();
collect_markdown(&directory, &mut found);
found.sort();
files.extend(found);
}
files
}
fn collect_markdown(directory: &Path, found: &mut Vec<PathBuf>) {
let entries = fs::read_dir(directory).expect("documentation directory should be readable");
for entry in entries.filter_map(std::result::Result::ok) {
let path = entry.path();
if path.is_dir() {
collect_markdown(&path, found);
} else if path.extension().is_some_and(|e| e == "md") {
found.push(path);
}
}
}
fn relative_links(source: &str) -> Vec<String> {
let mut links = Vec::new();
let mut rest = source;
while let Some(start) = rest.find("](") {
let after = &rest[start + 2..];
let Some(end) = after.find(')') else { break };
let target = &after[..end];
if !target.starts_with("http") && !target.starts_with('#') && !target.contains(' ') {
links.push(target.to_string());
}
rest = &after[end..];
}
links
}
#[test]
fn every_relative_link_resolves() {
let mut broken = Vec::new();
for file in documentation_files() {
let source = fs::read_to_string(&file).expect("documentation should be readable");
let directory = file.parent().expect("a file has a parent directory");
for link in relative_links(&source) {
let path = link.split('#').next().unwrap_or(&link);
if path.is_empty() {
continue;
}
if !directory.join(path).exists() {
broken.push(format!("{}: {link}", file.display()));
}
}
}
assert!(
broken.is_empty(),
"{} broken link(s):\n {}",
broken.len(),
broken.join("\n ")
);
}
#[test]
fn documentation_files_are_within_the_size_budget() {
let mut oversized = Vec::new();
for file in documentation_files() {
let bytes = fs::metadata(&file)
.expect("documentation should be readable")
.len();
if bytes > MAX_DOC_BYTES {
oversized.push(format!(
"{}: {bytes} bytes, over the {MAX_DOC_BYTES} budget",
file.display()
));
}
}
assert!(
oversized.is_empty(),
"{} file(s) over budget — split them:\n {}",
oversized.len(),
oversized.join("\n ")
);
}
#[test]
fn the_xpath_function_list_in_the_spec_matches_the_engine() {
let spec = fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("spec/xpath/index.md"))
.expect("spec/xpath/index.md should exist");
for function in schematron::xpath::function_names() {
let bare = format!("`{function}`");
let called = format!("`{function}(");
assert!(
spec.contains(&bare) || spec.contains(&called),
"spec/xpath/index.md does not mention the `{function}` function"
);
}
}
#[test]
fn the_xpath_two_function_list_in_the_spec_matches_the_engine() {
let spec = fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("spec/xpath2/index.md"))
.expect("spec/xpath2/index.md should exist");
for function in schematron::xpath::function_names_v2() {
let bare = format!("`{function}`");
let called = format!("`{function}(");
assert!(
spec.contains(&bare) || spec.contains(&called),
"spec/xpath2/index.md does not document the `{function}` function"
);
}
}
#[test]
fn the_xpath_three_function_list_in_the_spec_matches_the_engine() {
let spec = fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("spec/xpath3/index.md"))
.expect("spec/xpath3/index.md should exist");
for function in schematron::xpath::function_names_v3() {
let bare = format!("`{function}`");
let called = format!("`{function}(");
assert!(
spec.contains(&bare) || spec.contains(&called),
"spec/xpath3/index.md does not document the `{function}` function"
);
}
}
#[test]
fn the_lint_table_in_the_spec_matches_the_linter() {
let spec = fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("spec/linting/index.md"))
.expect("spec/linting/index.md should exist");
let source = fs::read_to_string(Path::new(env!("CARGO_MANIFEST_DIR")).join("src/lint.rs"))
.expect("src/lint.rs should exist");
let mut kinds = Vec::new();
for line in source.lines() {
let line = line.trim();
let Some(rest) = line.strip_prefix("LintKind::") else {
continue;
};
if !line.contains("=>") {
continue;
}
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if !name.is_empty() {
kinds.push(name);
}
}
assert!(!kinds.is_empty(), "no LintKind variants were found");
let documented: Vec<String> = spec
.lines()
.filter_map(|line| line.strip_prefix("| `"))
.filter_map(|rest| rest.split('`').next())
.map(ToString::to_string)
.collect();
for kind in &kinds {
assert!(
documented.contains(kind),
"spec/linting/index.md does not document the {kind} lint"
);
}
for name in &documented {
if !name.chars().next().is_some_and(char::is_uppercase) {
continue;
}
assert!(
kinds.contains(name),
"spec/linting/index.md documents a {name} lint, which does not exist"
);
}
}
#[test]
fn every_runnable_example_is_referenced_from_the_documentation() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let readme = fs::read_to_string(root.join("README.md")).expect("README.md");
let tutorial =
fs::read_to_string(root.join("spec/tutorial/index.md")).expect("spec/tutorial/index.md");
let both = format!("{readme}{tutorial}");
let mut unreferenced = Vec::new();
for entry in fs::read_dir(root.join("examples")).expect("examples/ should exist") {
let path = entry.expect("directory entry").path();
if path.extension().is_none_or(|e| e != "rs") {
continue;
}
let stem = path.file_stem().unwrap().to_string_lossy().to_string();
if !both.contains(&stem) {
unreferenced.push(stem);
}
}
assert!(
unreferenced.is_empty(),
"example(s) not referenced from README.md or spec/tutorial/index.md: {}",
unreferenced.join(", ")
);
}
#[test]
fn directories_are_lowercase() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let mut offenders = Vec::new();
for entry in fs::read_dir(root).expect("the repository root should be readable") {
let path = entry.expect("directory entry").path();
if !path.is_dir() {
continue;
}
let name = path.file_name().unwrap().to_string_lossy().to_string();
if name.starts_with('.') || name == "target" {
continue;
}
if name != name.to_lowercase() {
offenders.push(name);
}
}
assert!(
offenders.is_empty(),
"directory names must be lowercase; see spec/agents-directory-name-is-lowercase/index.md: {}",
offenders.join(", ")
);
}
#[test]
fn the_agents_directory_is_lowercase() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
assert!(
root.join("agents").is_dir(),
"the agent documentation directory must be `agents/`, lowercase"
);
let recorded = fs::read_dir(root)
.expect("the repository root should be readable")
.filter_map(std::result::Result::ok)
.map(|entry| entry.file_name().to_string_lossy().to_string())
.find(|name| name.eq_ignore_ascii_case("agents"));
assert_eq!(
recorded.as_deref(),
Some("agents"),
"the agent documentation directory is recorded with the wrong case"
);
}
#[test]
fn every_agent_document_is_reachable() {
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
let directory = root.join("agents");
if !directory.is_dir() {
return;
}
let entry_points = format!(
"{}{}",
fs::read_to_string(root.join("AGENTS.md")).unwrap_or_default(),
fs::read_to_string(root.join("index.md")).unwrap_or_default()
);
let mut orphans = Vec::new();
for entry in fs::read_dir(&directory).expect("agents/ should be readable") {
let path = entry.expect("directory entry").path();
if path.extension().is_none_or(|e| e != "md") {
continue;
}
let name = path.file_name().unwrap().to_string_lossy().to_string();
if !entry_points.contains(&format!("agents/{name}")) {
orphans.push(name);
}
}
assert!(
orphans.is_empty(),
"agents/ document(s) not linked from AGENTS.md or index.md: {}",
orphans.join(", ")
);
}
#[test]
fn documentation_does_not_hard_code_volatile_counts() {
let pattern = regex_lite_tests_count();
let mut offenders = Vec::new();
for file in documentation_files() {
let source = fs::read_to_string(&file).expect("documentation should be readable");
for (index, line) in source.lines().enumerate() {
if pattern(line) {
offenders.push(format!("{}:{}: {}", file.display(), index + 1, line.trim()));
}
}
}
assert!(
offenders.is_empty(),
"documentation hard-codes a test count; reference the command instead:\n {}",
offenders.join("\n ")
);
}
fn regex_lite_tests_count() -> impl Fn(&str) -> bool {
|line: &str| {
let lower = line.to_lowercase();
for marker in [" tests", " tests.", " tests,", " tests:", " tests passing"] {
let Some(position) = lower.find(marker) else {
continue;
};
let before = lower[..position].trim_end();
let token = before.rsplit(|c: char| c.is_whitespace()).next().unwrap_or("");
let digits = token.trim_start_matches(|c: char| !c.is_ascii_digit());
if digits.len() >= 2 && digits.chars().all(|c| c.is_ascii_digit()) {
return true;
}
}
false
}
}