307 lines
12 KiB
Rust
307 lines
12 KiB
Rust
use std::{
|
|
fs,
|
|
path::Path,
|
|
process::{Command, Output},
|
|
};
|
|
|
|
#[test]
|
|
fn direct_scalar_array_fixture_emits_direct_scalar_llvm_shapes() {
|
|
let output = run_glagol(["../examples/array-direct-scalars.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"compiler rejected direct-scalar array fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("define i32 @i32_second()")
|
|
&& stdout.contains("define i64 @i64_local_pick()")
|
|
&& stdout.contains("define double @f64_third()")
|
|
&& stdout.contains("define i1 @bool_local_pick()")
|
|
&& stdout.contains("insertvalue [3 x i32]")
|
|
&& stdout.contains("insertvalue [3 x double]")
|
|
&& stdout.contains("alloca [3 x i64]")
|
|
&& stdout.contains("alloca [3 x i1]"),
|
|
"LLVM output did not contain the widened direct-scalar array shapes\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
!stdout.contains("i32 direct scalar array index")
|
|
&& !stdout.contains("bool local direct scalar array index"),
|
|
"compiler emitted test metadata into LLVM output\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn direct_scalar_array_fixture_runs_tests_and_formats_stably() {
|
|
let run = run_glagol(["--run-tests", "../examples/array-direct-scalars.slo"]);
|
|
assert_success_stdout(
|
|
run,
|
|
concat!(
|
|
"test \"i32 direct scalar array index\" ... ok\n",
|
|
"test \"i64 local direct scalar array index\" ... ok\n",
|
|
"test \"f64 direct scalar array index\" ... ok\n",
|
|
"test \"bool local direct scalar array index\" ... ok\n",
|
|
"4 test(s) passed\n",
|
|
),
|
|
"direct-scalar array test runner output",
|
|
);
|
|
|
|
let expected = fs::read_to_string("../tests/array-direct-scalars.slo").expect("read fixture");
|
|
let format = run_glagol(["--format", "../tests/array-direct-scalars.slo"]);
|
|
assert_success_stdout(format, &expected, "direct-scalar array formatter output");
|
|
}
|
|
|
|
#[test]
|
|
fn direct_scalar_array_fixture_prints_lowered_shape() {
|
|
let surface = run_glagol([
|
|
"--inspect-lowering=surface",
|
|
"../examples/array-direct-scalars.slo",
|
|
]);
|
|
let surface_stdout = String::from_utf8_lossy(&surface.stdout);
|
|
let surface_stderr = String::from_utf8_lossy(&surface.stderr);
|
|
assert!(
|
|
surface.status.success(),
|
|
"surface lowering rejected direct-scalar array fixture\nstdout:\n{}\nstderr:\n{}",
|
|
surface_stdout,
|
|
surface_stderr
|
|
);
|
|
assert!(
|
|
surface_stdout.contains("array i64")
|
|
&& surface_stdout.contains("array f64")
|
|
&& surface_stdout.contains("array bool")
|
|
&& surface_stdout.contains("local let values: (array i64 3)")
|
|
&& surface_stdout.contains("local let flags: (array bool 3)"),
|
|
"surface lowering output lost direct-scalar array shape\nstdout:\n{}",
|
|
surface_stdout
|
|
);
|
|
assert!(
|
|
surface_stderr.is_empty(),
|
|
"surface lowering wrote stderr:\n{}",
|
|
surface_stderr
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
"--inspect-lowering=checked",
|
|
"../examples/array-direct-scalars.slo",
|
|
]);
|
|
let checked_stdout = String::from_utf8_lossy(&checked.stdout);
|
|
let checked_stderr = String::from_utf8_lossy(&checked.stderr);
|
|
assert!(
|
|
checked.status.success(),
|
|
"checked lowering rejected direct-scalar array fixture\nstdout:\n{}\nstderr:\n{}",
|
|
checked_stdout,
|
|
checked_stderr
|
|
);
|
|
assert!(
|
|
checked_stdout.contains("fn i32_second() -> i32")
|
|
&& checked_stdout.contains("fn i64_local_pick() -> i64")
|
|
&& checked_stdout.contains("fn f64_third() -> f64")
|
|
&& checked_stdout.contains("fn bool_local_pick() -> bool")
|
|
&& checked_stdout.contains("index : i32")
|
|
&& checked_stdout.contains("index : i64")
|
|
&& checked_stdout.contains("index : f64")
|
|
&& checked_stdout.contains("index : bool"),
|
|
"checked lowering output lost typed direct-scalar array shape\nstdout:\n{}",
|
|
checked_stdout
|
|
);
|
|
assert!(
|
|
checked_stderr.is_empty(),
|
|
"checked lowering wrote stderr:\n{}",
|
|
checked_stderr
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn direct_scalar_array_value_flow_fixture_emits_llvm_value_flow_and_bounds_shape() {
|
|
let output = run_glagol(["../examples/array-direct-scalars-value-flow.slo"]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
output.status.success(),
|
|
"compiler rejected direct-scalar array value-flow fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("define [3 x i32] @make_i32_values(i32 %base)")
|
|
&& stdout.contains("define [3 x i64] @make_i64_values(i64 %base)")
|
|
&& stdout.contains("define [3 x double] @make_f64_values(double %base)")
|
|
&& stdout.contains("define [3 x i1] @make_flags(i1 %first)")
|
|
&& stdout.contains("define i32 @i32_at([3 x i32] %values, i32 %i)")
|
|
&& stdout.contains("define i64 @i64_at([3 x i64] %values, i32 %i)")
|
|
&& stdout.contains("define double @f64_at([3 x double] %values, i32 %i)")
|
|
&& stdout.contains("define [3 x i1] @echo_flags([3 x i1] %values)")
|
|
&& stdout.contains("call void @__glagol_array_bounds_trap()")
|
|
&& stdout.contains("array.index.trap")
|
|
&& stdout.contains("getelementptr inbounds [3 x i64]")
|
|
&& stdout.contains("getelementptr inbounds [3 x double]"),
|
|
"LLVM output did not contain expected direct-scalar array value-flow shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
let dynamic_index_function =
|
|
llvm_function(&stdout, "define i64 @i64_at([3 x i64] %values, i32 %i)");
|
|
assert_contains_in_order(
|
|
dynamic_index_function,
|
|
&[
|
|
"icmp sge i32 %i, 0",
|
|
"icmp slt i32 %i, 3",
|
|
"array.index.trap",
|
|
"call void @__glagol_array_bounds_trap()",
|
|
"array.index.ok",
|
|
"getelementptr inbounds [3 x i64]",
|
|
"load i64, ptr %",
|
|
],
|
|
"LLVM output did not emit bounds-check shape before direct-scalar array indexing",
|
|
);
|
|
assert!(
|
|
!stdout.contains("i64 array local call value flow")
|
|
&& !stdout.contains("bool array dynamic index"),
|
|
"compiler emitted test metadata into LLVM output\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn direct_scalar_array_value_flow_fixture_runs_tests_and_formats_stably() {
|
|
let run = run_glagol([
|
|
"--run-tests",
|
|
"../examples/array-direct-scalars-value-flow.slo",
|
|
]);
|
|
assert_success_stdout(
|
|
run,
|
|
concat!(
|
|
"test \"i32 array parameter value flow\" ... ok\n",
|
|
"test \"i64 array local call value flow\" ... ok\n",
|
|
"test \"f64 array parameter local copy\" ... ok\n",
|
|
"test \"bool array dynamic index\" ... ok\n",
|
|
"test \"bool array return call value flow\" ... ok\n",
|
|
"5 test(s) passed\n",
|
|
),
|
|
"direct-scalar array value-flow test runner output",
|
|
);
|
|
|
|
let expected =
|
|
fs::read_to_string("../tests/array-direct-scalars-value-flow.slo").expect("read fixture");
|
|
let format = run_glagol(["--format", "../tests/array-direct-scalars-value-flow.slo"]);
|
|
assert_success_stdout(
|
|
format,
|
|
&expected,
|
|
"direct-scalar array value-flow formatter output",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn direct_scalar_array_value_flow_fixture_prints_lowered_shape() {
|
|
let surface = run_glagol([
|
|
"--inspect-lowering=surface",
|
|
"../examples/array-direct-scalars-value-flow.slo",
|
|
]);
|
|
let surface_stdout = String::from_utf8_lossy(&surface.stdout);
|
|
let surface_stderr = String::from_utf8_lossy(&surface.stderr);
|
|
assert!(
|
|
surface.status.success(),
|
|
"surface lowering rejected direct-scalar array value-flow fixture\nstdout:\n{}\nstderr:\n{}",
|
|
surface_stdout,
|
|
surface_stderr
|
|
);
|
|
assert!(
|
|
surface_stdout.contains("fn make_i32_values(base: i32) -> (array i32 3)")
|
|
&& surface_stdout.contains("fn make_i64_values(base: i64) -> (array i64 3)")
|
|
&& surface_stdout.contains("fn make_f64_values(base: f64) -> (array f64 3)")
|
|
&& surface_stdout.contains("fn make_flags(first: bool) -> (array bool 3)")
|
|
&& surface_stdout.contains("fn echo_flags(values: (array bool 3)) -> (array bool 3)")
|
|
&& surface_stdout.contains("local let copy: (array f64 3)"),
|
|
"surface lowering output lost direct-scalar array value-flow shape\nstdout:\n{}",
|
|
surface_stdout
|
|
);
|
|
assert!(
|
|
surface_stderr.is_empty(),
|
|
"surface lowering wrote stderr:\n{}",
|
|
surface_stderr
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
"--inspect-lowering=checked",
|
|
"../examples/array-direct-scalars-value-flow.slo",
|
|
]);
|
|
let checked_stdout = String::from_utf8_lossy(&checked.stdout);
|
|
let checked_stderr = String::from_utf8_lossy(&checked.stderr);
|
|
assert!(
|
|
checked.status.success(),
|
|
"checked lowering rejected direct-scalar array value-flow fixture\nstdout:\n{}\nstderr:\n{}",
|
|
checked_stdout,
|
|
checked_stderr
|
|
);
|
|
assert!(
|
|
checked_stdout.contains("fn make_i32_values(base: i32) -> (array i32 3)")
|
|
&& checked_stdout.contains("fn make_i64_values(base: i64) -> (array i64 3)")
|
|
&& checked_stdout.contains("fn f64_at(values: (array f64 3), i: i32) -> f64")
|
|
&& checked_stdout.contains("fn echo_flags(values: (array bool 3)) -> (array bool 3)")
|
|
&& checked_stdout.contains("call make_i64_values : (array i64 3)")
|
|
&& checked_stdout.contains("index : i64")
|
|
&& checked_stdout.contains("index : f64")
|
|
&& checked_stdout.contains("index : bool"),
|
|
"checked lowering output lost typed direct-scalar array value-flow shape\nstdout:\n{}",
|
|
checked_stdout
|
|
);
|
|
assert!(
|
|
checked_stderr.is_empty(),
|
|
"checked lowering wrote stderr:\n{}",
|
|
checked_stderr
|
|
);
|
|
}
|
|
|
|
fn run_glagol<const N: usize>(args: [&str; N]) -> Output {
|
|
Command::new(env!("CARGO_BIN_EXE_glagol"))
|
|
.args(args)
|
|
.current_dir(Path::new(env!("CARGO_MANIFEST_DIR")))
|
|
.output()
|
|
.expect("run glagol")
|
|
}
|
|
|
|
fn assert_success_stdout(output: Output, expected: &str, context: &str) {
|
|
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_eq!(stdout, expected, "{} drifted", context);
|
|
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
|
|
}
|
|
|
|
fn llvm_function<'a>(source: &'a str, signature: &str) -> &'a str {
|
|
let start = source
|
|
.find(signature)
|
|
.unwrap_or_else(|| panic!("LLVM output omitted `{}`\nstdout:\n{}", signature, source));
|
|
let tail = &source[start..];
|
|
let end = tail.find("\n}\n").unwrap_or_else(|| {
|
|
panic!(
|
|
"LLVM function `{}` was not closed\nstdout:\n{}",
|
|
signature, source
|
|
)
|
|
});
|
|
&tail[..end + "\n}\n".len()]
|
|
}
|
|
|
|
fn assert_contains_in_order(source: &str, needles: &[&str], message: &str) {
|
|
let mut cursor = 0;
|
|
for needle in needles {
|
|
let relative = source[cursor..]
|
|
.find(needle)
|
|
.unwrap_or_else(|| panic!("{}\nmissing `{}`\nsource:\n{}", message, needle, source));
|
|
cursor += relative + needle.len();
|
|
}
|
|
}
|