use std::path::Path;
const CHAPTERS: &[(&str, &str)] = &[
("ch01_hello_apr", "apr-book-ch01-v1.yaml"),
("ch02_tensors", "apr-book-ch02-v1.yaml"),
("ch03_apr_format", "apr-book-ch03-v1.yaml"),
("ch04_supervised", "apr-book-ch04-v1.yaml"),
("ch05_unsupervised", "apr-book-ch05-v1.yaml"),
("ch06_ensembles", "apr-book-ch06-v1.yaml"),
("ch07_model_selection", "apr-book-ch07-v1.yaml"),
("ch08_transformer", "apr-book-ch08-v1.yaml"),
("ch09_inference", "apr-book-ch09-v1.yaml"),
("ch10_training", "apr-book-ch10-v1.yaml"),
("ch11_formats", "apr-book-ch11-v1.yaml"),
("ch12_serving", "apr-book-ch12-v1.yaml"),
("ch13_profiling", "apr-book-ch13-v1.yaml"),
("ch14_contracts", "apr-book-ch14-v1.yaml"),
("ch15_orchestrate", "apr-book-ch15-v1.yaml"),
("ch16_timeseries", "apr-book-ch16-v1.yaml"),
("ch17_bayesian", "apr-book-ch17-v1.yaml"),
("ch18_graphs", "apr-book-ch18-v1.yaml"),
("ch19_text", "apr-book-ch19-v1.yaml"),
("ch20_rag", "apr-book-ch20-v1.yaml"),
("ch21_vs_candle", "apr-book-ch21-v1.yaml"),
("ch22_vs_llamacpp", "apr-book-ch22-v1.yaml"),
("ch23_training_bench", "apr-book-ch23-v1.yaml"),
("ch24_switch_pytorch", "apr-book-ch24-v1.yaml"),
("ch25_switch_ollama", "apr-book-ch25-v1.yaml"),
("ch26_switch_ndarray", "apr-book-ch26-v1.yaml"),
("ch27_switch_unsloth", "apr-book-ch27-v1.yaml"),
];
fn workspace_root() -> &'static Path {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("crates/")
.parent()
.expect("workspace root")
}
#[test]
fn all_chapter_examples_exist() {
let examples_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples");
let mut missing = Vec::new();
for (example, _) in CHAPTERS {
let path = examples_dir.join(format!("{example}.rs"));
if !path.exists() {
missing.push(example.to_string());
}
}
assert!(
missing.is_empty(),
"Missing chapter examples: {:?}",
missing
);
}
#[test]
fn all_chapter_contracts_exist() {
let contracts_dir = workspace_root().join("contracts");
let mut missing = Vec::new();
for (_, contract) in CHAPTERS {
let path = contracts_dir.join(contract);
if !path.exists() {
missing.push(contract.to_string());
}
}
assert!(
missing.is_empty(),
"Missing chapter contracts: {:?}",
missing
);
}
#[test]
fn chapter_count_is_27() {
assert_eq!(CHAPTERS.len(), 27, "Book must have exactly 27 chapters");
}
#[test]
fn namespace_discipline_in_examples() {
let examples_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("examples");
let legacy_names = [
"trueno",
"realizar",
"entrenar",
"batuta",
"presentar",
"renacer",
];
let mut violations = Vec::new();
for (example, _) in CHAPTERS {
let path = examples_dir.join(format!("{example}.rs"));
if let Ok(content) = std::fs::read_to_string(&path) {
for name in &legacy_names {
if content.contains(&format!("\"{name}\"")) && example == &"ch14_contracts" {
continue;
}
if content.contains(&format!("use {name}::"))
|| content.contains(&format!("{name} ="))
|| content.contains(&format!("cargo install {name}"))
{
violations.push(format!("{example}: contains legacy name '{name}'"));
}
}
}
}
assert!(
violations.is_empty(),
"Namespace violations: {:?}",
violations
);
}
#[test]
fn contracts_have_falsification_conditions() {
let contracts_dir = workspace_root().join("contracts");
let mut missing_falsification = Vec::new();
for (_, contract) in CHAPTERS {
let path = contracts_dir.join(contract);
if let Ok(content) = std::fs::read_to_string(&path) {
if !content.contains("falsification:") {
missing_falsification.push(contract.to_string());
}
let condition_count = content.matches("condition:").count();
if condition_count < 5 {
missing_falsification.push(format!(
"{contract}: only {condition_count} conditions (need 5)"
));
}
}
}
assert!(
missing_falsification.is_empty(),
"Contracts missing falsification: {:?}",
missing_falsification
);
}
#[test]
fn spec_file_exists() {
let spec_paths = [
workspace_root().join("docs/specifications/apr-book-spec.md"),
workspace_root().join("docs/specifications/aprender-monorepo-consolidation.md"),
];
let spec = spec_paths
.iter()
.find(|p| p.exists())
.expect("Book spec must exist at one of the known paths");
let content = std::fs::read_to_string(spec).expect("read spec");
assert!(
content.contains("falsification") || content.contains("Falsification"),
"Spec must reference falsification"
);
}