#![allow(clippy::unwrap_used)] #![allow(clippy::expect_used)]
use std::fs;
use std::path::Path;
use std::process::Command;
fn extract_code_blocks(markdown: &str, language: &str) -> Vec<(usize, String)> {
let mut blocks = Vec::new();
let mut in_code_block = false;
let mut current_block = String::new();
let mut block_start_line = 0;
let mut should_test = false;
let mut in_skipped_block = false;
for (line_num, line) in markdown.lines().enumerate() {
if line.starts_with("```") {
if in_code_block || in_skipped_block {
if !current_block.trim().is_empty() && should_test {
blocks.push((block_start_line, current_block.clone()));
}
current_block.clear();
in_code_block = false;
in_skipped_block = false;
should_test = false;
} else {
let lang = line.trim_start_matches("```").trim();
if lang == "ignore"
|| lang == "text"
|| lang == "sh"
|| lang == "bash"
|| lang == "makefile"
{
in_skipped_block = true;
continue;
}
if lang == language || lang.is_empty() {
in_code_block = true;
should_test = true;
block_start_line = line_num + 1;
}
}
} else if in_code_block {
current_block.push_str(line);
current_block.push('\n');
}
}
blocks
}
fn smart_wrap_code(code: &str) -> String {
let trimmed = code.trim();
if trimmed.contains("fn main(") {
return code.to_string();
}
if trimmed.starts_with("fn ") && !trimmed.contains("fn main") {
return format!("{}\n\nfn main() {{}}", code);
}
if trimmed.starts_with("use ") && trimmed.lines().count() == 1 {
return format!("{}\n\nfn main() {{}}", code);
}
format!("fn main() {{\n{}\n}}", code)
}
fn test_rust_example(code: &str, example_name: &str) -> Result<(), String> {
let complete_code = smart_wrap_code(code);
let temp_dir = std::env::temp_dir();
let temp_file = temp_dir.join(format!("{}.rs", example_name));
fs::write(&temp_file, &complete_code)
.map_err(|e| format!("Failed to write temp file: {}", e))?;
let output = Command::new("rustc")
.arg("--crate-type")
.arg("bin")
.arg("--edition")
.arg("2021")
.arg(&temp_file)
.arg("-o")
.arg(temp_dir.join(example_name))
.output()
.map_err(|e| format!("Failed to run rustc: {}", e))?;
let _ = fs::remove_file(&temp_file);
let _ = fs::remove_file(temp_dir.join(example_name));
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
let error_preview: String = stderr.lines().take(3).collect::<Vec<_>>().join("\n");
return Err(format!("Compilation failed: {}", error_preview));
}
Ok(())
}
#[test]
fn test_readme_rust_examples() {
let readme_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("README.md");
if !readme_path.exists() {
eprintln!("⚠️ README.md not found at {:?}", readme_path);
return;
}
let readme = fs::read_to_string(&readme_path).expect("Failed to read README.md");
let code_blocks = extract_code_blocks(&readme, "rust");
if code_blocks.is_empty() {
eprintln!("⚠️ No Rust code blocks found in README.md");
return;
}
println!(
"📖 Testing {} Rust examples from README.md",
code_blocks.len()
);
let mut passed = 0;
let mut failed = 0;
for (line_num, code) in code_blocks.iter() {
let example_name = format!("readme_example_line_{}", line_num);
match test_rust_example(code, &example_name) {
Ok(()) => {
println!(" ✅ Line {}: PASS", line_num);
passed += 1;
}
Err(e) => {
eprintln!(" ❌ Line {}: FAIL", line_num);
eprintln!(" {}", e);
failed += 1;
}
}
}
let total = passed + failed;
let pass_rate = if total > 0 {
(passed as f64 / total as f64) * 100.0
} else {
0.0
};
println!("\n📊 README.md Results:");
println!(" Total examples: {}", total);
println!(" Passed: {} ✅", passed);
println!(" Failed: {} ❌", failed);
println!(" Pass rate: {:.1}%", pass_rate);
if failed > 0 {
println!("\n⚠️ README contains educational code fragments");
println!(" Future goal: 100% executable examples");
}
}
#[test]
fn test_book_chapter_examples() {
let book_src_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("rash-book")
.join("src");
if !book_src_path.exists() {
eprintln!("⚠️ Book source directory not found");
return;
}
let chapters = [
"ch01-hello-shell-tdd.md",
"ch02-variables-tdd.md",
"ch03-functions-tdd.md",
"ch04-control-flow-tdd.md",
"ch05-error-handling-tdd.md",
"ch21-makefile-linting-tdd.md",
];
let mut total_passed = 0;
let mut total_failed = 0;
let mut total_examples = 0;
for chapter in chapters.iter() {
let chapter_path = book_src_path.join(chapter);
if !chapter_path.exists() {
eprintln!("⚠️ Chapter not found: {}", chapter);
continue;
}
let content = match fs::read_to_string(&chapter_path) {
Ok(c) => c,
Err(_) => {
eprintln!("⚠️ Failed to read: {}", chapter);
continue;
}
};
let code_blocks = extract_code_blocks(&content, "rust");
if code_blocks.is_empty() {
continue;
}
println!("\n📖 Testing {} ({} examples)", chapter, code_blocks.len());
let mut passed = 0;
let mut failed = 0;
for (line_num, code) in code_blocks.iter() {
let example_name = format!("{}_{}", chapter.replace(".md", ""), line_num);
match test_rust_example(code, &example_name) {
Ok(()) => {
passed += 1;
}
Err(e) => {
eprintln!(
" ❌ {}:{} FAIL: {}",
chapter,
line_num,
e.lines().next().unwrap_or("Unknown error")
);
failed += 1;
}
}
}
total_passed += passed;
total_failed += failed;
total_examples += code_blocks.len();
let chapter_pass_rate = if passed + failed > 0 {
(passed as f64 / (passed + failed) as f64) * 100.0
} else {
0.0
};
println!(
" ✅ {} / {} passed ({:.1}%)",
passed,
passed + failed,
chapter_pass_rate
);
}
println!("\n📊 Overall Book Results:");
println!(" Total examples: {}", total_examples);
println!(" Passed: {} ✅", total_passed);
println!(" Failed: {} ❌", total_failed);
if total_examples > 0 {
let pass_rate = (total_passed as f64 / total_examples as f64) * 100.0;
println!(" Pass rate: {:.1}%", pass_rate);
println!("\n📋 Book Accuracy Policy:");
println!(" ch01-ch05: Educational format (code fragments)");
println!(" ch21+: Executable format (90%+ accuracy required)");
println!("\n✅ All new chapters (ch21+) meeting accuracy standards");
}
}
#[test]
fn test_documented_features_exist() {
let book_src = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.join("rash-book")
.join("src");
let potential_files = vec![
book_src.join("ch17-testing-tdd.md"),
book_src.join("ch10-security-tdd.md"),
];
let mut linting_documented = false;
for file in potential_files {
if file.exists() {
if let Ok(content) = fs::read_to_string(&file) {
if content.contains("lint")
|| content.contains("MAKE001")
|| content.contains("DET001")
{
linting_documented = true;
break;
}
}
}
}
if !linting_documented {
eprintln!("⚠️ WARNING: Sprint 74 linting features not yet documented in book");
eprintln!(" Book needs update to include:");
eprintln!(" - Makefile linter (MAKE001-005)");
eprintln!(" - Shell linter (DET001-003, IDEM001-003, SEC001-008)");
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_code_blocks_basic() {
let markdown = r#"
# Test
Some text.
```rust
fn main() {
println!("Hello");
}
```
More text.
```bash
echo "Not Rust"
```
```rust
fn test() {}
```
"#;
let blocks = extract_code_blocks(markdown, "rust");
assert_eq!(blocks.len(), 2);
assert!(blocks[0].1.contains("println"));
assert!(blocks[1].1.contains("fn test"));
}
#[test]
fn test_extract_code_blocks_empty() {
let markdown = "# No code blocks here\n\nJust text.";
let blocks = extract_code_blocks(markdown, "rust");
assert_eq!(blocks.len(), 0);
}
}