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

236 lines
8.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 integer_to_string_fixture_lowers_and_runs_tests() {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.slo");
let llvm = run_glagol([fixture.as_os_str()]);
assert_success("compile integer-to-string fixture", &llvm);
let stdout = String::from_utf8_lossy(&llvm.stdout);
assert!(
stdout.contains("declare ptr @__glagol_num_i32_to_string(i32)")
&& stdout.contains("declare ptr @__glagol_num_i64_to_string(i64)")
&& stdout.contains("call ptr @__glagol_num_i32_to_string(i32 0)")
&& stdout.contains("call ptr @__glagol_num_i32_to_string(i32 -7)")
&& stdout.contains("call ptr @__glagol_num_i64_to_string(i64 -9223372036854775808)")
&& stdout.contains("call ptr @__glagol_num_i64_to_string(i64 9223372036854775807)")
&& stdout.contains("call void @print_string(ptr %")
&& !stdout.contains("@std.num."),
"integer-to-string LLVM shape drifted\nstdout:\n{}",
stdout
);
let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
assert_success("run integer-to-string fixture tests", &tests);
assert_eq!(
String::from_utf8_lossy(&tests.stdout),
concat!(
"test \"i32 zero to string\" ... ok\n",
"test \"i32 negative to string\" ... ok\n",
"test \"i32 high to string\" ... ok\n",
"test \"i32 negative string length\" ... ok\n",
"test \"i64 low to string\" ... ok\n",
"test \"i64 high to string\" ... ok\n",
"test \"i64 beyond i32 to string\" ... ok\n",
"test \"i64 low string length\" ... ok\n",
"8 test(s) passed\n",
),
"integer-to-string test runner stdout drifted"
);
}
#[test]
fn integer_to_string_hosted_runtime_formats_bounds_when_clang_is_available() {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.slo");
let binary = unique_path("integer-to-string-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 integer-to-string build wrote stdout:\n{}",
stdout
);
assert!(
stderr.contains("ToolchainUnavailable"),
"integer-to-string build failed unexpectedly\nstderr:\n{}",
stderr
);
return;
}
let run = Command::new(&binary)
.output()
.expect("run integer-to-string binary");
assert_success("run integer-to-string binary", &run);
assert_eq!(
String::from_utf8_lossy(&run.stdout),
"0\n-7\n2147483647\n-9223372036854775808\n9223372036854775807\n2147483648\n",
"integer-to-string binary stdout drifted"
);
assert!(
run.stderr.is_empty(),
"integer-to-string binary wrote stderr:\n{}",
String::from_utf8_lossy(&run.stderr)
);
}
#[test]
fn integer_to_string_formatter_and_lowering_are_visible() {
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.slo");
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
assert_success("format integer-to-string fixture", &formatted);
let formatted_stdout = String::from_utf8_lossy(&formatted.stdout);
assert!(
formatted_stdout.contains("(std.num.i32_to_string -7)")
&& formatted_stdout.contains("(std.num.i64_to_string -9223372036854775808i64)")
&& formatted_stdout.contains("(std.string.len (i64_low_text))"),
"integer-to-string 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 integer-to-string surface lowering", &surface);
assert_eq!(
String::from_utf8_lossy(&surface.stdout),
fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.surface.lower")
)
.expect("read integer-to-string surface snapshot"),
"integer-to-string surface lowering snapshot drifted"
);
let checked = run_glagol([
OsStr::new("--inspect-lowering=checked"),
fixture.as_os_str(),
]);
assert_success("inspect integer-to-string checked lowering", &checked);
assert_eq!(
String::from_utf8_lossy(&checked.stdout),
fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/integer-to-string.checked.lower")
)
.expect("read integer-to-string checked snapshot"),
"integer-to-string checked lowering snapshot drifted"
);
}
#[test]
fn integer_to_string_rejections_are_explicit() {
for (name, source, expected) in [
(
"i32-arity",
"(module main)\n\n(fn main () -> string\n (std.num.i32_to_string))\n",
"wrong number of arguments",
),
(
"i64-type",
"(module main)\n\n(fn main () -> string\n (std.num.i64_to_string 1))\n",
"cannot call `std.num.i64_to_string` with argument of wrong type",
),
(
"generic-to-string",
"(module main)\n\n(fn main () -> i32\n (std.num.to_string 1)\n 0)\n",
"standard library call `std.num.to_string` is not supported",
),
(
"string-from-i64",
"(module main)\n\n(fn main () -> i32\n (std.string.from_i64 1i64)\n 0)\n",
"standard library call `std.string.from_i64` 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 integer-to-string 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),
"integer-to-string 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-integer-to-string-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(name: &str) -> PathBuf {
let mut path = env::temp_dir();
path.push(format!(
"glagol-{}-{}-{}",
name,
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
);
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
}