use std::process::Command;
const README: &str = include_str!("../README.md");
const FORMATS_RS: &str = include_str!("../src/formats.rs");
const RELEASE_YML: &str = include_str!("../.github/workflows/release.yml");
const TABLE_SECTION: &str = "## The numbers";
fn stated(label: &str) -> usize {
let section = README
.split_once(TABLE_SECTION)
.unwrap_or_else(|| panic!("the README has no {TABLE_SECTION:?} heading"))
.1;
let section = section
.split_once("\n## ")
.map_or(section, |(head, _)| head);
let row = section
.lines()
.find(|line| line.starts_with('|') && line.contains(label))
.unwrap_or_else(|| {
panic!("no row of the README's counts table is labelled {label:?}; if the row was renamed, rename it here too, and if it was deleted, this is the count going unchecked")
});
let cell = row
.split('|')
.find_map(|cell| cell.trim().strip_prefix("**")?.strip_suffix("**"))
.unwrap_or_else(|| panic!("the {label:?} row states no bold number: {row}"));
cell.replace(['\u{a0}', ' '], "")
.parse()
.unwrap_or_else(|e| panic!("the {label:?} row's number does not parse: {cell:?} ({e})"))
}
fn quoted_entries(source: &str, decl: &str) -> usize {
let start = source
.find(decl)
.unwrap_or_else(|| panic!("the source has no {decl:?}"));
let rest = &source[start + decl.len()..];
let end = rest
.find("\n];")
.unwrap_or_else(|| panic!("{decl:?} has no closing bracket on its own line"));
rest[..end].matches('"').count() / 2
}
#[test]
fn the_readme_states_the_number_of_languages_the_filter_holds() {
assert_eq!(
stated("languages searchable and graphed"),
semlith::filter::LANGUAGES.len(),
"the README's language count and filter::LANGUAGES disagree"
);
}
#[test]
fn the_readme_states_the_number_of_edge_kinds_the_graph_stores() {
assert_eq!(
stated("edge kinds"),
semlith::graph::KINDS.len(),
"the README's edge-kind count and graph::KINDS disagree"
);
}
#[test]
fn the_readme_states_the_number_of_document_formats_with_a_reader() {
assert_eq!(
stated("document formats with a reader"),
quoted_entries(FORMATS_RS, "const HANDLED: &[&str] = &["),
"the README's format count and formats::HANDLED disagree"
);
}
#[test]
fn the_readme_states_the_number_of_image_types() {
assert_eq!(
stated("image types"),
semlith::image::EXTENSIONS.len(),
"the README's image-type count and image::EXTENSIONS disagree"
);
}
#[test]
fn the_readme_states_the_number_of_mcp_tools() {
let tools = semlith::mcp::tool_names();
assert_eq!(
stated("MCP tools"),
tools.len(),
"the README's tool count and mcp::tool_names disagree: {tools:?}"
);
}
#[test]
fn the_readme_states_the_number_of_clients_with_a_stanza() {
assert_eq!(
stated("agent clients"),
semlith::clients::clients().len(),
"the README's client count and the stanzas parsed out of docs/clients.md disagree"
);
}
#[test]
fn the_readme_states_the_number_of_cli_commands() {
let out = Command::new(env!("CARGO_BIN_EXE_semlith"))
.arg("--help")
.output()
.expect("semlith --help runs");
let help = String::from_utf8_lossy(&out.stdout).into_owned();
let mut names = Vec::new();
let mut in_commands = false;
for line in help.lines() {
if line.trim_end().ends_with("Commands:") {
in_commands = true;
continue;
}
if in_commands {
if line.trim().is_empty() {
continue;
}
if !line.starts_with(" ") || line.trim_start().starts_with('-') {
break;
}
if let Some(name) = line.split_whitespace().next() {
names.push(name.to_string());
}
}
}
names.retain(|n| n != "help");
assert!(
names.len() >= 10,
"only parsed {names:?} out of --help; the parser, not the CLI, is probably wrong"
);
assert_eq!(
stated("CLI commands"),
names.len(),
"the README's command count and `semlith --help` disagree: {names:?}"
);
}
#[test]
fn the_readme_states_the_number_of_prebuilt_targets() {
let built = RELEASE_YML.matches(" target: ").count();
assert!(
built > 0,
"the release workflow's matrix has no target rows"
);
assert_eq!(
stated("prebuilt targets"),
built,
"the README's target count and release.yml's build matrix disagree"
);
}
#[test]
fn the_readme_carries_no_release_specific_content() {
for phrase in ["from 0.", "through 0.", "before 0.", "as of 0.", "coming"] {
assert!(
!README.contains(phrase),
"the README contains {phrase:?}; a release named in it is a sentence that goes stale \
on its own"
);
}
assert!(
!README.contains("cookie"),
"the README mentions a cookie; the portal's token is a Semlith-Token request header"
);
assert!(
README.contains("`Semlith-Token` request header"),
"the README no longer says how the portal's session token travels"
);
}
#[test]
fn the_readme_is_short() {
let lines = README.lines().count();
assert!(
lines < 500,
"the README is {lines} lines; the ceiling is 500"
);
}