1321 lines
38 KiB
Rust
1321 lines
38 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 print_string_literal_emits_static_global_and_call() {
|
|
let fixture = write_fixture(
|
|
"print-string-literal",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(print_string "hello")
|
|
0)
|
|
"#,
|
|
);
|
|
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 rejected print_string literal fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare void @print_string(ptr)")
|
|
&& stdout.contains(
|
|
"@.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\", align 1"
|
|
)
|
|
&& stdout.contains("call void @print_string(ptr @.str.0)"),
|
|
"LLVM output did not contain expected string runtime shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn print_string_literal_escapes_llvm_global_bytes() {
|
|
let fixture = write_fixture(
|
|
"print-string-escapes",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(print_string "line\nquote\"slash\\tab\t")
|
|
0)
|
|
"#,
|
|
);
|
|
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 rejected escaped print_string literal fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains(
|
|
"@.str.0 = private unnamed_addr constant [22 x i8] c\"line\\0Aquote\\22slash\\5Ctab\\09\\00\", align 1"
|
|
),
|
|
"LLVM string global did not preserve expected escapes\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn promoted_string_print_fixture_emits_static_globals_and_calls() {
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/string-print.slo");
|
|
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 rejected promoted string-print fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("@.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\", align 1")
|
|
&& stdout.contains(
|
|
"@.str.1 = private unnamed_addr constant [22 x i8] c\"line\\0Aquote\\22slash\\5Ctab\\09\\00\", align 1"
|
|
)
|
|
&& stdout.contains("call void @print_string(ptr @.str.0)")
|
|
&& stdout.contains("call void @print_string(ptr @.str.1)"),
|
|
"promoted fixture LLVM output did not contain expected string globals and calls\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn print_string_requires_exactly_one_string_argument() {
|
|
let cases = [
|
|
(
|
|
"print-string-wrong-type",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(print_string 1)
|
|
0)
|
|
"#,
|
|
"TypeMismatch",
|
|
),
|
|
(
|
|
"print-string-wrong-arity",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(print_string "a" "b")
|
|
0)
|
|
"#,
|
|
"ArityMismatch",
|
|
),
|
|
];
|
|
|
|
for (name, source, diagnostic) in cases {
|
|
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 invalid print_string call `{}`\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stderr.contains(diagnostic),
|
|
"diagnostic `{}` was not reported for `{}`\nstderr:\n{}",
|
|
diagnostic,
|
|
name,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"rejected compile wrote stdout:\n{}",
|
|
stdout
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn print_string_literal_rejects_non_ascii_payloads() {
|
|
let source = format!(
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(print_string "zdravo {}")
|
|
0)
|
|
"#,
|
|
'\u{017E}'
|
|
);
|
|
let fixture = write_fixture("print-string-non-ascii", &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 non-ASCII print_string literal\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stderr.contains("UnsupportedStringLiteral")
|
|
&& stderr.contains("printable ASCII string literal"),
|
|
"non-ASCII string literal did not report the expected diagnostic\nstderr:\n{}",
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"rejected compile wrote stdout:\n{}",
|
|
stdout
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn print_string_rejects_unsupported_escape_spellings() {
|
|
for escape in ["\\0", "\\r", "\\x"] {
|
|
let source = format!(
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(print_string "{}")
|
|
0)
|
|
"#,
|
|
escape
|
|
);
|
|
let fixture = write_fixture("print-string-unsupported-escape", &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 unsupported escape `{}`\nstdout:\n{}\nstderr:\n{}",
|
|
escape,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stderr.contains("UnsupportedStringEscape"),
|
|
"unsupported escape `{}` did not report the expected diagnostic\nstderr:\n{}",
|
|
escape,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"rejected compile wrote stdout:\n{}",
|
|
stdout
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn formatter_rejects_strings_outside_promoted_domain() {
|
|
let non_ascii_source = format!(
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(print_string "zdravo {}")
|
|
0)
|
|
"#,
|
|
'\u{017E}'
|
|
);
|
|
let cases: [(&str, String); 2] = [
|
|
("format-non-ascii", non_ascii_source),
|
|
(
|
|
"format-unsupported-escape",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(print_string "\0")
|
|
0)
|
|
"#
|
|
.to_string(),
|
|
),
|
|
];
|
|
|
|
for (name, source) in cases {
|
|
let fixture = write_fixture(name, &source);
|
|
let output = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
|
|
assert!(
|
|
!output.status.success(),
|
|
"formatter unexpectedly accepted `{}`\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stderr.contains("UnsupportedString"),
|
|
"formatter rejection for `{}` did not report a string-domain diagnostic\nstderr:\n{}",
|
|
name,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"rejected format wrote stdout:\n{}",
|
|
stdout
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn string_values_flow_through_returns_locals_calls_and_print_string() {
|
|
let fixture = write_fixture(
|
|
"string-value-flow",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn label () -> string
|
|
"hello")
|
|
|
|
(fn echo ((value string)) -> string
|
|
value)
|
|
|
|
(fn main () -> i32
|
|
(let value string (echo (label)))
|
|
(print_string value)
|
|
(string_len value))
|
|
"#,
|
|
);
|
|
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 rejected string value-flow fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("define ptr @label()")
|
|
&& stdout.contains("define ptr @echo(ptr %value)")
|
|
&& stdout.contains("%value.addr = alloca ptr")
|
|
&& stdout.contains("call void @print_string(ptr %")
|
|
&& stdout.contains("call i32 @string_len(ptr %"),
|
|
"LLVM output did not contain expected string value-flow shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_string_concat_emits_runtime_call_and_flows_as_string() {
|
|
let fixture = write_fixture(
|
|
"std-string-concat",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn hello () -> string
|
|
(std.string.concat "hel" "lo"))
|
|
|
|
(fn shout ((value string)) -> string
|
|
(std.string.concat value "!"))
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_string (shout (hello)))
|
|
(std.string.len (shout (hello))))
|
|
|
|
(test "std concat equality"
|
|
(= (std.string.concat "slo" "vo") "slovo"))
|
|
|
|
(test "std concat length"
|
|
(= (std.string.len (std.string.concat "a" "bc")) 3))
|
|
"#,
|
|
);
|
|
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 rejected std string concat fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare ptr @__glagol_string_concat(ptr, ptr)")
|
|
&& stdout.contains("call ptr @__glagol_string_concat(ptr @.str.")
|
|
&& stdout.contains("call void @print_string(ptr %")
|
|
&& stdout.contains("call i32 @string_len(ptr %")
|
|
&& !stdout.contains("@std."),
|
|
"LLVM output did not contain expected concat runtime shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
|
|
let tests = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
assert_success("run std string concat tests", &tests);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&tests.stdout),
|
|
concat!(
|
|
"test \"std concat equality\" ... ok\n",
|
|
"test \"std concat length\" ... ok\n",
|
|
"2 test(s) passed\n",
|
|
),
|
|
"concat test runner stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_string_concat_is_formatter_and_lowering_inspector_visible() {
|
|
let fixture = write_fixture(
|
|
"std-string-concat-tooling",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn joined () -> string
|
|
(std.string.concat
|
|
(std.string.concat "slo" "v")
|
|
"o"))
|
|
|
|
(fn main () -> i32
|
|
(std.string.len (joined)))
|
|
"#,
|
|
);
|
|
|
|
let formatted = run_glagol([OsStr::new("--format"), fixture.as_os_str()]);
|
|
assert_success("format std string concat", &formatted);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&formatted.stdout),
|
|
concat!(
|
|
"(module main)\n",
|
|
"\n",
|
|
"(fn joined () -> string\n",
|
|
" (std.string.concat (std.string.concat \"slo\" \"v\") \"o\"))\n",
|
|
"\n",
|
|
"(fn main () -> i32\n",
|
|
" (std.string.len (joined)))\n",
|
|
),
|
|
"formatter output drifted for std string concat"
|
|
);
|
|
|
|
let surface = run_glagol([
|
|
OsStr::new("--inspect-lowering=surface"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect std string concat surface lowering", &surface);
|
|
let surface_stdout = String::from_utf8_lossy(&surface.stdout);
|
|
assert!(
|
|
surface_stdout.matches("call std.string.concat").count() == 2,
|
|
"surface lowering did not show concat calls\nstdout:\n{}",
|
|
surface_stdout
|
|
);
|
|
|
|
let checked = run_glagol([
|
|
OsStr::new("--inspect-lowering=checked"),
|
|
fixture.as_os_str(),
|
|
]);
|
|
assert_success("inspect std string concat checked lowering", &checked);
|
|
let checked_stdout = String::from_utf8_lossy(&checked.stdout);
|
|
assert!(
|
|
checked_stdout
|
|
.matches("call std.string.concat : string")
|
|
.count()
|
|
== 2,
|
|
"checked lowering did not show typed concat calls\nstdout:\n{}",
|
|
checked_stdout
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_string_concat_keeps_plus_deferred_rejects_alias_and_checks_calls() {
|
|
let cases = [
|
|
(
|
|
"std-string-concat-left-type",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> string
|
|
(std.string.concat 1 "a"))
|
|
"#,
|
|
"TypeMismatch",
|
|
),
|
|
(
|
|
"std-string-concat-arity",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> string
|
|
(std.string.concat "a"))
|
|
"#,
|
|
"ArityMismatch",
|
|
),
|
|
(
|
|
"legacy-string-concat-rejected",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> string
|
|
(string_concat "a" "b"))
|
|
"#,
|
|
"UnknownFunction",
|
|
),
|
|
(
|
|
"private-runtime-helper-shadow-rejected",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn __glagol_string_concat ((left string) (right string)) -> string
|
|
"intercepted")
|
|
|
|
(fn main () -> string
|
|
(std.string.concat "a" "b"))
|
|
"#,
|
|
"DuplicateFunction",
|
|
),
|
|
(
|
|
"plus-string-concat-still-deferred",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> string
|
|
(+ "a" "b"))
|
|
"#,
|
|
"UnsupportedStringConcatenation",
|
|
),
|
|
];
|
|
|
|
for (name, source, diagnostic) in cases {
|
|
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 invalid concat case `{}`\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stderr.contains(diagnostic),
|
|
"diagnostic `{}` was not reported for `{}`\nstderr:\n{}",
|
|
diagnostic,
|
|
name,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"rejected compile wrote stdout:\n{}",
|
|
stdout
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn standard_string_parse_i32_result_emits_i64_runtime_decode_shape() {
|
|
let fixture = write_fixture(
|
|
"std-string-parse-i32-result-shape",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn parse_or_code ((text string)) -> i32
|
|
(match (std.string.parse_i32_result text)
|
|
((ok value)
|
|
value)
|
|
((err code)
|
|
code)))
|
|
|
|
(fn main () -> i32
|
|
(parse_or_code "42"))
|
|
"#,
|
|
);
|
|
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 rejected std string parse_i32_result fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare i64 @__glagol_string_parse_i32_result(ptr)")
|
|
&& stdout.contains("call i64 @__glagol_string_parse_i32_result(ptr %text)")
|
|
&& stdout.contains("lshr i64 %")
|
|
&& stdout.contains("trunc i64 %")
|
|
&& stdout.contains("insertvalue { i1, i32 }")
|
|
&& stdout.contains("extractvalue { i1, i32 }")
|
|
&& !stdout.contains("@std.string.parse_i32_result"),
|
|
"LLVM output did not contain expected parse_i32_result decode shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn test_runner_executes_standard_string_parse_i32_result_boundaries() {
|
|
let non_ascii_digit = {
|
|
let path = env::temp_dir().join(format!(
|
|
"glagol-string-parse-non-ascii-digit-{}-{}.txt",
|
|
std::process::id(),
|
|
NEXT_FIXTURE_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
fs::write(&path, "\u{0661}")
|
|
.unwrap_or_else(|err| panic!("write `{}`: {}", path.display(), err));
|
|
path
|
|
};
|
|
let source = format!(
|
|
r#"
|
|
(module main)
|
|
|
|
(test "parse ok positive"
|
|
(= (unwrap_ok (std.string.parse_i32_result "42")) 42))
|
|
|
|
(test "parse ok negative"
|
|
(= (unwrap_ok (std.string.parse_i32_result "-7")) -7))
|
|
|
|
(test "parse ok min i32"
|
|
(= (unwrap_ok (std.string.parse_i32_result "-2147483648")) -2147483648))
|
|
|
|
(test "parse ok max i32"
|
|
(= (unwrap_ok (std.string.parse_i32_result "2147483647")) 2147483647))
|
|
|
|
(test "parse empty err one"
|
|
(= (unwrap_err (std.string.parse_i32_result "")) 1))
|
|
|
|
(test "parse lone minus err one"
|
|
(= (unwrap_err (std.string.parse_i32_result "-")) 1))
|
|
|
|
(test "parse plus sign err one"
|
|
(= (unwrap_err (std.string.parse_i32_result "+1")) 1))
|
|
|
|
(test "parse leading whitespace err one"
|
|
(= (unwrap_err (std.string.parse_i32_result " 1")) 1))
|
|
|
|
(test "parse trailing byte err one"
|
|
(= (unwrap_err (std.string.parse_i32_result "42\n")) 1))
|
|
|
|
(test "parse embedded nondigit err one"
|
|
(= (unwrap_err (std.string.parse_i32_result "4x")) 1))
|
|
|
|
(test "parse underscore err one"
|
|
(= (unwrap_err (std.string.parse_i32_result "1_0")) 1))
|
|
|
|
(test "parse prefix err one"
|
|
(= (unwrap_err (std.string.parse_i32_result "0x10")) 1))
|
|
|
|
(test "parse overflow err one"
|
|
(= (unwrap_err (std.string.parse_i32_result "2147483648")) 1))
|
|
|
|
(test "parse underflow err one"
|
|
(= (unwrap_err (std.string.parse_i32_result "-2147483649")) 1))
|
|
|
|
(test "parse non ascii digit err one"
|
|
(= (unwrap_err (std.string.parse_i32_result (unwrap_ok (std.fs.read_text_result "{}")))) 1))
|
|
"#,
|
|
slovo_path(&non_ascii_digit)
|
|
);
|
|
let fixture = write_fixture("std-string-parse-i32-result-test-runner", &source);
|
|
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
assert_success("run std string parse_i32_result tests", &output);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&output.stdout),
|
|
concat!(
|
|
"test \"parse ok positive\" ... ok\n",
|
|
"test \"parse ok negative\" ... ok\n",
|
|
"test \"parse ok min i32\" ... ok\n",
|
|
"test \"parse ok max i32\" ... ok\n",
|
|
"test \"parse empty err one\" ... ok\n",
|
|
"test \"parse lone minus err one\" ... ok\n",
|
|
"test \"parse plus sign err one\" ... ok\n",
|
|
"test \"parse leading whitespace err one\" ... ok\n",
|
|
"test \"parse trailing byte err one\" ... ok\n",
|
|
"test \"parse embedded nondigit err one\" ... ok\n",
|
|
"test \"parse underscore err one\" ... ok\n",
|
|
"test \"parse prefix err one\" ... ok\n",
|
|
"test \"parse overflow err one\" ... ok\n",
|
|
"test \"parse underflow err one\" ... ok\n",
|
|
"test \"parse non ascii digit err one\" ... ok\n",
|
|
"15 test(s) passed\n",
|
|
),
|
|
"parse_i32_result test runner stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_string_parse_i32_result_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!(
|
|
"skipping std string parse_i32_result runtime smoke: set GLAGOL_CLANG or install clang"
|
|
);
|
|
return;
|
|
};
|
|
|
|
let fixture = write_fixture(
|
|
"std-string-parse-i32-result-runtime-smoke",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_i32 (unwrap_ok (std.string.parse_i32_result "42")))
|
|
(std.io.print_i32 (unwrap_ok (std.string.parse_i32_result "-7")))
|
|
(unwrap_err (std.string.parse_i32_result "42x")))
|
|
"#,
|
|
);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success(
|
|
"compile std string parse_i32_result runtime smoke",
|
|
&compile,
|
|
);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "std-string-parse-i32-result", &compile.stdout);
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(1),
|
|
"std string parse_i32_result runtime smoke 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),
|
|
"42\n-7\n",
|
|
"std string parse_i32_result runtime smoke stdout drifted"
|
|
);
|
|
assert!(
|
|
run.stderr.is_empty(),
|
|
"std string parse_i32_result runtime smoke wrote stderr:\n{}",
|
|
String::from_utf8_lossy(&run.stderr)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn print_string_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping string runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture = write_fixture(
|
|
"print-string-runtime-smoke",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(print_string "hello from glagol")
|
|
0)
|
|
"#,
|
|
);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile string runtime smoke", &compile);
|
|
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let temp_dir = env::temp_dir().join(format!(
|
|
"glagol-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 ir_path = temp_dir.join("string-runtime.ll");
|
|
let exe_path = temp_dir.join("string-runtime");
|
|
fs::write(&ir_path, &compile.stdout)
|
|
.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 string runtime smoke", &clang_output);
|
|
|
|
let run = Command::new(&exe_path)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", exe_path.display(), err));
|
|
assert_success("run string runtime smoke", &run);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stdout),
|
|
"hello from glagol\n",
|
|
"runtime stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn promoted_string_print_fixture_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!(
|
|
"skipping promoted string fixture runtime smoke: set GLAGOL_CLANG or install clang"
|
|
);
|
|
return;
|
|
};
|
|
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/string-print.slo");
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile promoted string-print fixture", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "promoted-string-print", &compile.stdout);
|
|
assert_success("run promoted string-print fixture", &run);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stdout),
|
|
"hello\nline\nquote\"slash\\tab\t\n",
|
|
"promoted fixture runtime stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn v1_2_string_and_bool_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping v1.2 string/bool runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture = write_fixture(
|
|
"v1-2-string-bool-runtime-smoke",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn label () -> string
|
|
"slovo")
|
|
|
|
(fn main () -> i32
|
|
(print_bool (= (label) "slovo"))
|
|
(print_bool (= (label) "other"))
|
|
(print_i32 (string_len (label)))
|
|
0)
|
|
"#,
|
|
);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile v1.2 string/bool runtime smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "v1-2-string-bool", &compile.stdout);
|
|
assert_success("run v1.2 string/bool runtime smoke", &run);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stdout),
|
|
"true\nfalse\n5\n",
|
|
"v1.2 string/bool runtime stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_runtime_fixture_uses_legacy_llvm_symbols_and_tests_pass() {
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/standard-runtime.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(),
|
|
"compile standard-runtime fixture failed\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("call void @print_string(ptr %")
|
|
&& stdout.contains("call void @print_bool(i1 %")
|
|
&& stdout.contains("call void @print_i32(i32 %")
|
|
&& stdout.contains("call i32 @string_len(ptr ")
|
|
&& !stdout.contains("@std."),
|
|
"standard-runtime LLVM did not lower through legacy symbols\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.is_empty(),
|
|
"standard-runtime compile wrote stderr:\n{}",
|
|
stderr
|
|
);
|
|
|
|
let run = run_glagol([OsStr::new("--run-tests"), fixture.as_os_str()]);
|
|
assert_success("run standard-runtime tests", &run);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stdout),
|
|
concat!(
|
|
"test \"std string equality\" ... ok\n",
|
|
"test \"std string byte length\" ... ok\n",
|
|
"2 test(s) passed\n",
|
|
),
|
|
"standard-runtime test runner stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_runtime_print_calls_are_rejected_by_test_runner() {
|
|
let cases = [
|
|
("std-print-i32-rejected", "(std.io.print_i32 1)"),
|
|
(
|
|
"std-print-string-rejected",
|
|
"(std.io.print_string \"test\")",
|
|
),
|
|
("std-print-bool-rejected", "(std.io.print_bool true)"),
|
|
];
|
|
|
|
for (name, print_call) in cases {
|
|
let source = format!(
|
|
r#"
|
|
(module main)
|
|
|
|
(fn noisy () -> i32
|
|
{}
|
|
1)
|
|
|
|
(test "std print rejected"
|
|
(= (noisy) 1))
|
|
"#,
|
|
print_call
|
|
);
|
|
let fixture = write_fixture(name, &source);
|
|
|
|
let output = run_glagol([OsStr::new("--run-tests"), 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),
|
|
"std print test runner rejection `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"std print test runner rejection `{}` wrote stdout:\n{}",
|
|
name,
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("UnsupportedTestExpression"),
|
|
"std print test runner rejection `{}` diagnostic drifted\nstderr:\n{}",
|
|
name,
|
|
stderr
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn standard_runtime_fixture_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping standard-runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("../examples/standard-runtime.slo");
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile standard-runtime smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "standard-runtime", &compile.stdout);
|
|
assert_success("run standard-runtime smoke", &run);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stdout),
|
|
"standard\ntrue\n8\n",
|
|
"standard-runtime stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_string_concat_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping std string concat runtime smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let fixture = write_fixture(
|
|
"std-string-concat-runtime-smoke",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_string (std.string.concat "hello" "!"))
|
|
(std.io.print_i32 (std.string.len (std.string.concat "ab" "c")))
|
|
0)
|
|
"#,
|
|
);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile std string concat runtime smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "std-string-concat", &compile.stdout);
|
|
assert_success("run std string concat runtime smoke", &run);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stdout),
|
|
"hello!\n3\n",
|
|
"std string concat runtime stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_string_concat_allocation_failure_traps_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!(
|
|
"skipping std string concat allocation trap smoke: set GLAGOL_CLANG or install clang"
|
|
);
|
|
return;
|
|
};
|
|
|
|
let manifest = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let temp_dir = env::temp_dir().join(format!(
|
|
"glagol-string-runtime-allocation-trap-{}-{}",
|
|
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 probe = temp_dir.join("concat-allocation-trap.c");
|
|
let exe = temp_dir.join("concat-allocation-trap");
|
|
fs::write(
|
|
&probe,
|
|
r#"#include <stddef.h>
|
|
|
|
char *__glagol_string_concat(const char *left, const char *right);
|
|
|
|
void *__wrap_malloc(size_t size) {
|
|
(void)size;
|
|
return NULL;
|
|
}
|
|
|
|
int main(void) {
|
|
(void)__glagol_string_concat("a", "b");
|
|
return 0;
|
|
}
|
|
"#,
|
|
)
|
|
.unwrap_or_else(|err| panic!("write `{}`: {}", probe.display(), err));
|
|
|
|
let runtime = manifest.join("../runtime/runtime.c");
|
|
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 allocation trap probe", &clang_output);
|
|
|
|
let run = Command::new(&exe)
|
|
.output()
|
|
.unwrap_or_else(|err| panic!("run `{}`: {}", exe.display(), err));
|
|
assert_eq!(
|
|
run.status.code(),
|
|
Some(1),
|
|
"allocation trap 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.stderr),
|
|
"slovo runtime error: string allocation failed\n",
|
|
"allocation trap stderr drifted"
|
|
);
|
|
assert!(
|
|
run.stdout.is_empty(),
|
|
"allocation trap wrote stdout:\n{}",
|
|
String::from_utf8_lossy(&run.stdout)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn v1_2_runtime_traps_exit_one_with_contract_messages_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!("skipping v1.2 runtime trap smoke: set GLAGOL_CLANG or install clang");
|
|
return;
|
|
};
|
|
|
|
let cases = [
|
|
(
|
|
"array-bounds",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn at ((i i32)) -> i32
|
|
(index (array i32 7) i))
|
|
|
|
(fn main () -> i32
|
|
(at 1))
|
|
"#,
|
|
"slovo runtime error: array index out of bounds\n",
|
|
),
|
|
(
|
|
"unwrap-some",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(unwrap_some (none i32)))
|
|
"#,
|
|
"slovo runtime error: unwrap_some on none\n",
|
|
),
|
|
(
|
|
"unwrap-ok",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(unwrap_ok (err i32 i32 1)))
|
|
"#,
|
|
"slovo runtime error: unwrap_ok on err\n",
|
|
),
|
|
(
|
|
"unwrap-err",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(unwrap_err (ok i32 i32 1)))
|
|
"#,
|
|
"slovo runtime error: unwrap_err on ok\n",
|
|
),
|
|
];
|
|
|
|
for (name, source, expected_stderr) in cases {
|
|
let fixture = write_fixture(name, source);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile v1.2 runtime trap smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, name, &compile.stdout);
|
|
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 v1_2_test_runner_reports_runtime_traps_with_contract_messages() {
|
|
let cases = [
|
|
(
|
|
"test-array-bounds",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn at ((i i32)) -> i32
|
|
(index (array i32 7) i))
|
|
|
|
(test "array trap"
|
|
(= (at 1) 0))
|
|
"#,
|
|
"slovo runtime error: array index out of bounds",
|
|
),
|
|
(
|
|
"test-unwrap-some",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "unwrap some trap"
|
|
(= (unwrap_some (none i32)) 0))
|
|
"#,
|
|
"slovo runtime error: unwrap_some on none",
|
|
),
|
|
(
|
|
"test-unwrap-ok",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "unwrap ok trap"
|
|
(= (unwrap_ok (err i32 i32 1)) 0))
|
|
"#,
|
|
"slovo runtime error: unwrap_ok on err",
|
|
),
|
|
(
|
|
"test-unwrap-err",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "unwrap err trap"
|
|
(= (unwrap_err (ok i32 i32 1)) 0))
|
|
"#,
|
|
"slovo runtime error: unwrap_err on ok",
|
|
),
|
|
];
|
|
|
|
for (name, source, expected_message) 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 trap `{}` exit code drifted\nstdout:\n{}\nstderr:\n{}",
|
|
name,
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.is_empty(),
|
|
"test runner trap `{}` wrote stdout:\n{}",
|
|
name,
|
|
stdout
|
|
);
|
|
assert!(
|
|
stderr.contains("TestRuntimeTrap")
|
|
&& stderr.contains("test trapped")
|
|
&& stderr.contains(expected_message),
|
|
"test runner trap `{}` diagnostic drifted\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-runtime-{}-{}-{}.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 slovo_path(path: &Path) -> String {
|
|
path.to_string_lossy()
|
|
.replace('\\', "\\\\")
|
|
.replace('"', "\\\"")
|
|
}
|
|
|
|
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 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-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 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 string 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 mut paths = vec![lib64, lib];
|
|
|
|
if let Some(existing) = env::var_os("LD_LIBRARY_PATH") {
|
|
paths.extend(env::split_paths(&existing));
|
|
}
|
|
|
|
let joined = env::join_paths(paths).expect("join LD_LIBRARY_PATH");
|
|
command.env("LD_LIBRARY_PATH", joined);
|
|
}
|