313 lines
11 KiB
Rust
313 lines
11 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_f64_result_fixture_lowers_and_runs_tests() {
|
|
let fixture =
|
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-f64-result.slo");
|
|
|
|
let llvm = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile string-parse-f64-result fixture", &llvm);
|
|
let stdout = String::from_utf8_lossy(&llvm.stdout);
|
|
assert!(
|
|
stdout.contains("declare i32 @__glagol_string_parse_f64_result(ptr, ptr)")
|
|
&& stdout.contains("define { i1, double, i32 } @parse_f64(ptr %text)")
|
|
&& stdout.contains("alloca double")
|
|
&& stdout.contains("store double 0.0, ptr %")
|
|
&& stdout.contains("call i32 @__glagol_string_parse_f64_result(ptr %text, ptr %")
|
|
&& stdout.contains("load double, ptr %")
|
|
&& stdout.contains("insertvalue { i1, double, i32 }")
|
|
&& stdout.contains("extractvalue { i1, double, i32 }")
|
|
&& !stdout.contains("@std.string.parse_f64_result"),
|
|
"string-parse-f64-result LLVM shape drifted\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
|
|
let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
assert_success("run string-parse-f64-result fixture tests", &tests);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&tests.stdout),
|
|
concat!(
|
|
"test \"parse f64 decimal ok\" ... ok\n",
|
|
"test \"parse f64 negative decimal ok\" ... ok\n",
|
|
"test \"parse f64 text err\" ... ok\n",
|
|
"test \"parse f64 nan err\" ... ok\n",
|
|
"4 test(s) passed\n",
|
|
),
|
|
"string-parse-f64-result test runner stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_parse_f64_result_test_runner_covers_decimal_boundaries() {
|
|
let source = r#"
|
|
(module main)
|
|
|
|
(test "parse f64 decimal ok"
|
|
(= (std.result.unwrap_ok (std.string.parse_f64_result "3.5")) 3.5))
|
|
|
|
(test "parse f64 negative decimal ok"
|
|
(= (std.result.unwrap_ok (std.string.parse_f64_result "-3.5")) (- 0.0 3.5)))
|
|
|
|
(test "parse f64 signed plus err one"
|
|
(= (std.result.unwrap_err (std.string.parse_f64_result "+3.5")) 1))
|
|
|
|
(test "parse f64 leading dot err one"
|
|
(= (std.result.unwrap_err (std.string.parse_f64_result ".5")) 1))
|
|
|
|
(test "parse f64 trailing dot err one"
|
|
(= (std.result.unwrap_err (std.string.parse_f64_result "5.")) 1))
|
|
|
|
(test "parse f64 exponent err one"
|
|
(= (std.result.unwrap_err (std.string.parse_f64_result "1e2")) 1))
|
|
|
|
(test "parse f64 nan err one"
|
|
(= (std.result.unwrap_err (std.string.parse_f64_result "nan")) 1))
|
|
|
|
(test "parse f64 infinity err one"
|
|
(= (std.result.unwrap_err (std.string.parse_f64_result "inf")) 1))
|
|
|
|
(test "parse f64 leading whitespace err one"
|
|
(= (std.result.unwrap_err (std.string.parse_f64_result " 1.0")) 1))
|
|
|
|
(test "parse f64 underscore err one"
|
|
(= (std.result.unwrap_err (std.string.parse_f64_result "1_0")) 1))
|
|
|
|
(test "parse f64 hex err one"
|
|
(= (std.result.unwrap_err (std.string.parse_f64_result "0x1.0p0")) 1))
|
|
"#;
|
|
let fixture = write_fixture("boundaries", source);
|
|
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
assert_success("run string parse f64 boundary tests", &output);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&output.stdout),
|
|
concat!(
|
|
"test \"parse f64 decimal ok\" ... ok\n",
|
|
"test \"parse f64 negative decimal ok\" ... ok\n",
|
|
"test \"parse f64 signed plus err one\" ... ok\n",
|
|
"test \"parse f64 leading dot err one\" ... ok\n",
|
|
"test \"parse f64 trailing dot err one\" ... ok\n",
|
|
"test \"parse f64 exponent err one\" ... ok\n",
|
|
"test \"parse f64 nan err one\" ... ok\n",
|
|
"test \"parse f64 infinity err one\" ... ok\n",
|
|
"test \"parse f64 leading whitespace err one\" ... ok\n",
|
|
"test \"parse f64 underscore err one\" ... ok\n",
|
|
"test \"parse f64 hex err one\" ... ok\n",
|
|
"11 test(s) passed\n",
|
|
),
|
|
"string parse f64 boundary test stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_parse_f64_result_hosted_runtime_parses_decimal_when_clang_is_available() {
|
|
let fixture = write_fixture(
|
|
"runtime-smoke",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_string (std.num.f64_to_string (std.result.unwrap_ok (std.string.parse_f64_result "-0.5"))))
|
|
(std.io.print_string (std.num.f64_to_string (std.result.unwrap_ok (std.string.parse_f64_result "12.5"))))
|
|
(std.result.unwrap_err (std.string.parse_f64_result "inf")))
|
|
"#,
|
|
);
|
|
let binary = unique_path("string-parse-f64-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-f64-result build wrote stdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("ToolchainUnavailable"),
|
|
"string-parse-f64-result build failed unexpectedly\nstderr:\n{}",
|
|
stderr
|
|
);
|
|
return;
|
|
}
|
|
|
|
let run = Command::new(&binary)
|
|
.output()
|
|
.expect("run string-parse-f64-result binary");
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(1),
|
|
"string-parse-f64-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),
|
|
"-0.5\n12.5\n",
|
|
"string-parse-f64-result binary stdout drifted"
|
|
);
|
|
assert!(
|
|
run.stderr.is_empty(),
|
|
"string-parse-f64-result binary wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_parse_f64_result_formatter_and_lowering_are_visible() {
|
|
let fixture =
|
|
Path::new(env!("CARGO_MANIFEST_DIR")).join("../tests/string-parse-f64-result.slo");
|
|
|
|
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
assert_success("format string-parse-f64-result fixture", &formatted);
|
|
let formatted_stdout = String::from_utf8_lossy(&formatted.stdout);
|
|
assert!(
|
|
formatted_stdout.contains("(std.string.parse_f64_result text)")
|
|
&& formatted_stdout.contains("(std.result.unwrap_ok value)")
|
|
&& formatted_stdout.contains("(std.result.is_err value)"),
|
|
"string-parse-f64-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-f64-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-f64-result.surface.lower")
|
|
)
|
|
.expect("read string-parse-f64-result surface snapshot"),
|
|
"string-parse-f64-result surface lowering snapshot drifted"
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
OsStr::new("--inspect-lowering=checked"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect string-parse-f64-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-f64-result.checked.lower")
|
|
)
|
|
.expect("read string-parse-f64-result checked snapshot"),
|
|
"string-parse-f64-result checked lowering snapshot drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn string_parse_f64_result_rejections_are_explicit() {
|
|
for (name, source, expected) in [
|
|
(
|
|
"arity",
|
|
"(module main)\n\n(fn main () -> (result f64 i32)\n (std.string.parse_f64_result))\n",
|
|
"wrong number of arguments",
|
|
),
|
|
(
|
|
"type",
|
|
"(module main)\n\n(fn main () -> (result f64 i32)\n (std.string.parse_f64_result 1))\n",
|
|
"cannot call `std.string.parse_f64_result` with argument of wrong type",
|
|
),
|
|
(
|
|
"trap-parse-f64",
|
|
"(module main)\n\n(fn main () -> i32\n (std.string.parse_f64 \"1.0\")\n 0)\n",
|
|
"standard library call `std.string.parse_f64` is not supported",
|
|
),
|
|
(
|
|
"generic-parse",
|
|
"(module main)\n\n(fn main () -> i32\n (std.string.parse_result \"1.0\")\n 0)\n",
|
|
"standard library call `std.string.parse_result` 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-f64 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-f64 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-f64-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
|
|
);
|
|
}
|