use std::{ env, fs, path::{Path, PathBuf}, process::Command, }; const SUPPORTED_COMPILE_FIXTURES: &[&str] = &[ "../examples/add.slo", "../examples/array-direct-scalars-value-flow.slo", "../examples/array-direct-scalars.slo", "../examples/array-enum.slo", "../examples/array-struct-elements.slo", "../examples/array-struct-fields.slo", "../examples/array-string-value-flow.slo", "../examples/array-string.slo", "../examples/array-value-flow.slo", "../examples/array.slo", "../examples/enum-payload-structs.slo", "../examples/if.slo", "../examples/local-variables.slo", "../examples/option-result-flow.slo", "../examples/option-result-match.slo", "../examples/option-result-payload.slo", "../examples/option-result.slo", "../examples/owned-string-concat.slo", "../examples/f64-to-i32-result.slo", "../examples/f64-to-i64-result.slo", "../examples/f64-to-string.slo", "../examples/print-bool.slo", "../examples/standard-runtime.slo", "../examples/string-parse-bool-result.slo", "../examples/string-parse-i64-result.slo", "../examples/string-parse-f64-result.slo", "../examples/string-print.slo", "../examples/string-value-flow.slo", "../examples/struct-value-flow.slo", "../examples/struct.slo", "../examples/unsafe.slo", "../examples/while.slo", ]; #[test] #[ignore = "requires clang or GLAGOL_CLANG; optional promotion smoke for emitted LLVM IR"] fn emitted_supported_fixture_ir_compiles_with_clang_when_available() { let Some(clang) = find_clang() else { eprintln!("skipping LLVM smoke test: set GLAGOL_CLANG or install clang"); return; }; let manifest = Path::new(env!("CARGO_MANIFEST_DIR")); let runtime = manifest.join("../runtime/runtime.c"); let temp_dir = env::temp_dir().join(format!("glagol-llvm-smoke-{}", std::process::id())); fs::create_dir_all(&temp_dir) .unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err)); for fixture in SUPPORTED_COMPILE_FIXTURES { let ir = emit_fixture_ir(manifest, fixture); let stem = fixture_stem(fixture); let ir_path = temp_dir.join(format!("{}.ll", stem)); let exe_path = temp_dir.join(stem); fs::write(&ir_path, ir) .unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err)); let mut command = Command::new(&clang); command .arg(&runtime) .arg(&ir_path) .arg("-o") .arg(&exe_path) .current_dir(manifest); configure_clang_runtime_env(&mut command, &clang); let output = command .output() .unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err)); let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); assert!( output.status.success(), "clang rejected emitted LLVM for `{}`\nstdout:\n{}\nstderr:\n{}", fixture, stdout, stderr ); } } fn emit_fixture_ir(manifest: &Path, fixture: &str) -> String { let output = Command::new(env!("CARGO_BIN_EXE_glagol")) .arg(fixture) .current_dir(manifest) .output() .unwrap_or_else(|err| panic!("run glagol on `{}`: {}", fixture, err)); let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); assert!( output.status.success(), "glagol rejected `{}`\nstdout:\n{}\nstderr:\n{}", fixture, stdout, stderr ); assert!( stderr.is_empty(), "glagol wrote stderr for `{}`: {}", fixture, stderr ); stdout.into_owned() } fn find_clang() -> Option { if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) { return Some(PathBuf::from(path)); } let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang"); if hermetic_clang.is_file() { return Some(hermetic_clang); } find_on_path("clang") } fn find_on_path(program: &str) -> Option { let path = env::var_os("PATH")?; env::split_paths(&path) .map(|dir| dir.join(program)) .find(|candidate| candidate.is_file()) } fn configure_clang_runtime_env(command: &mut Command, clang: &Path) { if !clang.starts_with("/tmp/glagol-clang-root") { return; } let root = Path::new("/tmp/glagol-clang-root"); let lib64 = root.join("usr/lib64"); let lib = root.join("usr/lib"); let mut paths = vec![lib64, lib]; if let Some(existing) = env::var_os("LD_LIBRARY_PATH") { paths.extend(env::split_paths(&existing)); } let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH"); command.env("LD_LIBRARY_PATH", joined); } fn fixture_stem(fixture: &str) -> String { Path::new(fixture) .file_stem() .and_then(|stem| stem.to_str()) .expect("fixture has utf-8 file stem") .replace('-', "_") }