310 lines
10 KiB
Rust
310 lines
10 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 standard_string_scanning_beta16_lowering_shape_uses_runtime_symbols() {
|
|
let fixture = write_fixture(
|
|
"std-string-scanning-beta16-lowering",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn byte_or_code ((text string) (index i32)) -> i32
|
|
(match (std.string.byte_at_result text index)
|
|
((ok value)
|
|
value)
|
|
((err code)
|
|
code)))
|
|
|
|
(fn slice_or_empty ((text string) (start i32) (count i32)) -> string
|
|
(match (std.string.slice_result text start count)
|
|
((ok value)
|
|
value)
|
|
((err code)
|
|
"")))
|
|
|
|
(fn has_edges ((text string)) -> bool
|
|
(if (std.string.starts_with text "slo")
|
|
(std.string.ends_with text "vo")
|
|
false))
|
|
|
|
(fn main () -> i32
|
|
(if (has_edges (slice_or_empty "slovo" 0 5))
|
|
(byte_or_code "slovo" 3)
|
|
1))
|
|
"#,
|
|
);
|
|
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 beta16 string scanning fixture\nstdout:\n{}\nstderr:\n{}",
|
|
stdout,
|
|
stderr
|
|
);
|
|
assert!(
|
|
stdout.contains("declare i64 @__glagol_string_byte_at_result(ptr, i32)")
|
|
&& stdout.contains("declare ptr @__glagol_string_slice_result(ptr, i32, i32)")
|
|
&& stdout.contains("declare i1 @__glagol_string_starts_with(ptr, ptr)")
|
|
&& stdout.contains("declare i1 @__glagol_string_ends_with(ptr, ptr)")
|
|
&& stdout.contains("call i64 @__glagol_string_byte_at_result(ptr %text, i32 %index)")
|
|
&& stdout.contains(
|
|
"call ptr @__glagol_string_slice_result(ptr %text, i32 %start, i32 %count)"
|
|
)
|
|
&& stdout.contains("call i1 @__glagol_string_starts_with(ptr %text, ptr @.str.")
|
|
&& stdout.contains("call i1 @__glagol_string_ends_with(ptr %text, ptr @.str.")
|
|
&& stdout.contains("icmp ne ptr %")
|
|
&& stdout.contains("insertvalue { i1, ptr, i32 }")
|
|
&& !stdout.contains("@std.string.byte_at_result")
|
|
&& !stdout.contains("@std.string.slice_result")
|
|
&& !stdout.contains("@std.string.starts_with")
|
|
&& !stdout.contains("@std.string.ends_with"),
|
|
"LLVM output did not contain expected beta16 string runtime shape\nstdout:\n{}",
|
|
stdout
|
|
);
|
|
assert!(stderr.is_empty(), "compiler wrote stderr:\n{}", stderr);
|
|
}
|
|
|
|
#[test]
|
|
fn test_runner_executes_standard_string_scanning_beta16_boundaries() {
|
|
let fixture = write_fixture(
|
|
"std-string-scanning-beta16-test-runner",
|
|
r#"
|
|
(module main)
|
|
|
|
(test "byte first ok"
|
|
(= (unwrap_ok (std.string.byte_at_result "slovo" 0)) 115))
|
|
|
|
(test "byte last ok"
|
|
(= (unwrap_ok (std.string.byte_at_result "slovo" 4)) 111))
|
|
|
|
(test "byte empty err"
|
|
(= (unwrap_err (std.string.byte_at_result "" 0)) 1))
|
|
|
|
(test "byte negative err"
|
|
(= (unwrap_err (std.string.byte_at_result "slovo" -1)) 1))
|
|
|
|
(test "byte end err"
|
|
(= (unwrap_err (std.string.byte_at_result "slovo" 5)) 1))
|
|
|
|
(test "slice middle ok"
|
|
(= (unwrap_ok (std.string.slice_result "slovo" 1 3)) "lov"))
|
|
|
|
(test "slice zero count ok"
|
|
(= (unwrap_ok (std.string.slice_result "slovo" 2 0)) ""))
|
|
|
|
(test "slice len zero ok"
|
|
(= (unwrap_ok (std.string.slice_result "slovo" 5 0)) ""))
|
|
|
|
(test "slice full ok"
|
|
(= (unwrap_ok (std.string.slice_result "slovo" 0 5)) "slovo"))
|
|
|
|
(test "slice negative start err"
|
|
(= (unwrap_err (std.string.slice_result "slovo" -1 1)) 1))
|
|
|
|
(test "slice negative count err"
|
|
(= (unwrap_err (std.string.slice_result "slovo" 1 -1)) 1))
|
|
|
|
(test "slice overrun err"
|
|
(= (unwrap_err (std.string.slice_result "slovo" 4 2)) 1))
|
|
|
|
(test "slice start past end err"
|
|
(= (unwrap_err (std.string.slice_result "slovo" 6 0)) 1))
|
|
|
|
(test "starts true empty"
|
|
(if (std.string.starts_with "slovo" "slo")
|
|
(std.string.starts_with "slovo" "")
|
|
false))
|
|
|
|
(test "starts false middle"
|
|
(= (std.string.starts_with "slovo" "ovo") false))
|
|
|
|
(test "ends true empty"
|
|
(if (std.string.ends_with "slovo" "ovo")
|
|
(std.string.ends_with "slovo" "")
|
|
false))
|
|
|
|
(test "ends false prefix"
|
|
(= (std.string.ends_with "slovo" "slo") false))
|
|
"#,
|
|
);
|
|
let output = run_glagol([OsStr::new("test"), fixture.as_os_str()]);
|
|
assert_success("run beta16 string scanning tests", &output);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&output.stdout),
|
|
concat!(
|
|
"test \"byte first ok\" ... ok\n",
|
|
"test \"byte last ok\" ... ok\n",
|
|
"test \"byte empty err\" ... ok\n",
|
|
"test \"byte negative err\" ... ok\n",
|
|
"test \"byte end err\" ... ok\n",
|
|
"test \"slice middle ok\" ... ok\n",
|
|
"test \"slice zero count ok\" ... ok\n",
|
|
"test \"slice len zero ok\" ... ok\n",
|
|
"test \"slice full ok\" ... ok\n",
|
|
"test \"slice negative start err\" ... ok\n",
|
|
"test \"slice negative count err\" ... ok\n",
|
|
"test \"slice overrun err\" ... ok\n",
|
|
"test \"slice start past end err\" ... ok\n",
|
|
"test \"starts true empty\" ... ok\n",
|
|
"test \"starts false middle\" ... ok\n",
|
|
"test \"ends true empty\" ... ok\n",
|
|
"test \"ends false prefix\" ... ok\n",
|
|
"17 test(s) passed\n",
|
|
),
|
|
"beta16 string scanning test runner stdout drifted"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn standard_string_scanning_beta16_runtime_smoke_when_clang_is_available() {
|
|
let Some(clang) = find_clang() else {
|
|
eprintln!(
|
|
"skipping beta16 string scanning runtime smoke: set GLAGOL_CLANG or install clang"
|
|
);
|
|
return;
|
|
};
|
|
|
|
let fixture = write_fixture(
|
|
"std-string-scanning-beta16-runtime-smoke",
|
|
r#"
|
|
(module main)
|
|
|
|
(fn main () -> i32
|
|
(std.io.print_i32 (unwrap_ok (std.string.byte_at_result "slovo" 3)))
|
|
(std.io.print_string (unwrap_ok (std.string.slice_result "slovo" 1 3)))
|
|
(std.io.print_bool (std.string.starts_with "slovo" "slo"))
|
|
(std.io.print_bool (std.string.ends_with "slovo" "ovo"))
|
|
(std.io.print_i32 (unwrap_err (std.string.byte_at_result "slovo" 5)))
|
|
(std.io.print_i32 (unwrap_err (std.string.slice_result "slovo" 4 2)))
|
|
0)
|
|
"#,
|
|
);
|
|
let compile = run_glagol([fixture.as_os_str()]);
|
|
assert_success("compile beta16 string scanning runtime smoke", &compile);
|
|
|
|
let run = compile_and_run_with_runtime(&clang, "std-string-scanning-beta16", &compile.stdout);
|
|
assert_success("run beta16 string scanning runtime smoke", &run);
|
|
assert_eq!(
|
|
String::from_utf8_lossy(&run.stdout),
|
|
"118\nlov\ntrue\ntrue\n1\n1\n",
|
|
"beta16 string scanning runtime stdout drifted"
|
|
);
|
|
}
|
|
|
|
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-standard-string-scanning-beta16-{}-{}-{}.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 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-standard-string-scanning-beta16-{}-{}",
|
|
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 beta16 string scanning 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);
|
|
}
|