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

288 lines
9.6 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 string_parse_u64_result_fixture_lowers_and_runs_tests() {
let fixture =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-u64-result.slo");
let llvm = run_glagol([fixture.as_os_str()]);
assert_success("compile string-parse-u64-result fixture", &llvm);
let stdout = String::from_utf8_lossy(&llvm.stdout);
assert!(
stdout.contains("declare i32 @__glagol_string_parse_u64_result(ptr, ptr)")
&& stdout.contains("define { i1, i64, i32 } @parse_u64(ptr %text)")
&& stdout.contains("alloca i64")
&& stdout.contains("call i32 @__glagol_string_parse_u64_result(ptr %text, ptr %")
&& stdout.contains("load i64, ptr %")
&& stdout.contains("insertvalue { i1, i64, i32 }")
&& stdout.contains("extractvalue { i1, i64, i32 }")
&& !stdout.contains("@std.string.parse_u64_result"),
"string-parse-u64-result LLVM shape drifted\nstdout:\n{}",
stdout
);
let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
assert_success("run string-parse-u64-result fixture tests", &tests);
assert_eq!(
String::from_utf8_lossy(&tests.stdout),
concat!(
"test \"parse u64 zero ok\" ... ok\n",
"test \"parse u64 high ok\" ... ok\n",
"test \"parse u64 empty err\" ... ok\n",
"test \"parse u64 plus err\" ... ok\n",
"test \"parse u64 negative err\" ... ok\n",
"test \"parse u64 above range err\" ... ok\n",
"6 test(s) passed\n",
),
"string-parse-u64-result test runner stdout drifted"
);
}
#[test]
fn string_parse_u64_result_test_runner_covers_boundaries() {
let source = r#"
(module main)
(test "parse ok max u64"
(= (std.result.unwrap_ok (std.string.parse_u64_result "18446744073709551615")) 18446744073709551615u64))
(test "parse lone minus err one"
(= (std.result.unwrap_err (std.string.parse_u64_result "-")) 1))
(test "parse leading whitespace err one"
(= (std.result.unwrap_err (std.string.parse_u64_result " 1")) 1))
(test "parse trailing byte err one"
(= (std.result.unwrap_err (std.string.parse_u64_result "42x")) 1))
(test "parse underscore err one"
(= (std.result.unwrap_err (std.string.parse_u64_result "1_0")) 1))
(test "parse prefix err one"
(= (std.result.unwrap_err (std.string.parse_u64_result "0x10")) 1))
"#;
let fixture = write_fixture("boundaries", source);
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
assert_success("run string parse u64 boundary tests", &output);
assert_eq!(
String::from_utf8_lossy(&output.stdout),
concat!(
"test \"parse ok max u64\" ... ok\n",
"test \"parse lone minus err one\" ... ok\n",
"test \"parse leading whitespace err one\" ... ok\n",
"test \"parse trailing byte err one\" ... ok\n",
"test \"parse underscore err one\" ... ok\n",
"test \"parse prefix err one\" ... ok\n",
"6 test(s) passed\n",
),
"string parse u64 boundary test stdout drifted"
);
}
#[test]
fn string_parse_u64_result_hosted_runtime_parses_bounds_when_toolchain_is_available() {
let fixture = write_fixture(
"runtime-smoke",
r#"
(module main)
(fn main () -> i32
(std.io.print_u64 (std.result.unwrap_ok (std.string.parse_u64_result "18446744073709551615")))
(std.result.unwrap_err (std.string.parse_u64_result "+1")))
"#,
);
let binary = unique_path("string-parse-u64-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 string-parse-u64-result build wrote stdout:\n{}",
stdout
);
assert!(
stderr.contains("ToolchainUnavailable"),
"string-parse-u64-result build failed unexpectedly\nstderr:\n{}",
stderr
);
return;
}
let run = Command::new(&binary)
.output()
.expect("run string-parse-u64-result binary");
assert_eq!(
run.status.code(),
Some(1),
"string-parse-u64-result binary exit code drifted\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&run.stdout),
String::from_utf8_lossy(&run.stderr)
);
assert_eq!(
String::from_utf8_lossy(&run.stdout),
"18446744073709551615\n",
"string-parse-u64-result binary stdout drifted"
);
assert!(
run.stderr.is_empty(),
"string-parse-u64-result binary wrote stderr:\n{}",
String::from_utf8_lossy(&run.stderr)
);
}
#[test]
fn string_parse_u64_result_formatter_and_lowering_are_visible() {
let fixture =
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-u64-result.slo");
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
assert_success("format string-parse-u64-result fixture", &formatted);
let formatted_stdout = String::from_utf8_lossy(&formatted.stdout);
assert!(
formatted_stdout.contains("(std.string.parse_u64_result text)")
&& formatted_stdout
.contains("(std.num.u64_to_string (std.result.unwrap_ok (parse_u64 text)))"),
"string-parse-u64-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 string-parse-u64-result surface lowering", &surface);
assert_eq!(
String::from_utf8_lossy(&surface.stdout),
fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../tests/string-parse-u64-result.surface.lower"),
)
.expect("read string-parse-u64-result surface snapshot"),
"string-parse-u64-result surface lowering snapshot drifted"
);
let checked = run_glagol([
OsStr::new("--inspect-lowering=checked"),
fixture.as_os_str(),
]);
assert_success("inspect string-parse-u64-result checked lowering", &checked);
assert_eq!(
String::from_utf8_lossy(&checked.stdout),
fs::read_to_string(
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("../tests/string-parse-u64-result.checked.lower"),
)
.expect("read string-parse-u64-result checked snapshot"),
"string-parse-u64-result checked lowering snapshot drifted"
);
}
#[test]
fn string_parse_u64_result_rejections_are_explicit() {
for (name, source, expected) in [
(
"arity",
"(module main)\n\n(fn main () -> (result u64 i32)\n (std.string.parse_u64_result))\n",
"wrong number of arguments",
),
(
"type",
"(module main)\n\n(fn main () -> (result u64 i32)\n (std.string.parse_u64_result 1u64))\n",
"cannot call `std.string.parse_u64_result` with argument of wrong type",
),
(
"trap-parse-u64",
"(module main)\n\n(fn main () -> i32\n (std.string.parse_u64 \"42\")\n 0)\n",
"standard library call `std.string.parse_u64` 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 string-parse-u64 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),
"string-parse-u64 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-string-parse-u64-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(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
);
}