use std::{ ffi::OsStr, fs, path::PathBuf, process::{Command, Output}, sync::atomic::{AtomicUsize, Ordering}, time::{SystemTime, UNIX_EPOCH}, }; static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0); const CASES: &[ReservedCase] = &[ ReservedCase { name: "generic-function", source: r#" (module main) (fn id (type_params T) ((value T)) -> T value) (fn main () -> i32 0) "#, code: "UnsupportedGenericFunction", message: "generic function declarations are reserved but not supported in the current beta", }, ReservedCase { name: "generic-type-alias", source: r#" (module main) (type VecOf (type_params T) (vec T)) (fn main () -> i32 0) "#, code: "UnsupportedGenericTypeAlias", message: "parameterized type aliases are reserved but not supported in the current beta", }, ReservedCase { name: "generic-type-parameter", source: r#" (module main) (fn main () -> i32 (let xs (vec T) (std.vec.i32.empty)) 0) "#, code: "UnsupportedGenericTypeParameter", message: "generic type parameter `T` is reserved but not supported in the current beta", }, ReservedCase { name: "generic-vector-spelling", source: r#" (module main) (fn main () -> (vec) (std.vec.i32.empty)) "#, code: "UnsupportedGenericTypeParameter", message: "generic vector syntax is reserved but not supported in the current beta", }, ReservedCase { name: "map-type", source: r#" (module main) (fn main () -> (map string i32) 0) "#, code: "UnsupportedMapType", message: "`map` types are reserved but not supported in the current beta", }, ReservedCase { name: "set-type", source: r#" (module main) (fn main () -> (set string) 0) "#, code: "UnsupportedSetType", message: "`set` types are reserved but not supported in the current beta", }, ReservedCase { name: "std-vec-empty", source: r#" (module main) (fn main () -> i32 (std.vec.empty i32) 0) "#, code: "UnsupportedGenericStandardLibraryCall", message: "generic standard-library call `std.vec.empty` is reserved but not supported in the current beta", }, ReservedCase { name: "std-result-map", source: r#" (module main) (fn main () -> i32 (std.result.map (ok string i32 "a") mapper) 0) "#, code: "UnsupportedGenericStandardLibraryCall", message: "generic standard-library call `std.result.map` is reserved but not supported in the current beta", }, ]; #[test] fn check_fmt_and_project_paths_reject_reserved_generic_collection_surface() { for case in CASES { let fixture = write_fixture(case); let check = run_glagol([OsStr::new("check"), fixture.as_os_str()]); assert_rejection(&format!("{} check", case.name), &check, case); let fmt = run_glagol([ OsStr::new("fmt"), OsStr::new("--check"), fixture.as_os_str(), ]); assert_rejection(&format!("{} fmt --check", case.name), &fmt, case); let project = write_project(case); let project_check = run_glagol([OsStr::new("check"), project.as_os_str()]); assert_rejection( &format!("{} project check", case.name), &project_check, case, ); } } struct ReservedCase { name: &'static str, source: &'static str, code: &'static str, message: &'static str, } fn assert_rejection(context: &str, output: &Output, case: &ReservedCase) { assert_eq!( output.status.code(), Some(1), "{} exit code mismatch\nstdout:\n{}\nstderr:\n{}", context, String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) ); assert!( output.stdout.is_empty(), "{} wrote stdout:\n{}", context, String::from_utf8_lossy(&output.stdout) ); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains(&format!("error[{}]", case.code)), "{} human diagnostic did not contain code `{}`:\n{}", context, case.code, stderr ); assert!( stderr.contains(&format!(" (code {})", case.code)), "{} machine diagnostic did not contain code `{}`:\n{}", context, case.code, stderr ); assert!( stderr.contains(case.message), "{} stderr did not contain message `{}`:\n{}", context, case.message, stderr ); assert!( !stderr.contains("beta.9"), "{} stderr still used beta.9 wording:\n{}", context, stderr ); } fn write_fixture(case: &ReservedCase) -> PathBuf { let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed); let path = unique_base_path(&format!("file-{}-{}", id, case.name)).with_extension("slo"); fs::write(&path, case.source) .unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err)); path } fn write_project(case: &ReservedCase) -> PathBuf { let id = NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed); let root = unique_base_path(&format!("project-{}-{}", id, case.name)); let src = root.join("src"); fs::create_dir_all(&src).unwrap_or_else(|err| panic!("create `{}`: {}", src.display(), err)); fs::write( root.join("slovo.toml"), format!( "[project]\nname = \"reserved-beta15-{}\"\nsource_root = \"src\"\nentry = \"main\"\n", case.name ), ) .unwrap_or_else(|err| panic!("write project manifest for `{}`: {}", case.name, err)); fs::write(src.join("main.slo"), case.source) .unwrap_or_else(|err| panic!("write project source for `{}`: {}", case.name, err)); root } fn unique_base_path(name: &str) -> PathBuf { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .map(|duration| duration.as_nanos()) .unwrap_or(0); std::env::temp_dir().join(format!( "glagol-reserved-beta15-{}-{}-{}", std::process::id(), nanos, name )) } fn run_glagol(args: I) -> Output where I: IntoIterator, S: AsRef, { Command::new(env!("CARGO_BIN_EXE_glagol")) .args(args) .output() .expect("run glagol") }