use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
fn run(args: &[&str]) -> (bool, String, String) {
let out = ilo()
.args(args)
.output()
.unwrap_or_else(|e| panic!("failed to spawn ilo: {e}"));
(
out.status.success(),
String::from_utf8_lossy(&out.stdout).to_string(),
String::from_utf8_lossy(&out.stderr).to_string(),
)
}
fn write_temp(content: &str) -> (tempfile::TempDir, std::path::PathBuf) {
let dir = tempfile::tempdir().expect("tempdir");
let path = dir.path().join("prog.ilo");
std::fs::write(&path, content).expect("write temp ilo");
(dir, path)
}
#[test]
fn single_fn_file_runs_with_no_func_arg() {
let (_dir, path) = write_temp("f>n;42\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap()]);
assert!(ok, "expected success; stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
assert!(
!stdout.contains("\"declarations\""),
"no AST dump expected; got: {stdout}"
);
}
#[test]
fn single_fn_file_runs_with_positional_args() {
let (_dir, path) = write_temp("double x:n>n;*x 2\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "7"]);
assert!(ok, "stderr: {stderr}");
assert_eq!(stdout.trim(), "14");
}
#[test]
fn multi_fn_file_with_no_func_arg_lists_and_exits_nonzero() {
let (_dir, path) = write_temp("foo>n;1\nbar>n;2\nbaz>n;3\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap()]);
assert!(!ok, "expected non-zero exit; stdout: {stdout}");
assert!(
stderr.contains("defines multiple functions"),
"expected listing header; stderr: {stderr}"
);
assert!(
stderr.contains("foo"),
"expected foo listed; stderr: {stderr}"
);
assert!(
stderr.contains("bar"),
"expected bar listed; stderr: {stderr}"
);
assert!(
stderr.contains("baz"),
"expected baz listed; stderr: {stderr}"
);
assert!(
stderr.contains("--ast"),
"expected --ast hint; stderr: {stderr}"
);
assert!(
!stdout.contains("\"declarations\""),
"no AST dump expected; got: {stdout}"
);
}
#[test]
fn multi_fn_file_with_func_name_runs_unchanged() {
let (_dir, path) = write_temp("foo>n;1\nbar>n;2\nbaz>n;3\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "bar"]);
assert!(ok, "expected success; stderr: {stderr}");
assert_eq!(stdout.trim(), "2");
}
#[test]
fn multi_fn_file_with_func_name_and_args() {
let (_dir, path) = write_temp("add a:n b:n>n;+a b\nmul a:n b:n>n;*a b\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "mul", "6", "7"]);
assert!(ok, "stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
}
#[test]
fn ast_flag_dumps_single_fn_file() {
let (_dir, path) = write_temp("f>n;42\n");
let (ok, stdout, _stderr) = run(&["--ast", path.to_str().unwrap()]);
assert!(ok);
assert!(
stdout.contains("\"declarations\""),
"expected JSON AST; got: {stdout}"
);
assert!(
!stdout.trim().starts_with("42"),
"AST dump must not execute; got: {stdout}"
);
}
#[test]
fn ast_flag_dumps_multi_fn_file() {
let (_dir, path) = write_temp("foo>n;1\nbar>n;2\n");
let (ok, stdout, stderr) = run(&["--ast", path.to_str().unwrap()]);
assert!(ok, "stderr: {stderr}");
assert!(
stdout.contains("\"declarations\""),
"expected JSON AST; got: {stdout}"
);
assert!(
!stderr.contains("defines multiple functions"),
"no listing should fire when --ast is explicit; stderr: {stderr}"
);
}
#[test]
fn ast_flag_trailing_position() {
let (_dir, path) = write_temp("foo>n;1\nbar>n;2\n");
let (ok, stdout, _stderr) = run(&[path.to_str().unwrap(), "--ast"]);
assert!(ok);
assert!(stdout.contains("\"declarations\""));
}
#[test]
fn ast_flag_on_inline_code() {
let (ok, stdout, _stderr) = run(&["--ast", "f>n;5"]);
assert!(ok);
assert!(stdout.contains("\"declarations\""));
}
#[test]
fn multi_fn_file_with_main_auto_runs_main() {
let (_dir, path) = write_temp("helper a:n>n;+a 1\nmain>n;helper 41\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap()]);
assert!(ok, "expected success; stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
assert!(
!stderr.contains("defines multiple functions"),
"no listing should fire when main is defined; stderr: {stderr}"
);
}
#[test]
fn multi_fn_file_explicit_func_arg_overrides_main() {
let (_dir, path) = write_temp("helper>n;7\nmain>n;42\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "helper"]);
assert!(ok, "stderr: {stderr}");
assert_eq!(stdout.trim(), "7");
}
#[test]
fn inline_single_fn_auto_runs() {
let (ok, stdout, stderr) = run(&["f>n;42"]);
assert!(ok, "expected success; stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
assert!(
!stdout.contains("\"declarations\""),
"no AST dump expected for runnable inline snippet; got: {stdout}"
);
}
#[test]
fn inline_multi_fn_with_main_auto_runs_main() {
let (ok, stdout, stderr) = run(&["helper a:n>n;+a 1\nmain>n;helper 41"]);
assert!(ok, "stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
}
#[test]
fn inline_multi_fn_without_main_still_dumps_ast() {
let (ok, stdout, _stderr) = run(&["foo>n;1\nbar>n;2"]);
assert!(ok);
assert!(
stdout.contains("\"declarations\""),
"non-runnable inline snippet should AST-dump; got: {stdout}"
);
}
#[test]
fn inline_zero_fn_still_dumps_ast() {
let (ok, stdout, _stderr) = run(&["-- comment only"]);
assert!(ok);
assert!(
stdout.contains("\"declarations\""),
"zero-fn inline snippet should AST-dump; got: {stdout}"
);
}
#[test]
fn empty_file_dumps_ast() {
let (_dir, path) = write_temp("");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap()]);
assert!(ok, "stderr: {stderr}");
assert!(stdout.contains("\"declarations\""));
}
#[test]
fn synthetic_lambda_decls_hidden_from_multi_fn_listing() {
let (_dir, path) =
write_temp("sq xs:L n>L n;map (x:n>n;*x x) xs\nother xs:L n>L n;flt (x:n>b;>x 0) xs\n");
let (ok, _stdout, stderr) = run(&[path.to_str().unwrap()]);
assert!(!ok, "expected non-zero exit");
assert!(
!stderr.contains("__lit"),
"synthetic __lit_N names must not leak to user listing; stderr: {stderr}"
);
assert!(
stderr.contains("sq") && stderr.contains("other"),
"real fn names should still be listed; stderr: {stderr}"
);
}
#[test]
fn unknown_subcommand_errors_with_available_listing() {
let (_dir, path) = write_temp("helper a:n>n;+a 1\nmain>n;helper 41\n");
let (ok, _stdout, stderr) = run(&[path.to_str().unwrap(), "wibble", "x"]);
assert!(!ok, "unknown subcommand should exit non-zero");
assert!(
stderr.contains("no such function 'wibble'"),
"expected 'no such function' error; stderr: {stderr}"
);
assert!(
stderr.contains("available functions"),
"expected available-functions listing; stderr: {stderr}"
);
assert!(
stderr.contains("helper") && stderr.contains("main"),
"expected both fn names listed; stderr: {stderr}"
);
assert!(
!stderr.contains("expected 1 args, got 2"),
"must not leak first-fn arity error; stderr: {stderr}"
);
}
#[test]
fn known_subcommand_still_routes_correctly_in_multi_fn_file() {
let (_dir, path) = write_temp("helper a:n>n;+a 1\nmain>n;helper 41\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "helper", "5"]);
assert!(ok, "known subcommand should succeed; stderr: {stderr}");
assert_eq!(stdout.trim(), "6");
}
#[test]
fn unknown_subcommand_does_not_list_synthetic_lit_decls() {
let (_dir, path) =
write_temp("sq xs:L n>L n;map (x:n>n;*x x) xs\nother xs:L n>L n;flt (x:n>b;>x 0) xs\n");
let (ok, _stdout, stderr) = run(&[path.to_str().unwrap(), "wibble"]);
assert!(!ok, "unknown subcommand should exit non-zero");
assert!(
stderr.contains("no such function 'wibble'"),
"expected no-such-function error; stderr: {stderr}"
);
assert!(
!stderr.contains("__lit"),
"synthetic __lit_N names must not leak to user listing; stderr: {stderr}"
);
assert!(
stderr.contains("sq") && stderr.contains("other"),
"real fn names should still be listed; stderr: {stderr}"
);
}
#[test]
fn single_fn_file_treats_unknown_leading_token_as_arg() {
let (_dir, path) = write_temp("dbl x:n>n;*x 2\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "21"]);
assert!(ok, "single-fn auto-run should succeed; stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
}
#[test]
fn inline_program_treats_unknown_leading_token_as_arg() {
let (ok, stdout, stderr) = run(&["dbl x:n>n;*x 2", "21"]);
assert!(ok, "inline auto-run should succeed; stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
}
#[test]
fn multi_fn_file_numeric_leading_arg_passes_through_to_entry_fn() {
let (_dir, path) = write_temp("outer x:n>n;+x 1\ninner x:n>n;*x 2\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "41"]);
assert!(
ok,
"numeric leading arg should pass through to entry fn; stderr: {stderr}"
);
assert_eq!(stdout.trim(), "42");
}
#[test]
fn multi_fn_file_quoted_string_leading_arg_passes_through() {
let (_dir, path) = write_temp("greet s:t>t;s\nother s:t>t;s\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "\"hi\""]);
assert!(
ok,
"quoted-string leading arg should pass through; stderr: {stderr}"
);
assert_eq!(stdout.trim().trim_matches('"'), "hi");
}
#[test]
fn multi_fn_file_bracketed_list_leading_arg_passes_through() {
let (_dir, path) = write_temp("first xs:L n>n;hd xs\nother xs:L n>n;hd xs\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "[7,8,9]"]);
assert!(
ok,
"bracketed-list leading arg should pass through; stderr: {stderr}"
);
assert_eq!(stdout.trim(), "7");
}
fn run_engine_picks_main(engine_flag: &str) {
let (_dir, path) = write_temp("helper a:n>n;+a 1\nmain>n;helper 41\n");
let (ok, stdout, stderr) = run(&[engine_flag, path.to_str().unwrap()]);
assert!(
ok,
"{engine_flag}: expected main to auto-run; stderr: {stderr}"
);
assert_eq!(
stdout.trim(),
"42",
"{engine_flag}: expected main result 42; stdout: {stdout}"
);
assert!(
!stderr.contains("expected 1 args, got 0"),
"{engine_flag}: must not leak helper arity error; stderr: {stderr}"
);
assert!(
!stderr.contains("Cranelift JIT: compilation failed"),
"{engine_flag}: must not leak bare JIT-failed string; stderr: {stderr}"
);
}
#[test]
fn run_tree_flag_auto_picks_main_on_multi_fn_file() {
run_engine_picks_main("--vm");
}
#[test]
fn run_vm_flag_auto_picks_main_on_multi_fn_file() {
run_engine_picks_main("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_auto_picks_main_on_multi_fn_file() {
run_engine_picks_main("--jit");
}
#[test]
fn run_default_flag_auto_picks_main_on_multi_fn_file() {
let (_dir, path) = write_temp("helper a:n>n;+a 1\nmain>n;helper 41\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap()]);
assert!(ok, "default: expected main to auto-run; stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
}
fn run_engine_explicit_func_overrides_main(engine_flag: &str) {
let (_dir, path) = write_temp("helper>n;7\nmain>n;42\n");
let (ok, stdout, stderr) = run(&[engine_flag, path.to_str().unwrap(), "helper"]);
assert!(
ok,
"{engine_flag}: expected explicit helper to run; stderr: {stderr}"
);
assert_eq!(stdout.trim(), "7");
}
#[test]
fn run_tree_flag_explicit_func_overrides_main() {
run_engine_explicit_func_overrides_main("--vm");
}
#[test]
fn run_vm_flag_explicit_func_overrides_main() {
run_engine_explicit_func_overrides_main("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_explicit_func_overrides_main() {
run_engine_explicit_func_overrides_main("--jit");
}
fn run_engine_undefined_func_arg_still_errors(engine_flag: &str) {
let (_dir, path) = write_temp("helper a:n>n;+a 1\nmain>n;helper 41\n");
let (ok, _stdout, stderr) = run(&[engine_flag, path.to_str().unwrap(), "wibble"]);
assert!(!ok, "{engine_flag}: undefined fn arg must fail");
assert!(
stderr.contains("undefined")
|| stderr.contains("no such function")
|| stderr.contains("wibble"),
"{engine_flag}: expected fn-not-found-style error; stderr: {stderr}"
);
}
#[test]
fn run_tree_flag_undefined_func_arg_still_errors() {
run_engine_undefined_func_arg_still_errors("--vm");
}
#[test]
fn run_vm_flag_undefined_func_arg_still_errors() {
run_engine_undefined_func_arg_still_errors("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_undefined_func_arg_still_errors() {
run_engine_undefined_func_arg_still_errors("--jit");
}
fn run_engine_single_fn_no_args_still_runs(engine_flag: &str) {
let (_dir, path) = write_temp("entry>n;42\n");
let (ok, stdout, stderr) = run(&[engine_flag, path.to_str().unwrap()]);
assert!(ok, "{engine_flag}: expected success; stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
}
#[test]
fn run_tree_flag_single_fn_no_args_still_runs() {
run_engine_single_fn_no_args_still_runs("--vm");
}
#[test]
fn run_vm_flag_single_fn_no_args_still_runs() {
run_engine_single_fn_no_args_still_runs("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_single_fn_no_args_still_runs() {
run_engine_single_fn_no_args_still_runs("--jit");
}
#[test]
fn hyphenated_unknown_subcommand_errors_with_listing() {
let (_dir, path) = write_temp("load x:n>n;*x 2\nmain>n;load 21\n");
let (ok, _stdout, stderr) = run(&[path.to_str().unwrap(), "list-orders"]);
assert!(!ok, "unknown hyphenated subcommand should exit non-zero");
assert!(
stderr.contains("no such function 'list-orders'"),
"expected 'no such function' error for hyphenated name; stderr: {stderr}"
);
assert!(
stderr.contains("available functions"),
"expected available-functions listing; stderr: {stderr}"
);
assert!(
stderr.contains("load") && stderr.contains("main"),
"expected both fn names listed; stderr: {stderr}"
);
assert!(
!stderr.contains("'load' arg"),
"must not leak first-fn dispatch; stderr: {stderr}"
);
}
#[test]
fn hyphenated_unknown_subcommand_wibble_x() {
let (_dir, path) = write_temp("helper a:n>n;+a 1\nmain>n;helper 41\n");
let (ok, _stdout, stderr) = run(&[path.to_str().unwrap(), "wibble-x"]);
assert!(!ok);
assert!(
stderr.contains("no such function 'wibble-x'"),
"expected no-such-function for `wibble-x`; stderr: {stderr}"
);
}
#[test]
fn trailing_dash_falls_through_as_data() {
let (_dir, path) = write_temp("first s:t>t;s\nother s:t>t;s\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "foo-"]);
assert!(
ok,
"trailing-dash positional should pass through, not error; stderr: {stderr}"
);
assert_eq!(stdout.trim().trim_matches('"'), "foo-");
}
#[test]
fn non_ident_arg_routes_to_main_when_main_exists() {
let (_dir, path) = write_temp("hav lat:n lon:n r:n>n;+lat lon\nmain path:t>t;path\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "top200.csv"]);
assert!(ok, "non-ident arg should route to main; stderr: {stderr}");
assert_eq!(stdout.trim().trim_matches('"'), "top200.csv");
assert!(
!stderr.contains("hav:"),
"must not invoke `hav` (first declared); stderr: {stderr}"
);
}
#[test]
fn non_ident_arg_routes_to_main_with_multiple_positionals() {
let (_dir, path) = write_temp("hav lat:n lon:n>n;+lat lon\nmain a:t b:t>L t;[a,b]\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "data.csv", "out.txt"]);
assert!(ok, "expected success; stderr: {stderr}");
assert!(stdout.contains("data.csv"), "stdout: {stdout}");
assert!(stdout.contains("out.txt"), "stdout: {stdout}");
}
#[test]
fn non_ident_arg_routes_to_main_numeric() {
let (_dir, path) = write_temp("hav x:n>n;+x 1\nmain x:n>n;*x 2\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "21"]);
assert!(ok, "expected success; stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
}
#[test]
fn non_ident_arg_without_main_keeps_legacy_passthrough() {
let (_dir, path) = write_temp("outer x:n>n;+x 1\ninner x:n>n;*x 2\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "41"]);
assert!(ok, "stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
}
#[test]
fn non_ident_path_arg_routes_to_main_devops_sre_shape() {
let (_dir, path) = write_temp("gs i:_>t;i.host\nmain p:t>t;p\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "/tmp/inc.json"]);
assert!(
ok,
"non-ident path arg should route to main, not gs; stderr: {stderr}"
);
assert_eq!(stdout.trim().trim_matches('"'), "/tmp/inc.json");
assert!(
!stderr.contains("'gs'"),
"must not invoke first-declared `gs`; stderr: {stderr}"
);
assert!(
!stderr.contains("i.host"),
"must not surface gs's field-access path; stderr: {stderr}"
);
}
#[test]
fn known_func_name_overrides_main_routing() {
let (_dir, path) = write_temp("hav x:n>n;+x 1\nmain x:n>n;*x 2\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "hav", "41"]);
assert!(ok, "stderr: {stderr}");
assert_eq!(stdout.trim(), "42");
}
fn run_engine_non_ident_positional_routes_to_main(engine_flag: &str) {
let (_dir, path) = write_temp("helper>n;7\nmain p:t>n;+(len p) 1\n");
let (ok, stdout, stderr) = run(&[engine_flag, path.to_str().unwrap(), "paper.txt"]);
assert!(
ok,
"{engine_flag}: expected main to absorb non-ident positional; stderr: {stderr}"
);
assert_eq!(
stdout.trim(),
"10",
"{engine_flag}: expected len('paper.txt')+1 = 10; stdout: {stdout}"
);
assert!(
!stderr.contains("undefined function"),
"{engine_flag}: must not leak undefined-function diagnostic; stderr: {stderr}"
);
}
#[test]
fn run_tree_flag_non_ident_positional_routes_to_main() {
run_engine_non_ident_positional_routes_to_main("--vm");
}
#[test]
fn run_vm_flag_non_ident_positional_routes_to_main() {
run_engine_non_ident_positional_routes_to_main("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_non_ident_positional_routes_to_main() {
run_engine_non_ident_positional_routes_to_main("--jit");
}
#[test]
fn run_default_non_ident_positional_routes_to_main_pin() {
let (_dir, path) = write_temp("helper>n;7\nmain p:t>n;+(len p) 1\n");
let (ok, stdout, stderr) = run(&[path.to_str().unwrap(), "paper.txt"]);
assert!(ok, "default: stderr: {stderr}");
assert_eq!(stdout.trim(), "10");
}
fn run_engine_path_shaped_positional_routes_to_main(engine_flag: &str) {
let (_dir, path) = write_temp("helper>n;7\nmain p:t>n;len p\n");
let (ok, stdout, stderr) = run(&[engine_flag, path.to_str().unwrap(), "/tmp/x.json"]);
assert!(
ok,
"{engine_flag}: expected main to absorb path-shaped positional; stderr: {stderr}"
);
assert_eq!(
stdout.trim(),
"11",
"{engine_flag}: len('/tmp/x.json') = 11"
);
}
#[test]
fn run_tree_flag_path_shaped_positional_routes_to_main() {
run_engine_path_shaped_positional_routes_to_main("--vm");
}
#[test]
fn run_vm_flag_path_shaped_positional_routes_to_main() {
run_engine_path_shaped_positional_routes_to_main("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_path_shaped_positional_routes_to_main() {
run_engine_path_shaped_positional_routes_to_main("--jit");
}
fn run_engine_numeric_positional_routes_to_main(engine_flag: &str) {
let (_dir, path) = write_temp("helper>n;7\nmain a:n b:n>n;+a b\n");
let (ok, stdout, stderr) = run(&[engine_flag, path.to_str().unwrap(), "10", "32"]);
assert!(
ok,
"{engine_flag}: expected main to absorb numeric positionals; stderr: {stderr}"
);
assert_eq!(stdout.trim(), "42");
}
#[test]
fn run_tree_flag_numeric_positional_routes_to_main() {
run_engine_numeric_positional_routes_to_main("--vm");
}
#[test]
fn run_vm_flag_numeric_positional_routes_to_main() {
run_engine_numeric_positional_routes_to_main("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_numeric_positional_routes_to_main() {
run_engine_numeric_positional_routes_to_main("--jit");
}
fn run_engine_explicit_main_keyword_still_works(engine_flag: &str) {
let (_dir, path) = write_temp("helper>n;7\nmain p:t>n;+(len p) 1\n");
let (ok, stdout, stderr) = run(&[engine_flag, path.to_str().unwrap(), "main", "paper.txt"]);
assert!(
ok,
"{engine_flag}: explicit `main` + non-ident positional; stderr: {stderr}"
);
assert_eq!(stdout.trim(), "10");
}
#[test]
fn run_tree_flag_explicit_main_with_non_ident_arg() {
run_engine_explicit_main_keyword_still_works("--vm");
}
#[test]
fn run_vm_flag_explicit_main_with_non_ident_arg() {
run_engine_explicit_main_keyword_still_works("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_explicit_main_with_non_ident_arg() {
run_engine_explicit_main_keyword_still_works("--jit");
}
fn run_engine_explicit_helper_overrides_main_heuristic(engine_flag: &str) {
let (_dir, path) = write_temp("helper p:t>n;len p\nmain p:t>n;+(len p) 100\n");
let (ok, stdout, stderr) = run(&[engine_flag, path.to_str().unwrap(), "helper", "paper.txt"]);
assert!(
ok,
"{engine_flag}: explicit helper override; stderr: {stderr}"
);
assert_eq!(
stdout.trim(),
"9",
"{engine_flag}: expected len('paper.txt') = 9 from helper, not 109 from main; stdout: {stdout}"
);
}
#[test]
fn run_tree_flag_explicit_helper_overrides_main_heuristic() {
run_engine_explicit_helper_overrides_main_heuristic("--vm");
}
#[test]
fn run_vm_flag_explicit_helper_overrides_main_heuristic() {
run_engine_explicit_helper_overrides_main_heuristic("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_explicit_helper_overrides_main_heuristic() {
run_engine_explicit_helper_overrides_main_heuristic("--jit");
}
fn run_engine_unknown_ident_no_main_still_errors(engine_flag: &str) {
let (_dir, path) = write_temp("helper a:n>n;+a 1\n");
let (ok, _stdout, stderr) = run(&[engine_flag, path.to_str().unwrap(), "wibble"]);
assert!(!ok, "{engine_flag}: unknown ident must fail");
assert!(
stderr.contains("undefined") || stderr.contains("wibble"),
"{engine_flag}: expected fn-not-found diagnostic; stderr: {stderr}"
);
}
#[test]
fn run_tree_flag_unknown_ident_no_main_still_errors() {
run_engine_unknown_ident_no_main_still_errors("--vm");
}
#[test]
fn run_vm_flag_unknown_ident_no_main_still_errors() {
run_engine_unknown_ident_no_main_still_errors("--vm");
}
#[test]
#[cfg(feature = "cranelift")]
fn run_cranelift_flag_unknown_ident_no_main_still_errors() {
run_engine_unknown_ident_no_main_still_errors("--jit");
}