use std::{ ffi::OsStr, fs, path::{Path, PathBuf}, process::{Command, Output}, sync::atomic::{AtomicUsize, Ordering}, }; static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0); const EXPECTED_TEST_OUTPUT: &str = concat!( "test \"stdlib composition reads parses and cleans\" ... ok\n", "test \"stdlib composition clamps and squares\" ... ok\n", "test \"stdlib composition main score\" ... ok\n", "3 test(s) passed\n", ); #[test] fn stdlib_composition_project_checks_tests_docs_and_runs() { let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR")); let repo_root = compiler_root.parent().expect("compiler has repo parent"); let project = repo_root.join("examples/projects/stdlib-composition"); let source = read(&project.join("src/main.slo")); assert!( !project.join("src/fs.slo").exists() && !project.join("src/string.slo").exists() && !project.join("src/math.slo").exists() && !project.join("src/io.slo").exists(), "stdlib composition must use repo-root std imports, not local module copies" ); assert!( source.contains( "(import std.fs (write_text_status read_text_result exists is_file remove_file_ok))" ) && source.contains("(import std.string (concat parse_i32_result))") && source.contains("(import std.math (clamp_i32 square_i32))") && source.contains("(import std.io (print_i32_zero))"), "stdlib composition fixture must compose several explicit std imports" ); assert!( source.contains("(fn score_file_roundtrip_ok") && source.contains("(fn computed_score") && source.contains("(fn main_score") && source.contains("(fn cleanup_score_file"), "stdlib composition fixture must keep realistic read/parse/compute/cleanup flow visible" ); let fmt = run_glagol([ OsStr::new("fmt"), OsStr::new("--check"), project.as_os_str(), ]); assert_success("stdlib composition fmt --check", &fmt); let check = run_glagol([OsStr::new("check"), project.as_os_str()]); assert_success_stdout(check, "", "stdlib composition check"); let test_cwd = temp_root("test"); let test = run_glagol_in([OsStr::new("test"), project.as_os_str()], &test_cwd); assert_success_stdout(test, EXPECTED_TEST_OUTPUT, "stdlib composition test"); assert!( !test_cwd.join("slovo-stdlib-composition-beta.txt").exists(), "stdlib composition tests must clean their file fixture" ); let docs = temp_root("docs"); let doc = run_glagol([ OsStr::new("doc"), project.as_os_str(), OsStr::new("-o"), docs.as_os_str(), ]); assert_success("stdlib composition doc", &doc); let doc_index = read(&docs.join("index.md")); assert!(doc_index.contains("## Module main")); assert!(doc_index.contains("- `computed_score")); assert!(doc_index.contains("- `stdlib composition clamps and squares`")); let run_cwd = temp_root("run"); let run = run_glagol_in([OsStr::new("run"), project.as_os_str()], &run_cwd); if run.status.success() { assert_eq!(String::from_utf8_lossy(&run.stdout), "100\n"); assert!( run.stderr.is_empty(), "stdlib composition run wrote stderr:\n{}", String::from_utf8_lossy(&run.stderr) ); assert!( !run_cwd.join("slovo-stdlib-composition-beta.txt").exists(), "stdlib composition run must clean its file fixture" ); } else { assert_stderr_contains("stdlib composition run", &run, "ToolchainUnavailable"); } } fn run_glagol(args: I) -> Output where I: IntoIterator, S: AsRef, { run_glagol_in(args, Path::new(env!("CARGO_MANIFEST_DIR"))) } fn run_glagol_in(args: I, cwd: &Path) -> Output where I: IntoIterator, S: AsRef, { fs::create_dir_all(cwd).expect("create command cwd"); Command::new(env!("CARGO_BIN_EXE_glagol")) .args(args) .current_dir(cwd) .output() .expect("run glagol") } fn temp_root(name: &str) -> PathBuf { let id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed); let nanos = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .expect("system clock before UNIX_EPOCH") .as_nanos(); std::env::temp_dir().join(format!( "glagol-stdlib-composition-{}-{}-{}-{}", std::process::id(), nanos, id, name )) } fn read(path: &Path) -> String { fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err)) } fn assert_success(context: &str, output: &Output) { assert!( output.status.success(), "{} failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", context, output.status.code(), String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); } fn assert_success_stdout(output: Output, expected: &str, context: &str) { assert_success(context, &output); assert_eq!( String::from_utf8_lossy(&output.stdout), expected, "{}", context ); } fn assert_stderr_contains(context: &str, output: &Output, needle: &str) { let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains(needle), "{} stderr did not contain `{}`:\n{}", context, needle, stderr ); }