429 lines
16 KiB
Rust
429 lines
16 KiB
Rust
use std::{
|
|
env,
|
|
ffi::OsStr,
|
|
fs,
|
|
path::{Path, PathBuf},
|
|
process::{Command, Output},
|
|
sync::atomic::{AtomicUsize, Ordering},
|
|
};
|
|
|
|
static NEXT_TEMP_ID: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
const MISSING_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_UNLIKELY_MISSING";
|
|
const PRESENT_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT";
|
|
const PRESENT_ENV_VALUE: &str = "glagol-std-import-env-alpha-value";
|
|
const PRESENT_I32_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I32";
|
|
const PRESENT_I32_ENV_VALUE: &str = "42";
|
|
const PRESENT_U32_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U32";
|
|
const PRESENT_U32_ENV_VALUE: &str = "42";
|
|
const PRESENT_I64_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_I64";
|
|
const PRESENT_I64_ENV_VALUE: &str = "42000000000";
|
|
const PRESENT_U64_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_U64";
|
|
const PRESENT_U64_ENV_VALUE: &str = "4294967296";
|
|
const PRESENT_F64_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_F64";
|
|
const PRESENT_F64_ENV_VALUE: &str = "42.5";
|
|
const PRESENT_BOOL_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_PRESENT_BOOL";
|
|
const PRESENT_BOOL_ENV_VALUE: &str = "true";
|
|
const INVALID_ENV_NAME: &str = "GLAGOL_STD_IMPORT_ENV_ALPHA_INVALID";
|
|
const INVALID_ENV_VALUE: &str = "bad";
|
|
|
|
const EXPECTED_STD_TIME_OUTPUT: &str = concat!(
|
|
"test \"explicit std time monotonic facade\" ... ok\n",
|
|
"test \"explicit std time sleep zero facade\" ... ok\n",
|
|
"test \"explicit std time facade all\" ... ok\n",
|
|
"3 test(s) passed\n",
|
|
);
|
|
|
|
const EXPECTED_STD_RANDOM_OUTPUT: &str = concat!(
|
|
"test \"explicit std random i32 non-negative facade\" ... ok\n",
|
|
"test \"explicit std random facade all\" ... ok\n",
|
|
"2 test(s) passed\n",
|
|
);
|
|
|
|
const EXPECTED_STD_ENV_OUTPUT: &str = concat!(
|
|
"test \"explicit std env get missing facade\" ... ok\n",
|
|
"test \"explicit std env get result missing facade\" ... ok\n",
|
|
"test \"explicit std env has missing facade\" ... ok\n",
|
|
"test \"explicit std env get or missing facade\" ... ok\n",
|
|
"test \"explicit std env get option missing facade\" ... ok\n",
|
|
"test \"explicit std env has present facade\" ... ok\n",
|
|
"test \"explicit std env get or present facade\" ... ok\n",
|
|
"test \"explicit std env get option present facade\" ... ok\n",
|
|
"test \"explicit std env get i32 result present facade\" ... ok\n",
|
|
"test \"explicit std env get i32 or zero invalid facade\" ... ok\n",
|
|
"test \"explicit std env get u32 result present facade\" ... ok\n",
|
|
"test \"explicit std env get u32 or zero invalid facade\" ... ok\n",
|
|
"test \"explicit std env get i64 result present facade\" ... ok\n",
|
|
"test \"explicit std env get i64 or zero missing facade\" ... ok\n",
|
|
"test \"explicit std env get u64 result present facade\" ... ok\n",
|
|
"test \"explicit std env get u64 or zero missing facade\" ... ok\n",
|
|
"test \"explicit std env get f64 result present facade\" ... ok\n",
|
|
"test \"explicit std env get f64 or zero invalid facade\" ... ok\n",
|
|
"test \"explicit std env get bool result present facade\" ... ok\n",
|
|
"test \"explicit std env get bool or false invalid facade\" ... ok\n",
|
|
"test \"explicit std env typed option facade\" ... ok\n",
|
|
"test \"explicit std env typed custom fallback facade\" ... ok\n",
|
|
"test \"explicit std env facade all\" ... ok\n",
|
|
"23 test(s) passed\n",
|
|
);
|
|
|
|
const EXPECTED_STD_FS_OUTPUT: &str = concat!(
|
|
"test \"explicit std fs write text status facade\" ... ok\n",
|
|
"test \"explicit std fs read text facade\" ... ok\n",
|
|
"test \"explicit std fs write text result facade\" ... ok\n",
|
|
"test \"explicit std fs read text result facade\" ... ok\n",
|
|
"test \"explicit std fs read text via handle facade\" ... ok\n",
|
|
"test \"explicit std fs open read close handle facade\" ... ok\n",
|
|
"test \"explicit std fs closed handle read err facade\" ... ok\n",
|
|
"test \"explicit std fs invalid close err facade\" ... ok\n",
|
|
"test \"explicit std fs exists file facade\" ... ok\n",
|
|
"test \"explicit std fs exists missing facade\" ... ok\n",
|
|
"test \"explicit std fs create dir result facade\" ... ok\n",
|
|
"test \"explicit std fs is dir facade\" ... ok\n",
|
|
"test \"explicit std fs create dir ok facade\" ... ok\n",
|
|
"test \"explicit std fs remove file result facade\" ... ok\n",
|
|
"test \"explicit std fs remove file ok facade\" ... ok\n",
|
|
"test \"explicit std fs read text option missing facade\" ... ok\n",
|
|
"test \"explicit std fs read text option present facade\" ... ok\n",
|
|
"test \"explicit std fs read text or missing facade\" ... ok\n",
|
|
"test \"explicit std fs read text or present facade\" ... ok\n",
|
|
"test \"explicit std fs write text ok facade\" ... ok\n",
|
|
"test \"explicit std fs read i32 result present facade\" ... ok\n",
|
|
"test \"explicit std fs read i32 or zero invalid facade\" ... ok\n",
|
|
"test \"explicit std fs read u32 result present facade\" ... ok\n",
|
|
"test \"explicit std fs read u32 or zero invalid facade\" ... ok\n",
|
|
"test \"explicit std fs read i64 result present facade\" ... ok\n",
|
|
"test \"explicit std fs read i64 or zero missing facade\" ... ok\n",
|
|
"test \"explicit std fs read u64 result present facade\" ... ok\n",
|
|
"test \"explicit std fs read u64 or zero missing facade\" ... ok\n",
|
|
"test \"explicit std fs read f64 result present facade\" ... ok\n",
|
|
"test \"explicit std fs read f64 or zero invalid facade\" ... ok\n",
|
|
"test \"explicit std fs read bool result present facade\" ... ok\n",
|
|
"test \"explicit std fs read bool or false invalid facade\" ... ok\n",
|
|
"test \"explicit std fs typed option facade\" ... ok\n",
|
|
"test \"explicit std fs typed custom fallback facade\" ... ok\n",
|
|
"test \"explicit std fs facade all\" ... ok\n",
|
|
"35 test(s) passed\n",
|
|
);
|
|
|
|
const EXPECTED_STD_PROCESS_OUTPUT: &str = concat!(
|
|
"test \"explicit std process argc facade\" ... ok\n",
|
|
"test \"explicit std process arg result oob facade\" ... ok\n",
|
|
"test \"explicit std process arg option facade\" ... ok\n",
|
|
"test \"explicit std process has arg facade\" ... ok\n",
|
|
"test \"explicit std process arg fallback missing facade\" ... ok\n",
|
|
"test \"explicit std process arg fallback present facade\" ... ok\n",
|
|
"test \"explicit std process arg i32 missing facade\" ... ok\n",
|
|
"test \"explicit std process arg i32 invalid fallback facade\" ... ok\n",
|
|
"test \"explicit std process arg u32 missing facade\" ... ok\n",
|
|
"test \"explicit std process arg u32 invalid fallback facade\" ... ok\n",
|
|
"test \"explicit std process arg i64 missing facade\" ... ok\n",
|
|
"test \"explicit std process arg i64 invalid fallback facade\" ... ok\n",
|
|
"test \"explicit std process arg u64 missing facade\" ... ok\n",
|
|
"test \"explicit std process arg u64 invalid fallback facade\" ... ok\n",
|
|
"test \"explicit std process arg f64 missing facade\" ... ok\n",
|
|
"test \"explicit std process arg f64 invalid fallback facade\" ... ok\n",
|
|
"test \"explicit std process arg bool missing facade\" ... ok\n",
|
|
"test \"explicit std process arg bool invalid fallback facade\" ... ok\n",
|
|
"test \"explicit std process typed option facade\" ... ok\n",
|
|
"test \"explicit std process typed custom fallback facade\" ... ok\n",
|
|
"test \"explicit std process facade all\" ... ok\n",
|
|
"21 test(s) passed\n",
|
|
);
|
|
|
|
#[test]
|
|
fn explicit_std_time_import_loads_repo_root_standard_source() {
|
|
assert_host_facade_project(
|
|
"time",
|
|
&["monotonic_ms", "sleep_ms_zero"],
|
|
EXPECTED_STD_TIME_OUTPUT,
|
|
|_| {},
|
|
None,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_std_random_import_loads_repo_root_standard_source() {
|
|
assert_host_facade_project(
|
|
"random",
|
|
&["random_i32", "random_i32_non_negative"],
|
|
EXPECTED_STD_RANDOM_OUTPUT,
|
|
|_| {},
|
|
None,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_std_env_import_loads_repo_root_standard_source() {
|
|
assert_host_facade_project(
|
|
"env",
|
|
&[
|
|
"get",
|
|
"get_result",
|
|
"get_option",
|
|
"has",
|
|
"get_or",
|
|
"get_i32_result",
|
|
"get_i32_option",
|
|
"get_i32_or_zero",
|
|
"get_i32_or",
|
|
"get_u32_result",
|
|
"get_u32_option",
|
|
"get_u32_or_zero",
|
|
"get_u32_or",
|
|
"get_i64_result",
|
|
"get_i64_option",
|
|
"get_i64_or_zero",
|
|
"get_i64_or",
|
|
"get_u64_result",
|
|
"get_u64_option",
|
|
"get_u64_or_zero",
|
|
"get_u64_or",
|
|
"get_f64_result",
|
|
"get_f64_option",
|
|
"get_f64_or_zero",
|
|
"get_f64_or",
|
|
"get_bool_result",
|
|
"get_bool_option",
|
|
"get_bool_or_false",
|
|
"get_bool_or",
|
|
],
|
|
EXPECTED_STD_ENV_OUTPUT,
|
|
|command| {
|
|
command.env_remove(MISSING_ENV_NAME);
|
|
command.env(PRESENT_ENV_NAME, PRESENT_ENV_VALUE);
|
|
command.env(PRESENT_I32_ENV_NAME, PRESENT_I32_ENV_VALUE);
|
|
command.env(PRESENT_U32_ENV_NAME, PRESENT_U32_ENV_VALUE);
|
|
command.env(PRESENT_I64_ENV_NAME, PRESENT_I64_ENV_VALUE);
|
|
command.env(PRESENT_U64_ENV_NAME, PRESENT_U64_ENV_VALUE);
|
|
command.env(PRESENT_F64_ENV_NAME, PRESENT_F64_ENV_VALUE);
|
|
command.env(PRESENT_BOOL_ENV_NAME, PRESENT_BOOL_ENV_VALUE);
|
|
command.env(INVALID_ENV_NAME, INVALID_ENV_VALUE);
|
|
},
|
|
None,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_std_fs_import_loads_repo_root_standard_source() {
|
|
let cwd = temp_root("fs");
|
|
assert_host_facade_project(
|
|
"fs",
|
|
&[
|
|
"read_text",
|
|
"read_text_result",
|
|
"read_text_option",
|
|
"write_text_status",
|
|
"write_text_result",
|
|
"exists",
|
|
"is_file",
|
|
"is_dir",
|
|
"remove_file_result",
|
|
"create_dir_result",
|
|
"remove_file_ok",
|
|
"create_dir_ok",
|
|
"open_text_read_result",
|
|
"read_open_text_result",
|
|
"close_result",
|
|
"read_text_via_handle_result",
|
|
"close_ok",
|
|
"read_text_or",
|
|
"write_text_ok",
|
|
"read_i32_result",
|
|
"read_i32_option",
|
|
"read_i32_or_zero",
|
|
"read_i32_or",
|
|
"read_u32_result",
|
|
"read_u32_option",
|
|
"read_u32_or_zero",
|
|
"read_u32_or",
|
|
"read_i64_result",
|
|
"read_i64_option",
|
|
"read_i64_or_zero",
|
|
"read_i64_or",
|
|
"read_u64_result",
|
|
"read_u64_option",
|
|
"read_u64_or_zero",
|
|
"read_u64_or",
|
|
"read_f64_result",
|
|
"read_f64_option",
|
|
"read_f64_or_zero",
|
|
"read_f64_or",
|
|
"read_bool_result",
|
|
"read_bool_option",
|
|
"read_bool_or_false",
|
|
"read_bool_or",
|
|
],
|
|
EXPECTED_STD_FS_OUTPUT,
|
|
|_| {},
|
|
Some(&cwd),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn explicit_std_process_import_loads_repo_root_standard_source() {
|
|
assert_host_facade_project(
|
|
"process",
|
|
&[
|
|
"argc",
|
|
"arg",
|
|
"arg_result",
|
|
"arg_option",
|
|
"has_arg",
|
|
"arg_or",
|
|
"arg_or_empty",
|
|
"arg_i32_result",
|
|
"arg_i32_option",
|
|
"arg_i32_or_zero",
|
|
"arg_i32_or",
|
|
"arg_u32_result",
|
|
"arg_u32_option",
|
|
"arg_u32_or_zero",
|
|
"arg_u32_or",
|
|
"arg_i64_result",
|
|
"arg_i64_option",
|
|
"arg_i64_or_zero",
|
|
"arg_i64_or",
|
|
"arg_u64_result",
|
|
"arg_u64_option",
|
|
"arg_u64_or_zero",
|
|
"arg_u64_or",
|
|
"arg_f64_result",
|
|
"arg_f64_option",
|
|
"arg_f64_or_zero",
|
|
"arg_f64_or",
|
|
"arg_bool_result",
|
|
"arg_bool_option",
|
|
"arg_bool_or_false",
|
|
"arg_bool_or",
|
|
],
|
|
EXPECTED_STD_PROCESS_OUTPUT,
|
|
|_| {},
|
|
None,
|
|
);
|
|
}
|
|
|
|
fn assert_host_facade_project<F>(
|
|
module: &str,
|
|
helpers: &[&str],
|
|
expected: &str,
|
|
configure: F,
|
|
test_cwd: Option<&Path>,
|
|
) where
|
|
F: Fn(&mut Command),
|
|
{
|
|
let compiler_root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
let project = compiler_root.join(format!("../examples/projects/std-import-{}", module));
|
|
let slovo_module = compiler_root.join(format!("../lib/std/{}.slo", module));
|
|
|
|
assert!(
|
|
!project.join(format!("src/{}.slo", module)).exists(),
|
|
"std-import-{} must not carry a local {} module copy",
|
|
module,
|
|
module
|
|
);
|
|
assert!(
|
|
read(&project.join("src/main.slo"))
|
|
.starts_with(&format!("(module main)\n\n(import std.{} (", module)),
|
|
"std-import-{} must exercise explicit `std.{}` import syntax",
|
|
module,
|
|
module
|
|
);
|
|
|
|
let slovo_source = read(&slovo_module);
|
|
assert!(
|
|
slovo_source.starts_with(&format!("(module {} (export ", module)),
|
|
"repo-root Slovo std/{}.slo must export imported helpers directly",
|
|
module
|
|
);
|
|
if matches!(module, "env" | "fs" | "process") {
|
|
assert!(
|
|
slovo_source
|
|
.contains("(import std.result (ok_or_none_string ok_or_none_i32 ok_or_none_u32 ok_or_none_i64 ok_or_none_u64 ok_or_none_f64 ok_or_none_bool))"),
|
|
"Slovo std/{}.slo must compose option helpers through the exp-109 result bridge helpers",
|
|
module
|
|
);
|
|
}
|
|
for helper in helpers {
|
|
assert!(
|
|
slovo_source.contains(&format!("(fn {} ", helper)),
|
|
"Slovo std/{}.slo is missing helper `{}`",
|
|
module,
|
|
helper
|
|
);
|
|
}
|
|
|
|
let fmt = run_glagol_configured(
|
|
[
|
|
OsStr::new("fmt"),
|
|
OsStr::new("--check"),
|
|
project.as_os_str(),
|
|
],
|
|
Path::new(env!("CARGO_MANIFEST_DIR")),
|
|
|_| {},
|
|
);
|
|
assert_success("std host facade source search fmt --check", &fmt);
|
|
|
|
let check = run_glagol_configured(
|
|
[OsStr::new("check"), project.as_os_str()],
|
|
Path::new(env!("CARGO_MANIFEST_DIR")),
|
|
|_| {},
|
|
);
|
|
assert_success_stdout(check, "", "std host facade source search check");
|
|
|
|
let test = run_glagol_configured(
|
|
[OsStr::new("test"), project.as_os_str()],
|
|
test_cwd.unwrap_or(Path::new(env!("CARGO_MANIFEST_DIR"))),
|
|
configure,
|
|
);
|
|
assert_success_stdout(test, expected, "std host facade source search test");
|
|
}
|
|
|
|
fn run_glagol_configured<I, S, F>(args: I, cwd: &Path, configure: F) -> Output
|
|
where
|
|
I: IntoIterator<Item = S>,
|
|
S: AsRef<std::ffi::OsStr>,
|
|
F: FnOnce(&mut Command),
|
|
{
|
|
let mut command = Command::new(env!("CARGO_BIN_EXE_glagol"));
|
|
command.args(args).current_dir(cwd);
|
|
configure(&mut command);
|
|
command.output().expect("run glagol")
|
|
}
|
|
|
|
fn temp_root(name: &str) -> PathBuf {
|
|
let path = env::temp_dir().join(format!(
|
|
"glagol-standard-host-facade-source-search-{}-{}-{}",
|
|
name,
|
|
std::process::id(),
|
|
NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed)
|
|
));
|
|
let _ = fs::remove_dir_all(&path);
|
|
fs::create_dir_all(&path).unwrap_or_else(|err| panic!("create `{}`: {}", path.display(), err));
|
|
path
|
|
}
|
|
|
|
fn read(path: &Path) -> String {
|
|
fs::read_to_string(path).unwrap_or_else(|err| panic!("read `{}`: {}", path.display(), err))
|
|
}
|
|
|
|
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\nstatus: {:?}\nstdout:\n{}\nstderr:\n{}",
|
|
context,
|
|
output.status.code(),
|
|
stdout,
|
|
stderr
|
|
);
|
|
}
|
|
|
|
fn assert_success_stdout(output: Output, expected: &str, context: &str) {
|
|
assert_success(context, &output);
|
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
assert_eq!(stdout, expected, "{}", context);
|
|
}
|