/// Integration test — compile every example .ds file through the full pipeline. /// This is the ultimate regression guard: if any of the 51 examples break, this test catches it. use std::fs; use std::path::PathBuf; /// Get the workspace root (two levels up from ds-cli's Cargo.toml). fn workspace_root() -> PathBuf { let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR")); manifest.parent().unwrap().parent().unwrap().to_path_buf() } /// Compile a .ds source string through the full pipeline (lex → parse → analyze → codegen). fn compile_source(source: &str) -> Result { let mut lexer = ds_parser::Lexer::new(source); let tokens = lexer.tokenize(); for tok in &tokens { if let ds_parser::TokenKind::Error(msg) = &tok.kind { return Err(format!("Lexer error at line {}: {}", tok.line, msg)); } } let mut parser = ds_parser::Parser::with_source(tokens, source); let program = parser.parse_program().map_err(|e| e.to_string())?; let graph = ds_analyzer::SignalGraph::from_program(&program); let views = ds_analyzer::SignalGraph::analyze_views(&program); let html = ds_codegen::JsEmitter::emit_html(&program, &graph, &views, false); Ok(html) } #[test] fn test_compile_all_examples() { // Spawn with 8MB stack to handle deeply nested examples let builder = std::thread::Builder::new() .name("compile_examples".into()) .stack_size(8 * 1024 * 1024); let handle = builder.spawn(|| { let examples_dir = workspace_root().join("examples"); assert!(examples_dir.exists(), "examples/ directory not found at {:?}", examples_dir); let mut ds_files: Vec = fs::read_dir(&examples_dir) .expect("cannot read examples/") .filter_map(|e| e.ok()) .map(|e| e.path()) .filter(|p| p.extension().map_or(false, |ext| ext == "ds")) .collect(); ds_files.sort(); assert!(!ds_files.is_empty(), "no .ds files found in examples/"); let mut pass = 0; let mut fail = 0; let mut failures = Vec::new(); for path in &ds_files { let source = fs::read_to_string(path) .unwrap_or_else(|e| panic!("cannot read {}: {}", path.display(), e)); let name = path.file_name().unwrap().to_str().unwrap(); match compile_source(&source) { Ok(html) => { assert!(!html.is_empty(), "{}: produced empty output", name); pass += 1; } Err(e) => { failures.push(format!(" {} — {}", name, e)); fail += 1; } } } eprintln!("\n Examples: {} passed, {} failed, {} total", pass, fail, ds_files.len()); if !failures.is_empty() { panic!( "\n{} example(s) failed to compile:\n{}\n", fail, failures.join("\n") ); } }).expect("failed to spawn test thread"); handle.join().expect("test thread panicked"); }