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

307 lines
9.9 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 f64_to_i64_result_fixture_lowers_and_runs_tests() {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i64-result.slo");
let llvm = run_glagol([fixture.as_os_str()]);
assert_success("compile f64-to-i64-result fixture", &llvm);
let stdout = String::from_utf8_lossy(&llvm.stdout);
assert!(
stdout.contains("fcmp oge double")
&& stdout.contains("-9223372036854775808.0")
&& stdout.contains("fcmp olt double")
&& stdout.contains("9223372036854775808.0")
&& stdout.contains("br i1 %")
&& stdout.contains("f64.to_i64.integral")
&& stdout.contains("f64.to_i64.exponent")
&& stdout.contains("f64.to_i64.mantissa")
&& stdout.contains("f64.to_i64.fraction")
&& stdout.contains("bitcast double")
&& stdout.contains("lshr i64")
&& stdout.contains("icmp uge i64")
&& stdout.contains("fptosi double")
&& stdout.contains("to i64")
&& !stdout.contains("frem double")
&& stdout.contains("phi { i1, i64, i32 }")
&& !stdout.contains("@std.num.f64_to_i64_result")
&& !stdout.contains("__glagol_num_f64_to_i64_result"),
"f64-to-i64-result LLVM shape drifted\nstdout:\n{}",
stdout
);
let integral_pos = stdout
.find("f64.to_i64.integral")
.expect("integral check block is present");
let fraction_pos = stdout
.find("f64.to_i64.fraction")
.expect("fraction bit check block is present");
let fptosi_pos = stdout
.find("fptosi double")
.expect("narrowing conversion is present");
assert!(
integral_pos < fraction_pos && fraction_pos < fptosi_pos,
"f64-to-i64-result must prove integrality before fptosi\nstdout:\n{}",
stdout
);
let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
assert_success("run f64-to-i64-result fixture tests", &tests);
assert_eq!(
String::from_utf8_lossy(&tests.stdout),
concat!(
"test \"f64 zero narrows to i64\" ... ok\n",
"test \"negative f64 narrows to i64\" ... ok\n",
"test \"fractional f64 returns err for i64\" ... ok\n",
"test \"above i64 range f64 returns err\" ... ok\n",
"4 test(s) passed\n",
),
"f64-to-i64-result test runner stdout drifted"
);
}
#[test]
fn f64_to_i64_result_non_finite_returns_err_in_test_runner() {
let fixture = write_fixture(
"non-finite",
r#"
(module main)
(fn main () -> i32
0)
(test "infinity returns err for i64"
(let value (result i64 i32) (std.num.f64_to_i64_result (/ 1.0 0.0)))
(if (std.result.is_err value)
(= (std.result.unwrap_err value) 1)
false))
"#,
);
let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
assert_success("run f64-to-i64-result non-finite test", &tests);
assert_eq!(
String::from_utf8_lossy(&tests.stdout),
concat!(
"test \"infinity returns err for i64\" ... ok\n",
"1 test(s) passed\n",
),
"f64-to-i64-result non-finite test runner stdout drifted"
);
}
#[test]
fn f64_to_i64_result_non_finite_returns_err_in_hosted_runtime_when_available() {
let fixture = write_fixture(
"runtime-non-finite",
r#"
(module main)
(fn main () -> i32
(let value (result i64 i32) (std.num.f64_to_i64_result (/ 1.0 0.0)))
(if (std.result.is_err value)
(std.result.unwrap_err value)
99))
"#,
);
let binary = unique_path("f64-to-i64-result-bin");
let build = run_glagol([
OsStr::new("build"),
fixture.as_os_str(),
OsStr::new("-o"),
binary.as_os_str(),
]);
if !build.status.success() {
let stdout = String::from_utf8_lossy(&build.stdout);
let stderr = String::from_utf8_lossy(&build.stderr);
assert!(
stdout.is_empty(),
"failed f64-to-i64-result build wrote stdout:\n{}",
stdout
);
assert!(
stderr.contains("ToolchainUnavailable"),
"f64-to-i64-result build failed unexpectedly\nstderr:\n{}",
stderr
);
return;
}
let run = Command::new(&binary)
.output()
.expect("run f64-to-i64-result binary");
assert_eq!(
run.status.code(),
Some(1),
"f64-to-i64-result binary exit code drifted\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&run.stdout),
String::from_utf8_lossy(&run.stderr)
);
assert!(
run.stdout.is_empty(),
"f64-to-i64-result binary wrote stdout:\n{}",
String::from_utf8_lossy(&run.stdout)
);
assert!(
run.stderr.is_empty(),
"f64-to-i64-result binary wrote stderr:\n{}",
String::from_utf8_lossy(&run.stderr)
);
}
#[test]
fn f64_to_i64_result_formatter_and_lowering_are_visible() {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i64-result.slo");
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
assert_success("format f64-to-i64-result fixture", &formatted);
let formatted_stdout = String::from_utf8_lossy(&formatted.stdout);
assert!(
formatted_stdout.contains("(std.num.f64_to_i64_result value)")
&& formatted_stdout.contains("(narrow 9223372036854776000.0)")
&& formatted_stdout.contains("(std.result.unwrap_err value)"),
"f64-to-i64-result formatter output omitted expected calls\nstdout:\n{}",
formatted_stdout
);
let surface = run_glagol([
OsStr::new("--inspect-lowering=surface"),
fixture.as_os_str(),
]);
assert_success("inspect f64-to-i64-result surface lowering", &surface);
assert_eq!(
String::from_utf8_lossy(&surface.stdout),
fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i64-result.surface.lower")
)
.expect("read f64-to-i64-result surface snapshot"),
"f64-to-i64-result surface lowering snapshot drifted"
);
let checked = run_glagol([
OsStr::new("--inspect-lowering=checked"),
fixture.as_os_str(),
]);
assert_success("inspect f64-to-i64-result checked lowering", &checked);
assert_eq!(
String::from_utf8_lossy(&checked.stdout),
fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/f64-to-i64-result.checked.lower")
)
.expect("read f64-to-i64-result checked snapshot"),
"f64-to-i64-result checked lowering snapshot drifted"
);
}
#[test]
fn f64_to_i64_result_rejections_are_explicit() {
for (name, source, expected) in [
(
"arity",
"(module main)\n\n(fn main () -> (result i64 i32)\n (std.num.f64_to_i64_result))\n",
"wrong number of arguments",
),
(
"type",
"(module main)\n\n(fn main () -> (result i64 i32)\n (std.num.f64_to_i64_result 1))\n",
"cannot call `std.num.f64_to_i64_result` with argument of wrong type",
),
(
"context",
"(module main)\n\n(fn main () -> i32\n (std.num.f64_to_i64_result 1.0))\n",
"function `main` returns wrong type",
),
(
"unchecked",
"(module main)\n\n(fn main () -> i32\n (std.num.f64_to_i64 1.0)\n 0)\n",
"standard library call `std.num.f64_to_i64` is not supported",
),
(
"cast-checked",
"(module main)\n\n(fn main () -> i32\n (std.num.cast_checked 1.0)\n 0)\n",
"standard library call `std.num.cast_checked` is not supported",
),
] {
let fixture = write_fixture(name, source);
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 f64-to-i64-result rejection fixture `{}`\nstdout:\n{}\nstderr:\n{}",
name,
stdout,
stderr
);
assert!(
stdout.is_empty(),
"rejected compile wrote stdout for `{}`:\n{}",
name,
stdout
);
assert!(
stderr.contains(expected),
"f64-to-i64-result diagnostic drifted for `{}`\nstderr:\n{}",
name,
stderr
);
}
}
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-f64-to-i64-result-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 unique_path(stem: &str) -> PathBuf {
let mut path = env::temp_dir();
path.push(format!(
"glagol-{}-{}-{}",
stem,
std::process::id(),
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
));
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
);
}