pdftract/tests/doctor_runbook_coverage.rs
jedarden d9d21df157 docs(pdftract-653ah): add runbook integration for pdftract doctor
- Created docs/operations/manual-platform-smoke.md with comprehensive
  smoke test runbook for KU-12 quarterly manual platform testing
- Added troubleshooting table covering all 14 doctor checks
- Cross-referenced runbook from installation.md and quickstart.md
- Added CI gate test (doctor_runbook_coverage.rs) to verify
  troubleshooting table completeness

Acceptance criteria:
✓ Step 1: pdftract doctor as first section in runbook
✓ Troubleshooting table covers all FAIL-capable checks
✓ installation.md mentions pdftract doctor with runbook link
✓ quickstart.md uses pdftract doctor as first example command
✓ CI gate parses runbook and asserts all checks are present
✓ mdBook build succeeds
✓ No broken internal links

Closes: pdftract-653ah
2026-05-24 13:26:31 -04:00

85 lines
3 KiB
Rust

//! CI gate: Verify runbook troubleshooting table covers all doctor checks
//!
//! This test ensures that every check in the doctor registry has a corresponding
//! row in the troubleshooting table in docs/operations/manual-platform-smoke.md.
//!
//! Bead: pdftract-653ah (runbook integration)
use std::collections::HashSet;
fn main() {
// Load the runbook
let runbook_path = std::path::Path::new("docs/operations/manual-platform-smoke.md");
let runbook_content = std::fs::read_to_string(runbook_path)
.expect("Runbook file not found. Has docs/operations/manual-platform-smoke.md been created?");
// Extract check names from the troubleshooting table
// The table has rows like: "| tesseract install (FAIL) | ... |"
let mut table_checks = HashSet::new();
for line in runbook_content.lines() {
if let Some(start) = line.find("| ") {
if let Some(end) = line.find(" (FAIL)") {
let check_name = line[start + 2..end].trim();
table_checks.insert(check_name.to_string());
}
if let Some(end) = line.find(" (WARN)") {
let check_name = line[start + 2..end].trim();
table_checks.insert(check_name.to_string());
}
}
}
// Get all check names from the doctor registry
// We use the known check list instead of runtime registry
let expected_checks = vec![
"pdftract binary",
"tesseract install",
"tesseract languages",
"leptonica install",
"libtiff",
"libopenjp2",
"pdfium native lib",
"network reachability",
"cache directory",
"profile search path",
"ulimit -n",
"available RAM",
"system locale",
"temp dir writable",
];
// Verify each expected check is in the table
let mut missing_checks = Vec::new();
for check in &expected_checks {
if !table_checks.contains(*check) {
missing_checks.push(*check);
}
}
if !missing_checks.is_empty() {
eprintln!(
"ERROR: Runbook troubleshooting table is missing checks: {:?}",
missing_checks
);
eprintln!("Please add rows to docs/operations/manual-platform-smoke.md for each missing check.");
std::process::exit(1);
}
// Verify table doesn't have orphaned checks (checks in table but not in registry)
let expected_set: HashSet<String> = expected_checks.iter().map(|s| s.to_string()).collect();
let orphaned: Vec<_> = table_checks
.difference(&expected_set)
.collect();
if !orphaned.is_empty() {
eprintln!(
"ERROR: Runbook troubleshooting table has orphaned checks (in table but not in registry): {:?}",
orphaned
);
eprintln!("Please remove these rows or add the checks to the doctor registry.");
std::process::exit(1);
}
println!("✓ Runbook troubleshooting table covers all doctor checks");
println!("✓ No orphaned checks in table");
}