#![cfg(unix)]
use std::ffi::OsStr;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
use std::process::{Command, Output};
#[cfg(not(target_os = "macos"))]
use std::os::unix::ffi::OsStringExt;
fn veryl() -> Command {
Command::new(env!("CARGO_BIN_EXE_veryl"))
}
fn write_executable(dir: &Path, name: &str) {
write_executable_with_body(dir, OsStr::new(name), "#!/bin/sh\nexit 0\n");
}
fn write_executable_with_body(dir: &Path, name: &OsStr, body: &str) {
let path = dir.join(Path::new(name));
std::fs::write(&path, body).unwrap();
let mut permissions = std::fs::metadata(&path).unwrap().permissions();
permissions.set_mode(0o755);
std::fs::set_permissions(&path, permissions).unwrap();
}
fn run_with_path(work_dir: &Path, path_dirs: &[&Path], args: &[&str]) -> Output {
let path = std::env::join_paths(path_dirs.iter().map(|path| path.to_path_buf())).unwrap();
veryl()
.current_dir(work_dir)
.env("PATH", path)
.args(args)
.output()
.unwrap()
}
fn run_success(work_dir: &Path, path_dirs: &[&Path], args: &[&str]) -> String {
let output = run_with_path(work_dir, path_dirs, args);
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let command = format!("veryl {}", args.join(" "));
assert!(
output.status.success(),
"expected `{command}` to succeed; status: {:?}\nstderr:\n{stderr}\nstdout:\n{stdout}",
output.status.code()
);
assert!(
stderr.is_empty(),
"expected `{command}` to keep stderr empty; stderr:\n{stderr}"
);
stdout.into_owned()
}
fn root_help(work_dir: &Path, path_dirs: &[&Path], flag: &str) -> String {
run_success(work_dir, path_dirs, &[flag])
}
fn command_list(work_dir: &Path, path_dirs: &[&Path]) -> String {
run_success(work_dir, path_dirs, &["--list"])
}
fn command_line_count(output: &str, name: &str) -> usize {
output
.lines()
.filter(|line| line.split_whitespace().next() == Some(name))
.count()
}
fn command_line<'a>(output: &'a str, name: &str) -> &'a str {
output
.lines()
.find(|line| line.split_whitespace().next() == Some(name))
.unwrap_or("")
}
fn assert_output_lists_commands(label: &str, output: &str, expected: &[&str]) {
for name in expected {
assert_eq!(
command_line_count(output, name),
1,
"expected `{label}` to list command `{name}` exactly once; output:\n{output}"
);
}
}
fn assert_output_omits_commands(label: &str, output: &str, omitted: &[&str]) {
for name in omitted {
assert!(
command_line_count(output, name) == 0,
"expected `{label}` to omit command `{name}`; output:\n{output}"
);
}
}
fn assert_required_command_usage(output: &str) {
assert!(
output.contains("Usage: veryl [OPTIONS] <COMMAND>"),
"expected usage to keep required subcommand marker `<COMMAND>`; output:\n{output}"
);
assert!(
!output.contains("Usage: veryl [OPTIONS] [COMMAND]"),
"expected usage not to show optional subcommand marker `[COMMAND]`; output:\n{output}"
);
}
fn assert_list_hint(output: &str) {
assert!(
output.contains("--list") && output.contains("See all commands"),
"expected root help to show the `--list` discovery hint; output:\n{output}"
);
}
#[test]
fn root_help_omits_external_commands_and_points_to_list() {
let temp = tempfile::tempdir().unwrap();
write_executable(temp.path(), "veryl-import");
write_executable(temp.path(), "veryl-flist");
let short_help = root_help(temp.path(), &[temp.path()], "-h");
let long_help = root_help(temp.path(), &[temp.path()], "--help");
let help_command = run_success(temp.path(), &[temp.path()], &["help"]);
for (label, output) in [
("veryl -h", short_help.as_str()),
("veryl --help", long_help.as_str()),
("veryl help", help_command.as_str()),
] {
assert_output_omits_commands(label, output, &["flist", "import"]);
assert_required_command_usage(output);
assert_list_hint(output);
}
}
#[test]
#[cfg(not(target_os = "macos"))]
fn root_help_ignores_non_utf8_external_suffixes() {
let temp = tempfile::tempdir().unwrap();
write_executable(temp.path(), "veryl-flist");
let non_utf8_name = std::ffi::OsString::from_vec(vec![
b'v', b'e', b'r', b'y', b'l', b'-', b'i', b'm', 0xff, b'o', b'r', b't',
]);
write_executable_with_body(
temp.path(),
non_utf8_name.as_os_str(),
"#!/bin/sh\nexit 0\n",
);
let help = root_help(temp.path(), &[temp.path()], "-h");
assert_output_omits_commands("-h", &help, &["flist", "im\u{fffd}ort"]);
assert_list_hint(&help);
}
#[test]
fn bare_cli_reports_missing_required_subcommand_usage() {
let temp = tempfile::tempdir().unwrap();
let output = veryl()
.current_dir(temp.path())
.env("PATH", temp.path())
.output()
.unwrap();
assert!(
!output.status.success(),
"expected bare `veryl` to fail; status: {:?}",
output.status.code()
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("requires a subcommand") || stderr.contains("subcommand"),
"expected missing-subcommand style error; stderr:\n{stderr}"
);
assert_required_command_usage(&stderr);
}
#[test]
fn root_help_with_extra_argument_uses_clap_handling() {
let temp = tempfile::tempdir().unwrap();
write_executable(temp.path(), "veryl-import");
let path = std::env::join_paths([temp.path()]).unwrap();
let output = veryl()
.current_dir(temp.path())
.env("PATH", path)
.args(["-h", "extra"])
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"expected clap help to succeed; status: {:?}",
output.status.code()
);
assert_output_omits_commands("-h extra", &stdout, &["import"]);
assert_required_command_usage(&stdout);
}
#[test]
fn list_reports_external_commands_from_path() {
let temp = tempfile::tempdir().unwrap();
write_executable(temp.path(), "veryl-import");
write_executable(temp.path(), "veryl-flist");
let list = command_list(temp.path(), &[temp.path()]);
assert_output_lists_commands("veryl --list", &list, &["flist", "import"]);
}
#[test]
fn list_uses_info_description_for_surviving_external_commands_only() {
let temp = tempfile::tempdir().unwrap();
write_executable_with_body(
temp.path(),
OsStr::new("veryl-import"),
"#!/bin/sh\nif [ \"$1\" = \"--info\" ]; then printf 'Import source files\\n'; exit 0; fi\nexit 0\n",
);
write_executable_with_body(
temp.path(),
OsStr::new("veryl-flist"),
"#!/bin/sh\nif [ \"$1\" = \"--info\" ]; then printf 'first line\\nsecond line\\n'; exit 0; fi\nexit 0\n",
);
write_executable_with_body(
temp.path(),
OsStr::new("veryl-build"),
"#!/bin/sh\nif [ \"$1\" = \"--info\" ]; then printf 'External build should not be probed\\n'; exit 0; fi\nexit 0\n",
);
let list = command_list(temp.path(), &[temp.path()]);
assert!(
command_line(&list, "import").contains("Import source files"),
"expected valid `--info` output to describe import; output:\n{list}"
);
assert!(
command_line(&list, "flist").contains("External Veryl subcommand from PATH"),
"expected invalid multiline `--info` output to fall back; output:\n{list}"
);
assert!(
!list.contains("External build should not be probed"),
"expected overwritten built-in collision not to run `--info`; output:\n{list}"
);
}
#[test]
fn list_uses_builtin_rows_for_builtin_help_and_official_suffix_collisions() {
let temp = tempfile::tempdir().unwrap();
write_executable_with_body(
temp.path(),
OsStr::new("veryl-build"),
"#!/bin/sh\nprintf 'PATH veryl-build should not be listed\\n'\n",
);
write_executable_with_body(
temp.path(),
OsStr::new("veryl-check"),
"#!/bin/sh\nprintf 'PATH veryl-check should not be listed\\n'\n",
);
write_executable_with_body(
temp.path(),
OsStr::new("veryl-help"),
"#!/bin/sh\nprintf 'PATH veryl-help should not be listed\\n'\n",
);
write_executable_with_body(
temp.path(),
OsStr::new("veryl-ls"),
"#!/bin/sh\nprintf 'PATH veryl-ls should not be listed\\n'\n",
);
write_executable(temp.path(), "veryl-import");
let list = command_list(temp.path(), &[temp.path()]);
assert_output_lists_commands("veryl --list", &list, &["build", "check", "help", "import"]);
assert_output_omits_commands("veryl --list", &list, &["ls"]);
for name in ["build", "check", "help"] {
let line = command_line(&list, name);
assert!(
!line.contains("External"),
"expected `{name}` to be rendered as a built-in list row; line:\n{line}\nfull list:\n{list}"
);
}
for forbidden in [
"PATH veryl-build should not be listed",
"PATH veryl-check should not be listed",
"PATH veryl-help should not be listed",
"PATH veryl-ls should not be listed",
] {
assert!(
!list.contains(forbidden),
"expected list output not to contain external collision text `{forbidden}`; output:\n{list}"
);
}
}