use std::path::{Path, PathBuf};
pub(crate) fn rs_files(dir: &Path) -> Vec<PathBuf> {
let mut out = Vec::new();
walk(dir, &mut out);
out
}
pub(crate) fn rs_files_in(rel: &str) -> Vec<PathBuf> {
rs_files(&Path::new(env!("CARGO_MANIFEST_DIR")).join(rel))
}
pub(crate) fn production_code(source: &str) -> String {
source
.lines()
.filter(|line| !line.trim_start().starts_with("//"))
.take_while(|line| !line.trim_start().starts_with("#[cfg(test)]"))
.collect::<Vec<_>>()
.join("\n")
}
fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
for entry in std::fs::read_dir(dir).expect("a readable source directory") {
let path = entry.expect("a readable directory entry").path();
if path.is_dir() {
walk(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
out.push(path);
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
#[test]
fn no_changelog_entry_appears_under_two_versions() {
let Some(changelog) = changelog() else { return };
let mut seen: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
let mut version = "<before any version heading>";
for line in changelog.lines() {
if let Some(rest) = line.strip_prefix("## ") {
version = rest.trim();
} else if let Some(entry) = line.strip_prefix("- ") {
let entry = entry.trim();
if entry.len() > 24 {
seen.entry(entry).or_default().push(version);
}
}
}
let duplicated: Vec<_> = seen
.iter()
.filter(|(_, versions)| {
let mut distinct = versions.to_vec();
distinct.sort_unstable();
distinct.dedup();
distinct.len() > 1
})
.map(|(entry, versions)| format!("{versions:?}: {}…", &entry[..40.min(entry.len())]))
.collect();
assert!(
duplicated.is_empty(),
"these entries appear under more than one version — an [Unreleased] \\
note has merged into a released section:\n{}",
duplicated.join("\n")
);
}
#[test]
fn no_changelog_section_repeats_a_category() {
let Some(changelog) = changelog() else { return };
let mut offenders = Vec::new();
let mut version = "<before any version heading>";
let mut categories: Vec<&str> = Vec::new();
let mut flush = |version: &str, categories: &mut Vec<&str>| {
let mut sorted = categories.clone();
sorted.sort_unstable();
let before = sorted.len();
sorted.dedup();
if sorted.len() != before {
offenders.push(version.to_string());
}
categories.clear();
};
for line in changelog.lines() {
if let Some(rest) = line.strip_prefix("## ") {
flush(version, &mut categories);
version = rest.trim();
} else if let Some(rest) = line.strip_prefix("### ") {
categories.push(rest.trim());
}
}
flush(version, &mut categories);
const SHIPPED_THIS_WAY: [&str; 2] = ["[1.8.0] — 2026-08-28", "[0.14.0] — 2026-07-20"];
offenders.retain(|version| !SHIPPED_THIS_WAY.contains(&version.as_str()));
assert!(
offenders.is_empty(),
"these versions carry the same category heading twice, which hides \
whatever is under the second one from the release's version \
decision: {offenders:?}"
);
}
fn changelog() -> Option<String> {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("CHANGELOG.md");
std::fs::read_to_string(&path).ok()
}
}