use std::fs; use std::os::unix::fs::PermissionsExt; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; use inshellah::parsers::manpage::{ManpageResult, ManpageSubcommand}; use inshellah::store::write_result; fn unique_temp_dir(name: &str) -> std::path::PathBuf { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system time") .as_nanos(); std::env::temp_dir().join(format!("{name}-{}-{nanos}", std::process::id())) } #[test] fn complete_scrapes_missing_subcommand_when_parent_is_cached() { let root = unique_temp_dir("inshellah-runtime-complete"); let bin_dir = root.join("bin"); let cache_dir = root.join("cache"); fs::create_dir_all(&bin_dir).expect("bin dir"); fs::create_dir_all(&cache_dir).expect("cache dir"); let fakecmd = bin_dir.join("fakecmd"); fs::write( &fakecmd, r#"#!/bin/sh if [ "$1" = "clone" ]; then if [ "$2" = "--help" ] || [ "$2" = "-h" ]; then cat <<'EOF' Usage: fakecmd clone [OPTIONS] [directory] Options: --depth clone depth -v, --verbose verbose EOF exit 0 fi fi if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then cat <<'EOF' Usage: fakecmd [OPTIONS] COMMAND Commands: clone Clone a repository Options: -h, --help show help EOF exit 0 fi exit 2 "#, ) .expect("write fakecmd"); let mut perms = fs::metadata(&fakecmd).expect("metadata").permissions(); perms.set_mode(0o755); fs::set_permissions(&fakecmd, perms).expect("chmod"); let parent = ManpageResult { entries: Vec::new(), subcommands: vec![ManpageSubcommand { name: "clone".to_string(), desc: "Clone a repository".to_string(), }], positionals: Vec::new(), description: String::new(), }; write_result(&cache_dir, "fakecmd", "help", &parent).expect("parent cache"); let old_path = std::env::var_os("PATH").unwrap_or_default(); let output = Command::new(env!("CARGO_BIN_EXE_inshellah")) .arg("complete") .arg("--dir") .arg(&cache_dir) .arg("--timeout-ms") .arg("1000") .arg("fakecmd") .arg("clone") .arg("--") .env( "PATH", format!("{}:{}", bin_dir.display(), old_path.to_string_lossy()), ) .output() .expect("run inshellah complete"); assert!( output.status.success(), "stderr = {}", String::from_utf8_lossy(&output.stderr) ); let stdout = String::from_utf8(output.stdout).expect("stdout"); assert!(stdout.contains("--depth"), "stdout = {stdout}"); assert!( cache_dir.join("fakecmd_clone.json").is_file(), "subcommand cache was not written" ); let _ = fs::remove_dir_all(root); } #[test] fn complete_lists_path_commands_at_command_position() { let root = unique_temp_dir("inshellah-command-position-complete"); let bin_dir = root.join("bin"); let cache_dir = root.join("cache"); fs::create_dir_all(&bin_dir).expect("bin dir"); fs::create_dir_all(&cache_dir).expect("cache dir"); let fake_git = bin_dir.join("git"); fs::write(&fake_git, "#!/bin/sh\nexit 0\n").expect("write fake git"); let mut perms = fs::metadata(&fake_git).expect("metadata").permissions(); perms.set_mode(0o755); fs::set_permissions(&fake_git, perms).expect("chmod"); let output = Command::new(env!("CARGO_BIN_EXE_inshellah")) .arg("complete") .arg("--dir") .arg(&cache_dir) .arg("gi") .env("PATH", &bin_dir) .output() .expect("run inshellah complete"); assert!( output.status.success(), "stderr = {}", String::from_utf8_lossy(&output.stderr) ); let stdout = String::from_utf8(output.stdout).expect("stdout"); assert!(stdout.contains(r#""value":"git""#), "stdout = {stdout}"); let _ = fs::remove_dir_all(root); }