use std::{ ffi::OsStr, fs, path::{Path, PathBuf}, process::{Command, Output}, sync::atomic::{AtomicUsize, Ordering}, time::{SystemTime, UNIX_EPOCH}, }; static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0); struct MatrixEntry { path: &'static str, expected_tests: usize, run_tests: bool, } const MATRIX: &[MatrixEntry] = &[ MatrixEntry { path: "examples/projects/basic", expected_tests: 2, run_tests: true, }, MatrixEntry { path: "examples/projects/enum-imports", expected_tests: 9, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-cli", expected_tests: 17, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-env", expected_tests: 23, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-fs", expected_tests: 35, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-io", expected_tests: 12, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-json", expected_tests: 12, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-math", expected_tests: 4, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-net", expected_tests: 9, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-num", expected_tests: 5, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-option", expected_tests: 36, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-process", expected_tests: 21, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-random", expected_tests: 2, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-result", expected_tests: 26, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-string", expected_tests: 12, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-time", expected_tests: 3, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-vec_bool", expected_tests: 19, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-vec_f64", expected_tests: 20, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-vec_i32", expected_tests: 22, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-vec_i64", expected_tests: 20, run_tests: true, }, MatrixEntry { path: "examples/projects/std-import-vec_string", expected_tests: 19, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-cli", expected_tests: 17, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-env", expected_tests: 23, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-fs", expected_tests: 35, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-io", expected_tests: 12, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-json", expected_tests: 12, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-math", expected_tests: 7, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-net", expected_tests: 9, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-num", expected_tests: 5, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-option", expected_tests: 36, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-process", expected_tests: 21, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-random", expected_tests: 3, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-result", expected_tests: 26, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-string", expected_tests: 12, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-time", expected_tests: 3, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-vec_bool", expected_tests: 19, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-vec_f64", expected_tests: 20, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-vec_i32", expected_tests: 22, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-vec_i64", expected_tests: 20, run_tests: true, }, MatrixEntry { path: "examples/projects/std-layout-local-vec_string", expected_tests: 19, run_tests: true, }, MatrixEntry { path: "examples/projects/stdlib-composition", expected_tests: 3, run_tests: true, }, MatrixEntry { path: "examples/workspaces/exp-5-local", expected_tests: 2, run_tests: true, }, MatrixEntry { path: "examples/workspaces/std-import-option", expected_tests: 1, run_tests: true, }, ]; #[test] fn user_project_conformance_matrix_checks_lists_and_runs_stable_tests() { assert_matrix_inventory(); let repo_root = repo_root(); for entry in MATRIX { let fixture = repo_root.join(entry.path); assert!( fixture.join("slovo.toml").is_file(), "{} must remain an existing project or workspace fixture", entry.path ); let check = run_glagol( [OsStr::new("check"), fixture.as_os_str()], &temp_root(entry.path, "check"), ); assert_success_no_stderr(entry.path, "check", &check); assert_stdout_eq(entry.path, "check", &check, ""); let list = run_glagol( [ OsStr::new("test"), OsStr::new("--list"), fixture.as_os_str(), ], &temp_root(entry.path, "list"), ); assert_success_no_stderr(entry.path, "test --list", &list); assert_stdout_contains( entry.path, "test --list", &list, &format!( "{} test(s) selected (total_discovered {}, selected {}, passed 0, failed 0, skipped 0, filter none)", entry.expected_tests, entry.expected_tests, entry.expected_tests ), ); if entry.run_tests { let test = run_glagol( [OsStr::new("test"), fixture.as_os_str()], &temp_root(entry.path, "test"), ); assert_success_no_stderr(entry.path, "test", &test); assert_stdout_contains( entry.path, "test", &test, &format!("{} test(s) passed", entry.expected_tests), ); } } } fn assert_matrix_inventory() { assert_eq!(MATRIX.len(), 43, "beta25 fixture-root count changed"); assert_eq!( MATRIX .iter() .map(|entry| entry.expected_tests) .sum::(), 655, "beta25 discovered-test count changed" ); let matrix_paths = MATRIX .iter() .map(|entry| entry.path.to_owned()) .collect::>(); let mut sorted_matrix_paths = matrix_paths.clone(); sorted_matrix_paths.sort(); assert_eq!( matrix_paths, sorted_matrix_paths, "beta25 user-project conformance matrix must remain sorted by repository-relative path" ); let discovered_paths = discover_fixture_paths(&repo_root()); assert_eq!( discovered_paths, matrix_paths, "beta25 user-project conformance matrix must cover every top-level example project and workspace fixture" ); let inventory = MATRIX .iter() .map(|entry| { format!( "{}|tests={}|run_tests={}", entry.path, entry.expected_tests, entry.run_tests ) }) .collect::>() .join("\n"); assert_eq!( inventory, concat!( "examples/projects/basic|tests=2|run_tests=true\n", "examples/projects/enum-imports|tests=9|run_tests=true\n", "examples/projects/std-import-cli|tests=17|run_tests=true\n", "examples/projects/std-import-env|tests=23|run_tests=true\n", "examples/projects/std-import-fs|tests=35|run_tests=true\n", "examples/projects/std-import-io|tests=12|run_tests=true\n", "examples/projects/std-import-json|tests=12|run_tests=true\n", "examples/projects/std-import-math|tests=4|run_tests=true\n", "examples/projects/std-import-net|tests=9|run_tests=true\n", "examples/projects/std-import-num|tests=5|run_tests=true\n", "examples/projects/std-import-option|tests=36|run_tests=true\n", "examples/projects/std-import-process|tests=21|run_tests=true\n", "examples/projects/std-import-random|tests=2|run_tests=true\n", "examples/projects/std-import-result|tests=26|run_tests=true\n", "examples/projects/std-import-string|tests=12|run_tests=true\n", "examples/projects/std-import-time|tests=3|run_tests=true\n", "examples/projects/std-import-vec_bool|tests=19|run_tests=true\n", "examples/projects/std-import-vec_f64|tests=20|run_tests=true\n", "examples/projects/std-import-vec_i32|tests=22|run_tests=true\n", "examples/projects/std-import-vec_i64|tests=20|run_tests=true\n", "examples/projects/std-import-vec_string|tests=19|run_tests=true\n", "examples/projects/std-layout-local-cli|tests=17|run_tests=true\n", "examples/projects/std-layout-local-env|tests=23|run_tests=true\n", "examples/projects/std-layout-local-fs|tests=35|run_tests=true\n", "examples/projects/std-layout-local-io|tests=12|run_tests=true\n", "examples/projects/std-layout-local-json|tests=12|run_tests=true\n", "examples/projects/std-layout-local-math|tests=7|run_tests=true\n", "examples/projects/std-layout-local-net|tests=9|run_tests=true\n", "examples/projects/std-layout-local-num|tests=5|run_tests=true\n", "examples/projects/std-layout-local-option|tests=36|run_tests=true\n", "examples/projects/std-layout-local-process|tests=21|run_tests=true\n", "examples/projects/std-layout-local-random|tests=3|run_tests=true\n", "examples/projects/std-layout-local-result|tests=26|run_tests=true\n", "examples/projects/std-layout-local-string|tests=12|run_tests=true\n", "examples/projects/std-layout-local-time|tests=3|run_tests=true\n", "examples/projects/std-layout-local-vec_bool|tests=19|run_tests=true\n", "examples/projects/std-layout-local-vec_f64|tests=20|run_tests=true\n", "examples/projects/std-layout-local-vec_i32|tests=22|run_tests=true\n", "examples/projects/std-layout-local-vec_i64|tests=20|run_tests=true\n", "examples/projects/std-layout-local-vec_string|tests=19|run_tests=true\n", "examples/projects/stdlib-composition|tests=3|run_tests=true\n", "examples/workspaces/exp-5-local|tests=2|run_tests=true\n", "examples/workspaces/std-import-option|tests=1|run_tests=true", ), "beta25 user-project conformance matrix changed without updating the inventory assertion" ); } fn discover_fixture_paths(repo_root: &Path) -> Vec { let mut paths = Vec::new(); for parent in ["examples/projects", "examples/workspaces"] { let parent_path = repo_root.join(parent); for entry in fs::read_dir(&parent_path).unwrap_or_else(|err| { panic!( "read fixture inventory `{}`: {}", parent_path.display(), err ); }) { let entry = entry.unwrap_or_else(|err| { panic!( "read fixture entry under `{}`: {}", parent_path.display(), err ); }); let path = entry.path(); if path.join("slovo.toml").is_file() { paths.push( path.strip_prefix(repo_root) .expect("fixture path under repository root") .to_string_lossy() .replace('\\', "/"), ); } } } paths.sort(); paths } fn repo_root() -> PathBuf { Path::new(env!("CARGO_MANIFEST_DIR")) .parent() .expect("compiler crate has repository parent") .to_path_buf() } fn run_glagol(args: I, cwd: &Path) -> Output where I: IntoIterator, S: AsRef, { fs::create_dir_all(cwd).unwrap_or_else(|err| { panic!("create command cwd `{}`: {}", cwd.display(), err); }); let mut command = Command::new(env!("CARGO_BIN_EXE_glagol")); configure_conformance_env(&mut command); command .args(args) .current_dir(cwd) .output() .expect("run glagol") } fn configure_conformance_env(command: &mut Command) { for key in [ "GLAGOL_STD_IMPORT_ENV_ALPHA_UNLIKELY_MISSING", "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_UNLIKELY_MISSING", ] { command.env_remove(key); } for (key, value) in [ ( "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT", "glagol-std-import-env-alpha-value", ), ("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I32", "42"), ("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I64", "42000000000"), ("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U32", "42"), ("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U64", "4294967296"), ("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_F64", "42.5"), ("GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_BOOL", "true"), ("GLAGOL_STD_IMPORT_ENV_ALPHA_INVALID", "not-a-number"), ( "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT", "glagol-std-layout-local-env-alpha-value", ), ("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I32", "42"), ( "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_I64", "42000000000", ), ("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U32", "42"), ( "GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_U64", "4294967296", ), ("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_F64", "42.5"), ("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_PRESENT_BOOL", "true"), ("GLAGOL_STD_LAYOUT_LOCAL_ENV_ALPHA_INVALID", "not-a-number"), ] { command.env(key, value); } } fn temp_root(path: &str, command: &str) -> PathBuf { let id = NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed); let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system clock before UNIX_EPOCH") .as_nanos(); let fixture = path .chars() .map(|ch| if ch == '/' || ch == '-' { '_' } else { ch }) .collect::(); std::env::temp_dir().join(format!( "glagol-user-project-conformance-beta25-{}-{}-{}-{}-{}", std::process::id(), nanos, id, fixture, command )) } fn assert_success_no_stderr(fixture: &str, command: &str, output: &Output) { assert!( output.status.success(), "{} `{}` failed\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}", fixture, command, output.status.code(), String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); assert!( output.stderr.is_empty(), "{} `{}` wrote stderr:\n{}", fixture, command, String::from_utf8_lossy(&output.stderr) ); } fn assert_stdout_eq(fixture: &str, command: &str, output: &Output, expected: &str) { assert_eq!( String::from_utf8_lossy(&output.stdout), expected, "{} `{}` stdout mismatch", fixture, command ); } fn assert_stdout_contains(fixture: &str, command: &str, output: &Output, needle: &str) { let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains(needle), "{} `{}` stdout did not contain `{}`:\n{}", fixture, command, needle, stdout ); }