272 lines
9.0 KiB
Rust
272 lines
9.0 KiB
Rust
use std::{
|
|
env,
|
|
ffi::OsStr,
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
process::{Command, Output},
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
};
|
|
|
|
static NEXT_FIXTURE_ID: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
#[test]
|
|
fn i64_fixture_lowers_and_runs_tests() {
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/i64-numeric-primitive.slo");
|
|
|
|
let llvm = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile i64 fixture", &llvm);
|
|
let stdout = String::from_utf8_lossy(&llvm.stdout);
|
|
assert!(
|
|
stdout.contains("declare void @print_i64(i64)")
|
|
&& stdout.contains("define i64 @base()")
|
|
&& stdout.contains("add i64")
|
|
&& stdout.contains("mul i64")
|
|
&& stdout.contains("icmp sgt i64")
|
|
&& stdout.contains("icmp slt i64")
|
|
&& stdout.contains("icmp eq i64")
|
|
&& stdout.contains("call void @print_i64(i64"),
|
|
"i64 LLVM shape drifted\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
|
|
let division_fixture = write_fixture(
|
|
"division-lowering",
|
|
"(module main)\n\n(fn quotient ((value i64)) -> i64\n (/ value 3i64))\n\n(fn main () -> i32\n (if (>= (quotient 4294967289i64) 1431655763i64) 0 1))\n",
|
|
);
|
|
let division_llvm = run_glagol([division_fixture.as_os_str()]);
|
|
assert_success("compile i64 division lowering", &division_llvm);
|
|
let division_stdout = String::from_utf8_lossy(&division_llvm.stdout);
|
|
assert!(
|
|
division_stdout.contains("sdiv i64") && division_stdout.contains("icmp sge i64"),
|
|
"i64 division/order LLVM shape drifted\nstdout:\n{}",
|
|
division_stdout
|
|
);
|
|
|
|
let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
assert_success("run i64 fixture tests", &tests);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&tests.stdout),
|
|
concat!(
|
|
"test \"i64 arithmetic returns exact fixture value\" ... ok\n",
|
|
"test \"i64 comparison works in predicates\" ... ok\n",
|
|
"test \"i64 division and ordering\" ... ok\n",
|
|
"3 test(s) passed\n",
|
|
),
|
|
"i64 test runner stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn i64_formatter_and_lowering_are_visible() {
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/i64-numeric-primitive.slo");
|
|
|
|
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
assert_success("format i64 fixture", &formatted);
|
|
let formatted_stdout = String::from_utf8_lossy(&formatted.stdout);
|
|
assert!(
|
|
formatted_stdout.contains("(fn base () -> i64")
|
|
&& formatted_stdout.contains("2147483648i64")
|
|
&& formatted_stdout.contains("(std.io.print_i64 (local_total))"),
|
|
"i64 formatter output omitted expected forms\nstdout:\n{}",
|
|
formatted_stdout
|
|
);
|
|
|
|
let surface = run_glagol([
|
|
OsStr::new("--inspect-lowering=surface"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect i64 surface lowering", &surface);
|
|
let surface_stdout = String::from_utf8_lossy(&surface.stdout);
|
|
assert_eq!(
|
|
surface_stdout,
|
|
fs::read_to_string(
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../tests/i64-numeric-primitive.surface.lower")
|
|
)
|
|
.expect("read i64 surface snapshot"),
|
|
"i64 surface lowering snapshot drifted"
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
OsStr::new("--inspect-lowering=checked"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect i64 checked lowering", &checked);
|
|
let checked_stdout = String::from_utf8_lossy(&checked.stdout);
|
|
assert_eq!(
|
|
checked_stdout,
|
|
fs::read_to_string(
|
|
Path::new(env!("CARGO_MANIFEST_DIR"))
|
|
.join("../tests/i64-numeric-primitive.checked.lower")
|
|
)
|
|
.expect("read i64 checked snapshot"),
|
|
"i64 checked lowering snapshot drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn mixed_i32_i64_f64_operands_are_rejected_clearly() {
|
|
for (name, expression, found) in [
|
|
("mixed-i32-i64", "(= 1 1i64)", "i32 and i64"),
|
|
("mixed-i64-f64", "(= 1i64 1.0)", "i64 and f64"),
|
|
] {
|
|
let fixture = write_fixture(
|
|
name,
|
|
&format!(
|
|
"(module main)\n\n(fn main () -> i32\n (if {} 0 1))\n",
|
|
expression
|
|
),
|
|
);
|
|
let output = run_glagol([fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
!output.status.success(),
|
|
"compiler unexpectedly accepted mixed numeric operands\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"rejected compile wrote stdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("numeric operands must have the same primitive type")
|
|
&& stderr.contains("mixed i32/i64/u32/u64/f64")
|
|
&& stderr.contains(found),
|
|
"mixed numeric diagnostic drifted for {}\nstderr:\n{}",
|
|
name,
|
|
stderr
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn i64_runtime_print_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping i64 runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture =
|
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/i64-numeric-primitive.slo");
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile i64 runtime smoke", &compile);
|
|
let run = compile_and_run_with_runtime(&clang, "i64-runtime-smoke", &compile.stdout);
|
|
assert_success("run i64 runtime smoke", &run);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stdout),
|
|
"4294967289\n",
|
|
"i64 runtime print stdout drifted"
|
|
);
|
|
}
|
|
|
|
fn run_glagol<I, S>(args: I) -> Output
|
|
where
|
|
I: IntoIterator<Item = S>,
|
|
S: AsRef<OsStr>,
|
|
{
|
|
Command::new(env!("CARGO_BIN_EXE_glagol"))
|
|
.args(args)
|
|
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
|
|
.output()
|
|
.expect("run glagol")
|
|
}
|
|
|
|
fn write_fixture(name: &str, source: &str) -> PathBuf {
|
|
let mut path = env::temp_dir();
|
|
path.push(format!(
|
|
"glagol-i64-alpha-{}-{}-{}.slo",
|
|
name,
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
fs::write(&path, source).unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
|
|
path
|
|
}
|
|
|
|
fn assert_success(context: &str, output: &Output) {
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
assert!(
|
|
output.status.success(),
|
|
"{} failed\nstdout:\n{}\nstderr:\n{}",
|
|
context,
|
|
stdout,
|
|
stderr
|
|
);
|
|
}
|
|
|
|
fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output {
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let temp_dir = env::temp_dir().join(format!(
|
|
"glagol-i64-alpha-{}-{}",
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
fs::create_dir_all(&temp_dir)
|
|
.unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));
|
|
|
|
let ir_path = temp_dir.join(format!("{}.ll", name));
|
|
let exe_path = temp_dir.join(name);
|
|
fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err));
|
|
|
|
let runtime = manifest.join("../runtime/runtime.c");
|
|
let mut clang_command = Command::new(clang);
|
|
clang_command
|
|
.arg(&runtime)
|
|
.arg(&ir_path)
|
|
.arg("-o")
|
|
.arg(&exe_path)
|
|
.current_dir(manifest);
|
|
configure_clang_runtime_env(&mut clang_command, clang);
|
|
let clang_output = clang_command
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
|
|
assert_success("clang i64 runtime smoke", &clang_output);
|
|
|
|
Command::new(&exe_path)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err))
|
|
}
|
|
|
|
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);
|
|
}
|