slovo/compiler/tests/llvm_smoke.rs
2026-05-22 08:38:43 +02:00

156 lines
5.0 KiB
Rust

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<PathBuf> {
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<PathBuf> {
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('-', "_")
}