use std::fs;
use std::process::Command;
use tempfile::tempdir;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
#[cfg(feature = "cranelift")]
const ENGINES_ALL: &[&str] = &["--vm", "--jit"];
#[cfg(not(feature = "cranelift"))]
const ENGINES_ALL: &[&str] = &["--vm"];
fn make_fixture() -> tempfile::TempDir {
let dir = tempdir().expect("tempdir");
let root = dir.path();
fs::write(root.join("a.txt"), "a").unwrap();
fs::write(root.join("b.txt"), "b").unwrap();
fs::create_dir(root.join("sub")).unwrap();
fs::write(root.join("sub").join("c.txt"), "c").unwrap();
fs::write(root.join("sub").join("d.log"), "d").unwrap();
fs::create_dir(root.join("sub").join("nested")).unwrap();
fs::write(root.join("sub").join("nested").join("e.txt"), "e").unwrap();
dir
}
fn run_ok(engine: &str, src: &str, args: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(src).arg(engine);
for a in args {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
assert!(
out.status.success(),
"ilo {engine} {src:?} {args:?} failed: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout)
.trim_end_matches('\n')
.to_string()
}
fn run_err(engine: &str, src: &str, args: &[&str]) -> String {
let mut cmd = ilo();
cmd.arg(src).arg(engine);
for a in args {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
assert!(
!out.status.success(),
"ilo {engine} {src:?} {args:?} unexpectedly succeeded: stdout={}",
String::from_utf8_lossy(&out.stdout)
);
let s = String::from_utf8_lossy(&out.stderr);
s.trim_end_matches('\n').to_string()
}
#[test]
fn ls_basic_cross_engine() {
let fix = make_fixture();
let root = fix.path().to_str().unwrap();
let src = "f d:t>R (L t) t;lsd d";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", root]);
assert_eq!(out, "[a.txt, b.txt, sub]", "{engine}: ls basic");
}
}
#[test]
fn ls_empty_dir_cross_engine() {
let dir = tempdir().unwrap();
let root = dir.path().to_str().unwrap();
let src = "f d:t>R (L t) t;lsd d";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", root]);
assert_eq!(out, "[]", "{engine}: ls empty");
}
}
#[test]
fn ls_missing_dir_cross_engine() {
let src = "f d:t>R (L t) t;lsd d";
for engine in ENGINES_ALL {
let out = run_err(
engine,
src,
&["f", "/this/path/should/never/exist/xyzzy-ilo"],
);
assert!(
out.starts_with('^'),
"{engine}: expected ^err prefix, got {out:?}"
);
}
}
#[test]
fn walk_recursive_cross_engine() {
let fix = make_fixture();
let root = fix.path().to_str().unwrap();
let src = "f d:t>R (L t) t;walk d";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", root]);
assert_eq!(
out, "[a.txt, b.txt, sub, sub/c.txt, sub/d.log, sub/nested, sub/nested/e.txt]",
"{engine}: walk recursive"
);
}
}
#[test]
fn walk_missing_dir_cross_engine() {
let src = "f d:t>R (L t) t;walk d";
for engine in ENGINES_ALL {
let out = run_err(
engine,
src,
&["f", "/this/path/should/never/exist/xyzzy-ilo"],
);
assert!(
out.starts_with('^'),
"{engine}: expected ^err prefix, got {out:?}"
);
}
}
#[test]
fn glob_star_single_segment_cross_engine() {
let fix = make_fixture();
let root = fix.path().to_str().unwrap();
let src = "f d:t p:t>R (L t) t;glob d p";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", root, "*.txt"]);
assert_eq!(out, "[a.txt, b.txt]", "{engine}: glob *.txt");
}
}
#[test]
fn glob_double_star_recursive_cross_engine() {
let fix = make_fixture();
let root = fix.path().to_str().unwrap();
let src = "f d:t p:t>R (L t) t;glob d p";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", root, "**/*.txt"]);
assert_eq!(
out, "[a.txt, b.txt, sub/c.txt, sub/nested/e.txt]",
"{engine}: glob **/*.txt"
);
}
}
#[test]
fn glob_char_class_cross_engine() {
let fix = make_fixture();
let root = fix.path().to_str().unwrap();
let src = "f d:t p:t>R (L t) t;glob d p";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", root, "[ab].txt"]);
assert_eq!(out, "[a.txt, b.txt]", "{engine}: glob [ab].txt");
}
}
#[test]
fn glob_missing_dir_cross_engine() {
let src = "f d:t p:t>R (L t) t;glob d p";
for engine in ENGINES_ALL {
let out = run_err(
engine,
src,
&["f", "/this/path/should/never/exist/xyzzy-ilo", "*"],
);
assert!(
out.starts_with('^'),
"{engine}: expected ^err prefix, got {out:?}"
);
}
}
#[test]
fn glob_no_matches_returns_empty_cross_engine() {
let fix = make_fixture();
let root = fix.path().to_str().unwrap();
let src = "f d:t p:t>R (L t) t;glob d p";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", root, "no-such-pattern-*.xyzzy"]);
assert_eq!(out, "[]", "{engine}: glob empty match");
}
}
#[test]
fn walk_single_file_cross_engine() {
let dir = tempdir().unwrap();
fs::write(dir.path().join("only.txt"), "x").unwrap();
let src = "f d:t>R (L t) t;walk d";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", dir.path().to_str().unwrap()]);
assert_eq!(out, "[only.txt]", "{engine}: walk single file");
}
}
#[cfg(unix)]
fn make_perm_fixture() -> tempfile::TempDir {
use std::os::unix::fs::PermissionsExt;
let dir = tempdir().expect("tempdir");
let root = dir.path();
fs::write(root.join("a.txt"), "a").unwrap();
fs::write(root.join("b.txt"), "b").unwrap();
fs::create_dir(root.join("readable")).unwrap();
fs::write(root.join("readable").join("c.txt"), "c").unwrap();
fs::create_dir(root.join("locked")).unwrap();
fs::write(root.join("locked").join("secret.txt"), "s").unwrap();
let mut perm = fs::metadata(root.join("locked")).unwrap().permissions();
perm.set_mode(0o000);
fs::set_permissions(root.join("locked"), perm).unwrap();
dir
}
#[cfg(unix)]
fn restore_perm_fixture(fix: &tempfile::TempDir) {
use std::os::unix::fs::PermissionsExt;
let locked = fix.path().join("locked");
if let Ok(meta) = fs::metadata(&locked) {
let mut perm = meta.permissions();
perm.set_mode(0o755);
let _ = fs::set_permissions(&locked, perm);
}
}
#[cfg(unix)]
#[test]
fn walk_skips_permission_denied_subdir_cross_engine() {
let fix = make_perm_fixture();
let root = fix.path().to_str().unwrap();
let src = "f d:t>R (L t) t;walk d";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", root]);
assert_eq!(
out, "[a.txt, b.txt, locked, readable, readable/c.txt]",
"{engine}: walk skips perm-denied subdir"
);
}
restore_perm_fixture(&fix);
}
#[cfg(unix)]
#[test]
fn glob_skips_permission_denied_subdir_cross_engine() {
let fix = make_perm_fixture();
let root = fix.path().to_str().unwrap();
let src = "f d:t p:t>R (L t) t;glob d p";
for engine in ENGINES_ALL {
let out = run_ok(engine, src, &["f", root, "**/*.txt"]);
assert_eq!(
out, "[a.txt, b.txt, readable/c.txt]",
"{engine}: glob skips perm-denied subdir"
);
}
restore_perm_fixture(&fix);
}
#[cfg(unix)]
#[test]
fn walk_unreadable_root_is_err_cross_engine() {
use std::os::unix::fs::PermissionsExt;
let dir = tempdir().unwrap();
let root = dir.path();
let locked = root.join("root-locked");
fs::create_dir(&locked).unwrap();
fs::write(locked.join("inside.txt"), "x").unwrap();
let mut perm = fs::metadata(&locked).unwrap().permissions();
perm.set_mode(0o000);
fs::set_permissions(&locked, perm).unwrap();
let src = "f d:t>R (L t) t;walk d";
for engine in ENGINES_ALL {
let out = run_err(engine, src, &["f", locked.to_str().unwrap()]);
assert!(
out.starts_with('^'),
"{engine}: expected ^err prefix on unreadable root, got {out:?}"
);
}
let mut perm = fs::metadata(&locked).unwrap().permissions();
perm.set_mode(0o755);
fs::set_permissions(&locked, perm).unwrap();
}