1942 lines
59 KiB
Rust
1942 lines
59 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 vec_i32_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-i32.slo");
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
let stderr = String::from_utf8_lossy(&compile.stderr);
|
|
|
|
assert!(
|
|
compile.status.success(),
|
|
"compiler rejected vec-i32 fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare ptr @__glagol_vec_i32_empty()")
|
|
&& stdout.contains("declare ptr @__glagol_vec_i32_append(ptr, i32)")
|
|
&& stdout.contains("declare i32 @__glagol_vec_i32_len(ptr)")
|
|
&& stdout.contains("declare i32 @__glagol_vec_i32_index(ptr, i32)")
|
|
&& stdout.contains("declare i1 @__glagol_vec_i32_eq(ptr, ptr)")
|
|
&& stdout.contains("define ptr @empty_values()")
|
|
&& stdout.contains("define ptr @pair(i32 %base)")
|
|
&& stdout.contains("define ptr @echo(ptr %values)")
|
|
&& stdout.contains("define i32 @at(ptr %values, i32 %i)")
|
|
&& stdout.contains("call ptr @__glagol_vec_i32_empty()")
|
|
&& stdout.contains("call ptr @__glagol_vec_i32_append(ptr %")
|
|
&& stdout.contains("call i32 @__glagol_vec_i32_len(ptr %")
|
|
&& stdout.contains("call i32 @__glagol_vec_i32_index(ptr %")
|
|
&& !stdout.contains("@std."),
|
|
"LLVM output did not contain expected vec runtime shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
|
|
let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
run,
|
|
concat!(
|
|
"test \"vec i32 empty length\" ... ok\n",
|
|
"test \"vec i32 append length\" ... ok\n",
|
|
"test \"vec i32 index\" ... ok\n",
|
|
"test \"vec i32 append is immutable\" ... ok\n",
|
|
"test \"vec i32 equality\" ... ok\n",
|
|
"5 test(s) passed\n",
|
|
),
|
|
"vec-i32 test runner output",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i32_equality_lowers_to_runtime_helper() {
|
|
let fixture = write_fixture(
|
|
"vec-i32-equality",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn same () -> i32
|
|
(if (= (std.vec.i32.append (std.vec.i32.empty) 1)
|
|
(std.vec.i32.append (std.vec.i32.empty) 1))
|
|
1
|
|
0))
|
|
|
|
(fn main () -> i32
|
|
(same))
|
|
"#,
|
|
);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
let stderr = String::from_utf8_lossy(&compile.stderr);
|
|
|
|
assert!(
|
|
compile.status.success(),
|
|
"compiler rejected vec equality fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("call i1 @__glagol_vec_i32_eq(ptr %"),
|
|
"LLVM output did not lower vec equality through helper\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i32_formatter_and_lowering_inspector_are_visible() {
|
|
let fixture = write_fixture(
|
|
"vec-i32-tooling",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn make () -> (vec i32)
|
|
(let values (vec i32) (std.vec.i32.empty))
|
|
(std.vec.i32.append values 1))
|
|
|
|
(fn main () -> i32
|
|
(std.vec.i32.len (make)))
|
|
"#,
|
|
);
|
|
|
|
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
formatted,
|
|
concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(fn make () -> (vec i32)\n",
|
|
" (let values (vec i32) (std.vec.i32.empty))\n",
|
|
" (std.vec.i32.append values 1))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" (std.vec.i32.len (make)))\n",
|
|
),
|
|
"vec-i32 formatter output",
|
|
);
|
|
|
|
let surface = run_glagol([
|
|
OsStr::new("--inspect-lowering=surface"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-i32 surface lowering", &surface);
|
|
assert!(
|
|
String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec i32)"),
|
|
"surface lowering did not show vec return type\nstdout:\n{}",
|
|
String::from_utf8_lossy(&surface.stdout)
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
OsStr::new("--inspect-lowering=checked"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-i32 checked lowering", &checked);
|
|
assert!(
|
|
String::from_utf8_lossy(&checked.stdout).contains("call std.vec.i32.append : (vec i32)"),
|
|
"checked lowering did not show typed vec append\nstdout:\n{}",
|
|
String::from_utf8_lossy(&checked.stdout)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i32_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-i32 runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-i32.slo");
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec-i32 runtime smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "vec-i32", &compile.stdout);
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(41),
|
|
"vec-i32 runtime 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),
|
|
"2\n",
|
|
"vec-i32 runtime stdout drifted"
|
|
);
|
|
assert!(
|
|
run.stderr.is_empty(),
|
|
"vec-i32 runtime wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i32_runtime_traps_have_contract_messages_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-i32 trap smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let cases = [
|
|
(
|
|
"vec-index-bounds",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.vec.i32.index (std.vec.i32.empty) 0))
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-index-negative",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.vec.i32.index (std.vec.i32.append (std.vec.i32.empty) 1) -1))
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-allocation",
|
|
r#"#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
typedef struct __glagol_vec_i32 __glagol_vec_i32;
|
|
|
|
__glagol_vec_i32 *__glagol_vec_i32_empty(void);
|
|
|
|
void *__wrap_malloc(size_t size) {
|
|
(void)size;
|
|
return NULL;
|
|
}
|
|
|
|
int main(void) {
|
|
(void)__glagol_vec_i32_empty();
|
|
return 0;
|
|
}
|
|
"#,
|
|
"slovo runtime error: vector allocation failed\n",
|
|
),
|
|
];
|
|
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let runtime = manifest.join("../runtime/runtime.c");
|
|
|
|
for (name, source, expected_stderr) in cases {
|
|
let temp_dir = env::temp_dir().join(format!(
|
|
"glagol-vec-runtime-{}-{}",
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
fs::create_dir_all(&temp_dir)
|
|
.unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));
|
|
|
|
let exe = temp_dir.join(name);
|
|
let run = if name.starts_with("vec-index-") {
|
|
let fixture = write_fixture(name, source);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec index trap", &compile);
|
|
compile_and_run_with_runtime(&clang, name, &compile.stdout)
|
|
} else {
|
|
let probe = temp_dir.join(format!("{}.c", name));
|
|
fs::write(&probe, source)
|
|
.unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
|
|
let mut clang_command = Command::new(&clang);
|
|
clang_command
|
|
.arg(&runtime)
|
|
.arg(&probe)
|
|
.arg("-Wl,--wrap=malloc")
|
|
.arg("-o")
|
|
.arg(&exe)
|
|
.current_dir(manifest);
|
|
configure_clang_runtime_env(&mut clang_command, &clang);
|
|
let clang_output = clang_command
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
|
|
assert_success("clang vec allocation probe", &clang_output);
|
|
Command::new(&exe)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
|
|
};
|
|
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(1),
|
|
"trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout),
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stderr),
|
|
expected_stderr,
|
|
"trap `{}` stderr drifted",
|
|
name
|
|
);
|
|
assert!(
|
|
run.stdout.is_empty(),
|
|
"trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i32_test_runner_reports_index_trap() {
|
|
let cases = [
|
|
(
|
|
"vec-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec index trap"
|
|
(= (std.vec.i32.index (std.vec.i32.empty) 0) 0))
|
|
"#,
|
|
),
|
|
(
|
|
"vec-negative-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec negative index trap"
|
|
(= (std.vec.i32.index (std.vec.i32.append (std.vec.i32.empty) 1) -1) 0))
|
|
"#,
|
|
),
|
|
];
|
|
|
|
for (name, source) in cases {
|
|
let fixture = write_fixture(name, source);
|
|
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_eq!(
|
|
output.status.code(),
|
|
Some(1),
|
|
"test runner vec trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"test runner vec trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("TestRuntimeTrap")
|
|
&& stderr.contains("slovo runtime error: vector index out of bounds"),
|
|
"test runner vec trap `{}` diagnostic drifted\nstderr:\n{}",
|
|
name,
|
|
stderr
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i32_project_mode_rewrites_imported_vector_symbols() {
|
|
let project = write_vec_project();
|
|
|
|
let check = run_glagol([OsStr::new("check"), project.as_os_str()]);
|
|
assert_success("check vec-i32 project", &check);
|
|
|
|
let run = run_glagol([OsStr::new("test"), project.as_os_str()]);
|
|
assert_success_stdout(
|
|
run,
|
|
"test \"project vector import\" ... ok\n1 test(s) passed\n",
|
|
"vec-i32 project test output",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i64_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
|
|
let fixture = write_fixture("vec-i64", vec_i64_fixture_source());
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
let stderr = String::from_utf8_lossy(&compile.stderr);
|
|
|
|
assert!(
|
|
compile.status.success(),
|
|
"compiler rejected vec-i64 fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare ptr @__glagol_vec_i64_empty()")
|
|
&& stdout.contains("declare ptr @__glagol_vec_i64_append(ptr, i64)")
|
|
&& stdout.contains("declare i32 @__glagol_vec_i64_len(ptr)")
|
|
&& stdout.contains("declare i64 @__glagol_vec_i64_index(ptr, i32)")
|
|
&& stdout.contains("declare i1 @__glagol_vec_i64_eq(ptr, ptr)")
|
|
&& stdout.contains("define ptr @empty_values()")
|
|
&& stdout.contains("define ptr @pair(i64 %base)")
|
|
&& stdout.contains("define ptr @echo(ptr %values)")
|
|
&& stdout.contains("define i64 @at(ptr %values, i32 %i)")
|
|
&& stdout.contains("call ptr @__glagol_vec_i64_empty()")
|
|
&& stdout.contains("call ptr @__glagol_vec_i64_append(ptr %")
|
|
&& stdout.contains("call i32 @__glagol_vec_i64_len(ptr %")
|
|
&& stdout.contains("call i64 @__glagol_vec_i64_index(ptr %")
|
|
&& stdout.contains("call i1 @__glagol_vec_i64_eq(ptr %")
|
|
&& !stdout.contains("@std."),
|
|
"LLVM output did not contain expected vec i64 runtime shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
|
|
let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
run,
|
|
concat!(
|
|
"test \"vec i64 empty length\" ... ok\n",
|
|
"test \"vec i64 append length\" ... ok\n",
|
|
"test \"vec i64 index\" ... ok\n",
|
|
"test \"vec i64 append is immutable\" ... ok\n",
|
|
"test \"vec i64 equality\" ... ok\n",
|
|
"5 test(s) passed\n",
|
|
),
|
|
"vec-i64 test runner output",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i64_formatter_and_lowering_inspector_are_visible() {
|
|
let fixture = write_fixture(
|
|
"vec-i64-tooling",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn make () -> (vec i64)
|
|
(let values (vec i64) (std.vec.i64.empty))
|
|
(std.vec.i64.append values 1i64))
|
|
|
|
(fn main () -> i32
|
|
(std.vec.i64.len (make)))
|
|
"#,
|
|
);
|
|
|
|
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
formatted,
|
|
concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(fn make () -> (vec i64)\n",
|
|
" (let values (vec i64) (std.vec.i64.empty))\n",
|
|
" (std.vec.i64.append values 1i64))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" (std.vec.i64.len (make)))\n",
|
|
),
|
|
"vec-i64 formatter output",
|
|
);
|
|
|
|
let surface = run_glagol([
|
|
OsStr::new("--inspect-lowering=surface"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-i64 surface lowering", &surface);
|
|
assert!(
|
|
String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec i64)"),
|
|
"surface lowering did not show vec i64 return type\nstdout:\n{}",
|
|
String::from_utf8_lossy(&surface.stdout)
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
OsStr::new("--inspect-lowering=checked"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-i64 checked lowering", &checked);
|
|
assert!(
|
|
String::from_utf8_lossy(&checked.stdout).contains("call std.vec.i64.append : (vec i64)"),
|
|
"checked lowering did not show typed vec i64 append\nstdout:\n{}",
|
|
String::from_utf8_lossy(&checked.stdout)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i64_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-i64 runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture = write_fixture("vec-i64-runtime", vec_i64_fixture_source());
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec-i64 runtime smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "vec-i64", &compile.stdout);
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(0),
|
|
"vec-i64 runtime 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),
|
|
"21\n",
|
|
"vec-i64 runtime stdout drifted"
|
|
);
|
|
assert!(
|
|
run.stderr.is_empty(),
|
|
"vec-i64 runtime wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i64_runtime_traps_have_contract_messages_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-i64 trap smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let cases = [
|
|
(
|
|
"vec-i64-index-bounds",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_i64 (std.vec.i64.index (std.vec.i64.empty) 0))
|
|
0)
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-i64-index-negative",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_i64
|
|
(std.vec.i64.index (std.vec.i64.append (std.vec.i64.empty) 1i64) -1))
|
|
0)
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-i64-allocation",
|
|
r#"#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
typedef struct __glagol_vec_i64 __glagol_vec_i64;
|
|
|
|
__glagol_vec_i64 *__glagol_vec_i64_empty(void);
|
|
|
|
void *__wrap_malloc(size_t size) {
|
|
(void)size;
|
|
return NULL;
|
|
}
|
|
|
|
int main(void) {
|
|
(void)__glagol_vec_i64_empty();
|
|
return 0;
|
|
}
|
|
"#,
|
|
"slovo runtime error: vector allocation failed\n",
|
|
),
|
|
];
|
|
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let runtime = manifest.join("../runtime/runtime.c");
|
|
|
|
for (name, source, expected_stderr) in cases {
|
|
let temp_dir = env::temp_dir().join(format!(
|
|
"glagol-vec-i64-runtime-{}-{}",
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
fs::create_dir_all(&temp_dir)
|
|
.unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));
|
|
|
|
let exe = temp_dir.join(name);
|
|
let run = if name.starts_with("vec-i64-index-") {
|
|
let fixture = write_fixture(name, source);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec i64 index trap", &compile);
|
|
compile_and_run_with_runtime(&clang, name, &compile.stdout)
|
|
} else {
|
|
let probe = temp_dir.join(format!("{}.c", name));
|
|
fs::write(&probe, source)
|
|
.unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
|
|
let mut clang_command = Command::new(&clang);
|
|
clang_command
|
|
.arg(&runtime)
|
|
.arg(&probe)
|
|
.arg("-Wl,--wrap=malloc")
|
|
.arg("-o")
|
|
.arg(&exe)
|
|
.current_dir(manifest);
|
|
configure_clang_runtime_env(&mut clang_command, &clang);
|
|
let clang_output = clang_command
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
|
|
assert_success("clang vec i64 allocation probe", &clang_output);
|
|
Command::new(&exe)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
|
|
};
|
|
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(1),
|
|
"trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout),
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stderr),
|
|
expected_stderr,
|
|
"trap `{}` stderr drifted",
|
|
name
|
|
);
|
|
assert!(
|
|
run.stdout.is_empty(),
|
|
"trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vec_i64_test_runner_reports_index_trap() {
|
|
let cases = [
|
|
(
|
|
"vec-i64-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec i64 index trap"
|
|
(= (std.vec.i64.index (std.vec.i64.empty) 0) 0i64))
|
|
"#,
|
|
),
|
|
(
|
|
"vec-i64-negative-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec i64 negative index trap"
|
|
(= (std.vec.i64.index (std.vec.i64.append (std.vec.i64.empty) 1i64) -1) 0i64))
|
|
"#,
|
|
),
|
|
];
|
|
|
|
for (name, source) in cases {
|
|
let fixture = write_fixture(name, source);
|
|
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_eq!(
|
|
output.status.code(),
|
|
Some(1),
|
|
"test runner vec i64 trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"test runner vec i64 trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("TestRuntimeTrap")
|
|
&& stderr.contains("slovo runtime error: vector index out of bounds"),
|
|
"test runner vec i64 trap `{}` diagnostic drifted\nstderr:\n{}",
|
|
name,
|
|
stderr
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vec_f64_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-f64.slo");
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
let stderr = String::from_utf8_lossy(&compile.stderr);
|
|
|
|
assert!(
|
|
compile.status.success(),
|
|
"compiler rejected vec-f64 fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare ptr @__glagol_vec_f64_empty()")
|
|
&& stdout.contains("declare ptr @__glagol_vec_f64_append(ptr, double)")
|
|
&& stdout.contains("declare i32 @__glagol_vec_f64_len(ptr)")
|
|
&& stdout.contains("declare double @__glagol_vec_f64_index(ptr, i32)")
|
|
&& stdout.contains("declare i1 @__glagol_vec_f64_eq(ptr, ptr)")
|
|
&& stdout.contains("define ptr @empty_values()")
|
|
&& stdout.contains("define ptr @pair(double %base)")
|
|
&& stdout.contains("define ptr @echo(ptr %values)")
|
|
&& stdout.contains("define double @at(ptr %values, i32 %i)")
|
|
&& stdout.contains("call ptr @__glagol_vec_f64_empty()")
|
|
&& stdout.contains("call ptr @__glagol_vec_f64_append(ptr %")
|
|
&& stdout.contains("call i32 @__glagol_vec_f64_len(ptr %")
|
|
&& stdout.contains("call double @__glagol_vec_f64_index(ptr %")
|
|
&& !stdout.contains("@std."),
|
|
"LLVM output did not contain expected vec f64 runtime shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
|
|
let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
run,
|
|
concat!(
|
|
"test \"vec f64 empty length\" ... ok\n",
|
|
"test \"vec f64 append length\" ... ok\n",
|
|
"test \"vec f64 index\" ... ok\n",
|
|
"test \"vec f64 append is immutable\" ... ok\n",
|
|
"test \"vec f64 equality\" ... ok\n",
|
|
"5 test(s) passed\n",
|
|
),
|
|
"vec-f64 test runner output",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_f64_formatter_and_lowering_inspector_are_visible() {
|
|
let fixture = write_fixture(
|
|
"vec-f64-tooling",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn make () -> (vec f64)
|
|
(let values (vec f64) (std.vec.f64.empty))
|
|
(std.vec.f64.append values 1.0))
|
|
|
|
(fn main () -> i32
|
|
(std.vec.f64.len (make)))
|
|
"#,
|
|
);
|
|
|
|
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
formatted,
|
|
concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(fn make () -> (vec f64)\n",
|
|
" (let values (vec f64) (std.vec.f64.empty))\n",
|
|
" (std.vec.f64.append values 1.0))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" (std.vec.f64.len (make)))\n",
|
|
),
|
|
"vec-f64 formatter output",
|
|
);
|
|
|
|
let surface = run_glagol([
|
|
OsStr::new("--inspect-lowering=surface"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-f64 surface lowering", &surface);
|
|
assert!(
|
|
String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec f64)"),
|
|
"surface lowering did not show vec f64 return type\nstdout:\n{}",
|
|
String::from_utf8_lossy(&surface.stdout)
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
OsStr::new("--inspect-lowering=checked"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-f64 checked lowering", &checked);
|
|
assert!(
|
|
String::from_utf8_lossy(&checked.stdout).contains("call std.vec.f64.append : (vec f64)"),
|
|
"checked lowering did not show typed vec f64 append\nstdout:\n{}",
|
|
String::from_utf8_lossy(&checked.stdout)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_f64_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-f64 runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture = write_fixture("vec-f64-runtime", vec_f64_fixture_source());
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec-f64 runtime smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "vec-f64", &compile.stdout);
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(0),
|
|
"vec-f64 runtime 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),
|
|
"21\n",
|
|
"vec-f64 runtime stdout drifted"
|
|
);
|
|
assert!(
|
|
run.stderr.is_empty(),
|
|
"vec-f64 runtime wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_f64_runtime_traps_have_contract_messages_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-f64 trap smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let cases = [
|
|
(
|
|
"vec-f64-index-bounds",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_f64 (std.vec.f64.index (std.vec.f64.empty) 0))
|
|
0)
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-f64-index-negative",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_f64
|
|
(std.vec.f64.index (std.vec.f64.append (std.vec.f64.empty) 1.0) -1))
|
|
0)
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-f64-allocation",
|
|
r#"#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
typedef struct __glagol_vec_f64 __glagol_vec_f64;
|
|
|
|
__glagol_vec_f64 *__glagol_vec_f64_empty(void);
|
|
|
|
void *__wrap_malloc(size_t size) {
|
|
(void)size;
|
|
return NULL;
|
|
}
|
|
|
|
int main(void) {
|
|
(void)__glagol_vec_f64_empty();
|
|
return 0;
|
|
}
|
|
"#,
|
|
"slovo runtime error: vector allocation failed\n",
|
|
),
|
|
];
|
|
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let runtime = manifest.join("../runtime/runtime.c");
|
|
|
|
for (name, source, expected_stderr) in cases {
|
|
let temp_dir = env::temp_dir().join(format!(
|
|
"glagol-vec-f64-runtime-{}-{}",
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
fs::create_dir_all(&temp_dir)
|
|
.unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));
|
|
|
|
let exe = temp_dir.join(name);
|
|
let run = if name.starts_with("vec-f64-index-") {
|
|
let fixture = write_fixture(name, source);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec f64 index trap", &compile);
|
|
compile_and_run_with_runtime(&clang, name, &compile.stdout)
|
|
} else {
|
|
let probe = temp_dir.join(format!("{}.c", name));
|
|
fs::write(&probe, source)
|
|
.unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
|
|
let mut clang_command = Command::new(&clang);
|
|
clang_command
|
|
.arg(&runtime)
|
|
.arg(&probe)
|
|
.arg("-Wl,--wrap=malloc")
|
|
.arg("-o")
|
|
.arg(&exe)
|
|
.current_dir(manifest);
|
|
configure_clang_runtime_env(&mut clang_command, &clang);
|
|
let clang_output = clang_command
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
|
|
assert_success("clang vec f64 allocation probe", &clang_output);
|
|
Command::new(&exe)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
|
|
};
|
|
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(1),
|
|
"trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout),
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stderr),
|
|
expected_stderr,
|
|
"trap `{}` stderr drifted",
|
|
name
|
|
);
|
|
assert!(
|
|
run.stdout.is_empty(),
|
|
"trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vec_f64_test_runner_reports_index_trap() {
|
|
let cases = [
|
|
(
|
|
"vec-f64-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec f64 index trap"
|
|
(= (std.vec.f64.index (std.vec.f64.empty) 0) 0.0))
|
|
"#,
|
|
),
|
|
(
|
|
"vec-f64-negative-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec f64 negative index trap"
|
|
(= (std.vec.f64.index (std.vec.f64.append (std.vec.f64.empty) 1.0) -1) 0.0))
|
|
"#,
|
|
),
|
|
];
|
|
|
|
for (name, source) in cases {
|
|
let fixture = write_fixture(name, source);
|
|
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_eq!(
|
|
output.status.code(),
|
|
Some(1),
|
|
"test runner vec f64 trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"test runner vec f64 trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("TestRuntimeTrap")
|
|
&& stderr.contains("slovo runtime error: vector index out of bounds"),
|
|
"test runner vec f64 trap `{}` diagnostic drifted\nstderr:\n{}",
|
|
name,
|
|
stderr
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vec_bool_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-bool.slo");
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
let stderr = String::from_utf8_lossy(&compile.stderr);
|
|
|
|
assert!(
|
|
compile.status.success(),
|
|
"compiler rejected vec-bool fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare ptr @__glagol_vec_bool_empty()")
|
|
&& stdout.contains("declare ptr @__glagol_vec_bool_append(ptr, i1)")
|
|
&& stdout.contains("declare i32 @__glagol_vec_bool_len(ptr)")
|
|
&& stdout.contains("declare i1 @__glagol_vec_bool_index(ptr, i32)")
|
|
&& stdout.contains("declare i1 @__glagol_vec_bool_eq(ptr, ptr)")
|
|
&& stdout.contains("define ptr @empty_values()")
|
|
&& stdout.contains("define ptr @pair()")
|
|
&& stdout.contains("define ptr @echo(ptr %values)")
|
|
&& stdout.contains("define i1 @at(ptr %values, i32 %i)")
|
|
&& stdout.contains("call ptr @__glagol_vec_bool_empty()")
|
|
&& stdout.contains("call ptr @__glagol_vec_bool_append(ptr %")
|
|
&& stdout.contains("call i32 @__glagol_vec_bool_len(ptr %")
|
|
&& stdout.contains("call i1 @__glagol_vec_bool_index(ptr %")
|
|
&& !stdout.contains("@std."),
|
|
"LLVM output did not contain expected vec bool runtime shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
|
|
let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
run,
|
|
concat!(
|
|
"test \"vec bool empty length\" ... ok\n",
|
|
"test \"vec bool append length\" ... ok\n",
|
|
"test \"vec bool index\" ... ok\n",
|
|
"test \"vec bool append is immutable\" ... ok\n",
|
|
"test \"vec bool equality\" ... ok\n",
|
|
"5 test(s) passed\n",
|
|
),
|
|
"vec-bool test runner output",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_bool_formatter_and_lowering_inspector_are_visible() {
|
|
let fixture = write_fixture(
|
|
"vec-bool-tooling",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn make () -> (vec bool)
|
|
(let values (vec bool) (std.vec.bool.empty))
|
|
(std.vec.bool.append values true))
|
|
|
|
(fn main () -> i32
|
|
(if (= (std.vec.bool.index (make) 0) true)
|
|
1
|
|
0))
|
|
"#,
|
|
);
|
|
|
|
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
formatted,
|
|
concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(fn make () -> (vec bool)\n",
|
|
" (let values (vec bool) (std.vec.bool.empty))\n",
|
|
" (std.vec.bool.append values true))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" (if (= (std.vec.bool.index (make) 0) true)\n",
|
|
" 1\n",
|
|
" 0))\n",
|
|
),
|
|
"vec-bool formatter output",
|
|
);
|
|
|
|
let surface = run_glagol([
|
|
OsStr::new("--inspect-lowering=surface"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-bool surface lowering", &surface);
|
|
assert!(
|
|
String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec bool)"),
|
|
"surface lowering did not show vec bool return type\nstdout:\n{}",
|
|
String::from_utf8_lossy(&surface.stdout)
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
OsStr::new("--inspect-lowering=checked"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-bool checked lowering", &checked);
|
|
assert!(
|
|
String::from_utf8_lossy(&checked.stdout).contains("call std.vec.bool.append : (vec bool)"),
|
|
"checked lowering did not show typed vec bool append\nstdout:\n{}",
|
|
String::from_utf8_lossy(&checked.stdout)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_bool_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-bool runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture = write_fixture("vec-bool-runtime", vec_bool_fixture_source());
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec-bool runtime smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "vec-bool", &compile.stdout);
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(0),
|
|
"vec-bool runtime 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),
|
|
"false\n",
|
|
"vec-bool runtime stdout drifted"
|
|
);
|
|
assert!(
|
|
run.stderr.is_empty(),
|
|
"vec-bool runtime wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_bool_runtime_traps_have_contract_messages_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-bool trap smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let cases = [
|
|
(
|
|
"vec-bool-index-bounds",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_bool (std.vec.bool.index (std.vec.bool.empty) 0))
|
|
0)
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-bool-index-negative",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_bool
|
|
(std.vec.bool.index (std.vec.bool.append (std.vec.bool.empty) true) -1))
|
|
0)
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-bool-allocation",
|
|
r#"#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
typedef struct __glagol_vec_bool __glagol_vec_bool;
|
|
|
|
__glagol_vec_bool *__glagol_vec_bool_empty(void);
|
|
|
|
void *__wrap_malloc(size_t size) {
|
|
(void)size;
|
|
return NULL;
|
|
}
|
|
|
|
int main(void) {
|
|
(void)__glagol_vec_bool_empty();
|
|
return 0;
|
|
}
|
|
"#,
|
|
"slovo runtime error: vector allocation failed\n",
|
|
),
|
|
];
|
|
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let runtime = manifest.join("../runtime/runtime.c");
|
|
|
|
for (name, source, expected_stderr) in cases {
|
|
let temp_dir = env::temp_dir().join(format!(
|
|
"glagol-vec-bool-runtime-{}-{}",
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
fs::create_dir_all(&temp_dir)
|
|
.unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));
|
|
|
|
let exe = temp_dir.join(name);
|
|
let run = if name.starts_with("vec-bool-index-") {
|
|
let fixture = write_fixture(name, source);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec bool index trap", &compile);
|
|
compile_and_run_with_runtime(&clang, name, &compile.stdout)
|
|
} else {
|
|
let probe = temp_dir.join(format!("{}.c", name));
|
|
fs::write(&probe, source)
|
|
.unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
|
|
let mut clang_command = Command::new(&clang);
|
|
clang_command
|
|
.arg(&runtime)
|
|
.arg(&probe)
|
|
.arg("-Wl,--wrap=malloc")
|
|
.arg("-o")
|
|
.arg(&exe)
|
|
.current_dir(manifest);
|
|
configure_clang_runtime_env(&mut clang_command, &clang);
|
|
let clang_output = clang_command
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
|
|
assert_success("clang vec bool allocation probe", &clang_output);
|
|
Command::new(&exe)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
|
|
};
|
|
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(1),
|
|
"trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout),
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stderr),
|
|
expected_stderr,
|
|
"trap `{}` stderr drifted",
|
|
name
|
|
);
|
|
assert!(
|
|
run.stdout.is_empty(),
|
|
"trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vec_bool_test_runner_reports_index_trap() {
|
|
let cases = [
|
|
(
|
|
"vec-bool-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec bool index trap"
|
|
(= (std.vec.bool.index (std.vec.bool.empty) 0) false))
|
|
"#,
|
|
),
|
|
(
|
|
"vec-bool-negative-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec bool negative index trap"
|
|
(= (std.vec.bool.index (std.vec.bool.append (std.vec.bool.empty) true) -1) false))
|
|
"#,
|
|
),
|
|
];
|
|
|
|
for (name, source) in cases {
|
|
let fixture = write_fixture(name, source);
|
|
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_eq!(
|
|
output.status.code(),
|
|
Some(1),
|
|
"test runner vec bool trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"test runner vec bool trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("TestRuntimeTrap")
|
|
&& stderr.contains("slovo runtime error: vector index out of bounds"),
|
|
"test runner vec bool trap `{}` diagnostic drifted\nstderr:\n{}",
|
|
name,
|
|
stderr
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vec_string_fixture_emits_runtime_owned_vector_calls_and_tests_pass() {
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-string.slo");
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
let stderr = String::from_utf8_lossy(&compile.stderr);
|
|
|
|
assert!(
|
|
compile.status.success(),
|
|
"compiler rejected vec-string fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare ptr @__glagol_vec_string_empty()")
|
|
&& stdout.contains("declare ptr @__glagol_vec_string_append(ptr, ptr)")
|
|
&& stdout.contains("declare i32 @__glagol_vec_string_len(ptr)")
|
|
&& stdout.contains("declare ptr @__glagol_vec_string_index(ptr, i32)")
|
|
&& stdout.contains("declare i1 @__glagol_vec_string_eq(ptr, ptr)")
|
|
&& stdout.contains("define ptr @empty_values()")
|
|
&& stdout.contains("define ptr @pair(ptr %first, ptr %second)")
|
|
&& stdout.contains("define ptr @echo(ptr %values)")
|
|
&& stdout.contains("define ptr @at(ptr %values, i32 %i)")
|
|
&& stdout.contains("call ptr @__glagol_vec_string_empty()")
|
|
&& stdout.contains("call ptr @__glagol_vec_string_append(ptr %")
|
|
&& stdout.contains("call i32 @__glagol_vec_string_len(ptr %")
|
|
&& stdout.contains("call ptr @__glagol_vec_string_index(ptr %")
|
|
&& !stdout.contains("@std."),
|
|
"LLVM output did not contain expected vec string runtime shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
|
|
let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
run,
|
|
concat!(
|
|
"test \"vec string empty length\" ... ok\n",
|
|
"test \"vec string append length\" ... ok\n",
|
|
"test \"vec string index\" ... ok\n",
|
|
"test \"vec string append is immutable\" ... ok\n",
|
|
"test \"vec string equality\" ... ok\n",
|
|
"5 test(s) passed\n",
|
|
),
|
|
"vec-string test runner output",
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_string_equality_lowers_to_runtime_helper() {
|
|
let fixture = write_fixture(
|
|
"vec-string-equality",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn same () -> i32
|
|
(if (= (std.vec.string.append (std.vec.string.empty) "same")
|
|
(std.vec.string.append (std.vec.string.empty) "same"))
|
|
1
|
|
0))
|
|
|
|
(fn main () -> i32
|
|
(same))
|
|
"#,
|
|
);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&compile.stdout);
|
|
let stderr = String::from_utf8_lossy(&compile.stderr);
|
|
|
|
assert!(
|
|
compile.status.success(),
|
|
"compiler rejected vec string equality fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("call i1 @__glagol_vec_string_eq(ptr %"),
|
|
"LLVM output did not lower vec string equality through helper\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_string_formatter_and_lowering_inspector_are_visible() {
|
|
let fixture = write_fixture(
|
|
"vec-string-tooling",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn make () -> (vec string)
|
|
(let values (vec string) (std.vec.string.empty))
|
|
(std.vec.string.append values "alpha"))
|
|
|
|
(fn main () -> i32
|
|
(std.vec.string.len (make)))
|
|
"#,
|
|
);
|
|
|
|
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
assert_success_stdout(
|
|
formatted,
|
|
concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(fn make () -> (vec string)\n",
|
|
" (let values (vec string) (std.vec.string.empty))\n",
|
|
" (std.vec.string.append values \"alpha\"))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" (std.vec.string.len (make)))\n",
|
|
),
|
|
"vec-string formatter output",
|
|
);
|
|
|
|
let surface = run_glagol([
|
|
OsStr::new("--inspect-lowering=surface"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-string surface lowering", &surface);
|
|
assert!(
|
|
String::from_utf8_lossy(&surface.stdout).contains("fn make() -> (vec string)"),
|
|
"surface lowering did not show vec string return type\nstdout:\n{}",
|
|
String::from_utf8_lossy(&surface.stdout)
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
OsStr::new("--inspect-lowering=checked"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect vec-string checked lowering", &checked);
|
|
assert!(
|
|
String::from_utf8_lossy(&checked.stdout)
|
|
.contains("call std.vec.string.append : (vec string)"),
|
|
"checked lowering did not show typed vec string append\nstdout:\n{}",
|
|
String::from_utf8_lossy(&checked.stdout)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_string_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-string runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/vec-string.slo");
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec-string runtime smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "vec-string", &compile.stdout);
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(0),
|
|
"vec-string runtime 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),
|
|
"tree\n",
|
|
"vec-string runtime stdout drifted"
|
|
);
|
|
assert!(
|
|
run.stderr.is_empty(),
|
|
"vec-string runtime wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn vec_string_runtime_traps_have_contract_messages_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping vec-string trap smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let cases = [
|
|
(
|
|
"vec-string-index-bounds",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_string (std.vec.string.index (std.vec.string.empty) 0))
|
|
0)
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-string-index-negative",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_string
|
|
(std.vec.string.index
|
|
(std.vec.string.append (std.vec.string.empty) "only")
|
|
-1))
|
|
0)
|
|
"#,
|
|
"slovo runtime error: vector index out of bounds\n",
|
|
),
|
|
(
|
|
"vec-string-allocation",
|
|
r#"#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
typedef struct __glagol_vec_string __glagol_vec_string;
|
|
|
|
__glagol_vec_string *__glagol_vec_string_empty(void);
|
|
|
|
void *__wrap_malloc(size_t size) {
|
|
(void)size;
|
|
return NULL;
|
|
}
|
|
|
|
int main(void) {
|
|
(void)__glagol_vec_string_empty();
|
|
return 0;
|
|
}
|
|
"#,
|
|
"slovo runtime error: vector allocation failed\n",
|
|
),
|
|
];
|
|
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let runtime = manifest.join("../runtime/runtime.c");
|
|
|
|
for (name, source, expected_stderr) in cases {
|
|
let temp_dir = env::temp_dir().join(format!(
|
|
"glagol-vec-string-runtime-{}-{}",
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
fs::create_dir_all(&temp_dir)
|
|
.unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));
|
|
|
|
let exe = temp_dir.join(name);
|
|
let run = if name.starts_with("vec-string-index-") {
|
|
let fixture = write_fixture(name, source);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile vec string index trap", &compile);
|
|
compile_and_run_with_runtime(&clang, name, &compile.stdout)
|
|
} else {
|
|
let probe = temp_dir.join(format!("{}.c", name));
|
|
fs::write(&probe, source)
|
|
.unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
|
|
let mut clang_command = Command::new(&clang);
|
|
clang_command
|
|
.arg(&runtime)
|
|
.arg(&probe)
|
|
.arg("-Wl,--wrap=malloc")
|
|
.arg("-o")
|
|
.arg(&exe)
|
|
.current_dir(manifest);
|
|
configure_clang_runtime_env(&mut clang_command, &clang);
|
|
let clang_output = clang_command
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
|
|
assert_success("clang vec string allocation probe", &clang_output);
|
|
Command::new(&exe)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err))
|
|
};
|
|
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(1),
|
|
"trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout),
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stderr),
|
|
expected_stderr,
|
|
"trap `{}` stderr drifted",
|
|
name
|
|
);
|
|
assert!(
|
|
run.stdout.is_empty(),
|
|
"trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
String::from_utf8_lossy(&run.stdout)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn vec_string_test_runner_reports_index_trap() {
|
|
let cases = [
|
|
(
|
|
"vec-string-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec string index trap"
|
|
(= (std.vec.string.index (std.vec.string.empty) 0) "missing"))
|
|
"#,
|
|
),
|
|
(
|
|
"vec-string-negative-index-test-trap",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "vec string negative index trap"
|
|
(= (std.vec.string.index (std.vec.string.append (std.vec.string.empty) "only") -1) "missing"))
|
|
"#,
|
|
),
|
|
];
|
|
|
|
for (name, source) in cases {
|
|
let fixture = write_fixture(name, source);
|
|
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert_eq!(
|
|
output.status.code(),
|
|
Some(1),
|
|
"test runner vec string trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"test runner vec string trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("TestRuntimeTrap")
|
|
&& stderr.contains("slovo runtime error: vector index out of bounds"),
|
|
"test runner vec string trap `{}` diagnostic drifted\nstderr:\n{}",
|
|
name,
|
|
stderr
|
|
);
|
|
}
|
|
}
|
|
|
|
fn vec_i64_fixture_source() -> &'static str {
|
|
r#"
|
|
(module main)
|
|
|
|
(fn empty_values () -> (vec i64)
|
|
(std.vec.i64.empty))
|
|
|
|
(fn pair ((base i64)) -> (vec i64)
|
|
(let values (vec i64) (std.vec.i64.empty))
|
|
(let first (vec i64) (std.vec.i64.append values base))
|
|
(std.vec.i64.append first (+ base 1i64)))
|
|
|
|
(fn echo ((values (vec i64))) -> (vec i64)
|
|
values)
|
|
|
|
(fn length ((values (vec i64))) -> i32
|
|
(std.vec.i64.len values))
|
|
|
|
(fn at ((values (vec i64)) (i i32)) -> i64
|
|
(std.vec.i64.index values i))
|
|
|
|
(fn call_return_value () -> i64
|
|
(std.vec.i64.index (echo (pair 20i64)) 1))
|
|
|
|
(fn original_len_after_append () -> i32
|
|
(let values (vec i64) (std.vec.i64.empty))
|
|
(let appended (vec i64) (std.vec.i64.append values 1i64))
|
|
(std.vec.i64.len values))
|
|
|
|
(fn same_pair () -> bool
|
|
(= (pair 5i64)
|
|
(std.vec.i64.append (std.vec.i64.append (std.vec.i64.empty) 5i64) 6i64)))
|
|
|
|
(test "vec i64 empty length"
|
|
(= (std.vec.i64.len (empty_values)) 0))
|
|
|
|
(test "vec i64 append length"
|
|
(= (length (pair 40i64)) 2))
|
|
|
|
(test "vec i64 index"
|
|
(= (at (pair 40i64) 1) 41i64))
|
|
|
|
(test "vec i64 append is immutable"
|
|
(= (original_len_after_append) 0))
|
|
|
|
(test "vec i64 equality"
|
|
(same_pair))
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_i64 (call_return_value))
|
|
0)
|
|
"#
|
|
}
|
|
|
|
fn vec_f64_fixture_source() -> &'static str {
|
|
r#"
|
|
(module main)
|
|
|
|
(fn empty_values () -> (vec f64)
|
|
(std.vec.f64.empty))
|
|
|
|
(fn pair ((base f64)) -> (vec f64)
|
|
(let values (vec f64) (std.vec.f64.empty))
|
|
(let first (vec f64) (std.vec.f64.append values base))
|
|
(std.vec.f64.append first (+ base 1.0)))
|
|
|
|
(fn echo ((values (vec f64))) -> (vec f64)
|
|
values)
|
|
|
|
(fn length ((values (vec f64))) -> i32
|
|
(std.vec.f64.len values))
|
|
|
|
(fn at ((values (vec f64)) (i i32)) -> f64
|
|
(std.vec.f64.index values i))
|
|
|
|
(fn call_return_value () -> f64
|
|
(std.vec.f64.index (echo (pair 20.0)) 1))
|
|
|
|
(fn original_len_after_append () -> i32
|
|
(let values (vec f64) (std.vec.f64.empty))
|
|
(let appended (vec f64) (std.vec.f64.append values 1.0))
|
|
(std.vec.f64.len values))
|
|
|
|
(fn same_pair () -> bool
|
|
(= (pair 5.0)
|
|
(std.vec.f64.append (std.vec.f64.append (std.vec.f64.empty) 5.0) 6.0)))
|
|
|
|
(test "vec f64 empty length"
|
|
(= (std.vec.f64.len (empty_values)) 0))
|
|
|
|
(test "vec f64 append length"
|
|
(= (length (pair 40.0)) 2))
|
|
|
|
(test "vec f64 index"
|
|
(= (at (pair 40.0) 1) 41.0))
|
|
|
|
(test "vec f64 append is immutable"
|
|
(= (original_len_after_append) 0))
|
|
|
|
(test "vec f64 equality"
|
|
(same_pair))
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_f64 (call_return_value))
|
|
0)
|
|
"#
|
|
}
|
|
|
|
fn vec_bool_fixture_source() -> &'static str {
|
|
r#"
|
|
(module main)
|
|
|
|
(fn empty_values () -> (vec bool)
|
|
(std.vec.bool.empty))
|
|
|
|
(fn pair () -> (vec bool)
|
|
(let values (vec bool) (std.vec.bool.empty))
|
|
(let first (vec bool) (std.vec.bool.append values true))
|
|
(std.vec.bool.append first false))
|
|
|
|
(fn echo ((values (vec bool))) -> (vec bool)
|
|
values)
|
|
|
|
(fn length ((values (vec bool))) -> i32
|
|
(std.vec.bool.len values))
|
|
|
|
(fn at ((values (vec bool)) (i i32)) -> bool
|
|
(std.vec.bool.index values i))
|
|
|
|
(fn call_return_value () -> bool
|
|
(std.vec.bool.index (echo (pair)) 1))
|
|
|
|
(fn original_len_after_append () -> i32
|
|
(let values (vec bool) (std.vec.bool.empty))
|
|
(let appended (vec bool) (std.vec.bool.append values true))
|
|
(std.vec.bool.len values))
|
|
|
|
(fn same_pair () -> bool
|
|
(= (pair)
|
|
(std.vec.bool.append (std.vec.bool.append (std.vec.bool.empty) true) false)))
|
|
|
|
(test "vec bool empty length"
|
|
(= (std.vec.bool.len (empty_values)) 0))
|
|
|
|
(test "vec bool append length"
|
|
(= (length (pair)) 2))
|
|
|
|
(test "vec bool index"
|
|
(= (at (pair) 1) false))
|
|
|
|
(test "vec bool append is immutable"
|
|
(= (original_len_after_append) 0))
|
|
|
|
(test "vec bool equality"
|
|
(same_pair))
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_bool (call_return_value))
|
|
0)
|
|
"#
|
|
}
|
|
|
|
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-collections-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 write_vec_project() -> PathBuf {
|
|
let root = env::temp_dir().join(format!(
|
|
"glagol-collections-alpha-project-{}-{}",
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
let src = root.join("src");
|
|
fs::create_dir_all(&src).unwrap_or_else(|err| panic!("create `{}`: {}", src.display(), err));
|
|
fs::write(
|
|
root.join("slovo.toml"),
|
|
"[project]\nname = \"vecproj\"\nsource_root = \"src\"\nentry = \"main\"\n",
|
|
)
|
|
.unwrap_or_else(|err| panic!("write project manifest: {}", err));
|
|
fs::write(
|
|
src.join("math.slo"),
|
|
r#"(module math (export singleton length))
|
|
|
|
(fn singleton ((value i32)) -> (vec i32)
|
|
(std.vec.i32.append (std.vec.i32.empty) value))
|
|
|
|
(fn length ((values (vec i32))) -> i32
|
|
(std.vec.i32.len values))
|
|
"#,
|
|
)
|
|
.unwrap_or_else(|err| panic!("write math module: {}", err));
|
|
fs::write(
|
|
src.join("main.slo"),
|
|
r#"(module main)
|
|
|
|
(import math (singleton length))
|
|
|
|
(fn main () -> i32
|
|
(length (singleton 7)))
|
|
|
|
(test "project vector import"
|
|
(= (length (singleton 7)) 1))
|
|
"#,
|
|
)
|
|
.unwrap_or_else(|err| panic!("write main module: {}", err));
|
|
root
|
|
}
|
|
|
|
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
|
|
);
|
|
}
|
|
|
|
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, "{} stdout drifted", context);
|
|
assert!(stderr.is_empty(), "{} wrote stderr:\n{}", context, stderr);
|
|
}
|
|
|
|
fn compile_and_run_with_runtime(clang: &Path, name: &str, ir: &[u8]) -> Output {
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let temp_dir = env::temp_dir().join(format!(
|
|
"glagol-vec-runtime-{}-{}",
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
fs::create_dir_all(&temp_dir)
|
|
.unwrap_or_else(|err| panic!("create `{}`: {}", temp_dir.display(), err));
|
|
|
|
let ir_path = temp_dir.join(format!("{}.ll", name));
|
|
let exe_path = temp_dir.join(name);
|
|
fs::write(&ir_path, ir).unwrap_or_else(|err| panic!("write `{}`: {}", ir_path.display(), err));
|
|
|
|
let runtime = manifest.join("../runtime/runtime.c");
|
|
let mut clang_command = Command::new(clang);
|
|
clang_command
|
|
.arg(&runtime)
|
|
.arg(&ir_path)
|
|
.arg("-o")
|
|
.arg(&exe_path)
|
|
.current_dir(manifest);
|
|
configure_clang_runtime_env(&mut clang_command, clang);
|
|
let clang_output = clang_command
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", clang.display(), err));
|
|
assert_success("clang vec runtime smoke", &clang_output);
|
|
|
|
Command::new(&exe_path)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err))
|
|
}
|
|
|
|
fn find_clang() -> Option<PathBuf> {
|
|
if let Some(path) = env::var_os("GLAGOL_CLANG").filter(|value| !value.is_empty()) {
|
|
return Some(PathBuf::from(path));
|
|
}
|
|
|
|
let hermetic_clang = PathBuf::from("/tmp/glagol-clang-root/usr/bin/clang");
|
|
if hermetic_clang.is_file() {
|
|
return Some(hermetic_clang);
|
|
}
|
|
|
|
find_on_path("clang")
|
|
}
|
|
|
|
fn find_on_path(program: &str) -> Option<PathBuf> {
|
|
let path = env::var_os("PATH")?;
|
|
env::split_paths(&path)
|
|
.map(|dir| dir.join(program))
|
|
.find(|candidate| candidate.is_file())
|
|
}
|
|
|
|
fn configure_clang_runtime_env(command: &mut Command, clang: &Path) {
|
|
if !clang.starts_with("/tmp/glagol-clang-root") {
|
|
return;
|
|
}
|
|
|
|
let root = Path::new("/tmp/glagol-clang-root");
|
|
let lib64 = root.join("usr/lib64");
|
|
let lib = root.join("usr/lib");
|
|
let existing = env::var_os("LD_LIBRARY_PATH").unwrap_or_default();
|
|
let mut paths = vec![lib64, lib];
|
|
paths.extend(env::split_paths(&existing));
|
|
let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH");
|
|
command.env("LD_LIBRARY_PATH", joined);
|
|
}
|