use crate::args;
use crate::cmd;
use crate::io::command_exists;
use crate::prelude::CommandOutput;
use crate::prelude::OsString;
use crate::test::utils::{echo_args, echo_bin, exit_args, exit_bin, pwsh_is_healthy, sh_echo, stderr_args, stderr_bin};
use crate::util::cmd::redact;
#[cfg(feature = "shell-lint")]
use crate::util::cmd::Severity as CmdSeverity;
#[cfg(feature = "shell-lint")]
use crate::util::constants::env::SHELL_LINT_MIN_SEVERITY;
#[cfg(feature = "shell-lint")]
use shuck_linter::{lint, AnalysisRequest, LinterSettings, Severity as LintSeverity, ShellDialect};
#[cfg(feature = "shell-lint")]
use shuck_parser::parser::Parser;
#[cfg(feature = "shell-lint")]
use temp_env::with_vars;
#[test]
fn test_redact() {
let cases: &[(&[&str], &str)] = &[
(&[], ""),
(&["hello", "world"], "hello world"),
(&["hello world", "foo"], "\"hello world\" foo"),
(&["--api-key=mysecret"], "--api-key=[REDACTED]"),
(&["--password=mysecret"], "--password=[REDACTED]"),
(&["--registration-token=mysecret"], "--registration-token=[REDACTED]"),
(&["--secret=mysecret"], "--secret=[REDACTED]"),
(&["--token=mysecret"], "--token=[REDACTED]"),
(&["--token", "mysecret"], "--token [REDACTED]"),
(&["--api-key", "key123", "--token", "tok456"], "--api-key [REDACTED] --token [REDACTED]"),
(
&["--output", "file.txt", "--token=secret", "--verbose"],
"--output file.txt --token=[REDACTED] --verbose",
),
(&["--token"], "--token"),
(&["--token="], "--token=[REDACTED]"),
(&["--username", "admin", "--host=localhost"], "--username admin --host=localhost"),
(&["--token=first", "--password=second"], "--token=[REDACTED] --password=[REDACTED]"),
(&["--not-token=value", "--my-secret=value"], "--not-token=value --my-secret=value"),
];
for (args, expected) in cases {
let oss_args: Vec<OsString> = args.iter().map(OsString::from).collect();
assert_eq!(redact(&oss_args).join(" "), *expected, "failed for args: {args:?}");
}
}
mod args_macro {
use super::*;
#[test]
fn test_basic() {
let result = args!["hello", "world"];
assert_eq!(result, vec![OsString::from("hello"), OsString::from("world")]);
}
#[test]
fn test_empty() {
let result: Vec<OsString> = args![];
assert!(result.is_empty());
}
#[test]
fn test_single() {
let result = args!["--version"];
assert_eq!(result, vec![OsString::from("--version")]);
}
#[test]
fn test_spread_vec() {
let extra = vec!["--gpus".to_string(), "all".to_string()];
let result = args!["run", "--detach", ..extra, "image"];
let expected: Vec<OsString> = ["run", "--detach", "--gpus", "all", "image"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_spread_and_trailing_comma() {
let extra = vec!["-v"];
let result = args!["cmd", ..extra, "arg",];
let expected: Vec<OsString> = ["cmd", "-v", "arg"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_spread_at_end() {
let extra = vec!["--gpus", "all"];
let result = args!["run", ..extra];
let expected: Vec<OsString> = ["run", "--gpus", "all"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_spread_only() {
let extra = vec!["--gpus", "all"];
let result = args![..extra];
let expected: Vec<OsString> = ["--gpus", "all"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_str_refs() {
let a = "hello";
let b = "world";
let result = args![a, b];
let expected: Vec<OsString> = ["hello", "world"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_trailing_comma() {
let result = args!["a", "b",];
let expected: Vec<OsString> = ["a", "b"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_with_variables() {
let name = "test".to_string();
let result = args!["--name", name, "--verbose"];
let expected: Vec<OsString> = ["--name", "test", "--verbose"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_tuple_key_value() {
let result = args![("--name", "Bill")];
let expected: Vec<OsString> = ["--name", "Bill"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_tuple_mixed() {
let result = args!["run", ("--name", "container"), "--detach"];
let expected: Vec<OsString> = ["run", "--name", "container", "--detach"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_tuple_with_variables() {
let key = "--name";
let value = "Bill".to_string();
let result = args![(key, value)];
let expected: Vec<OsString> = ["--name", "Bill"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
#[test]
fn test_tuple_trailing_comma() {
let result = args![("--name", "Bill"),];
let expected: Vec<OsString> = ["--name", "Bill"].map(OsString::from).to_vec();
assert_eq!(result, expected);
}
}
mod cmd_macro {
use super::*;
#[cfg(feature = "shell-lint")]
#[test]
fn test_configured_shell_lint_min_severity() {
let default = with_vars([(SHELL_LINT_MIN_SEVERITY, None::<&str>)], CmdSeverity::minimum);
assert_eq!(default, CmdSeverity::Hint);
let warning = with_vars([(SHELL_LINT_MIN_SEVERITY, Some("warning"))], CmdSeverity::minimum);
assert_eq!(warning, CmdSeverity::Warning);
let warning_alias = with_vars([(SHELL_LINT_MIN_SEVERITY, Some(" warn "))], CmdSeverity::minimum);
assert_eq!(warning_alias, CmdSeverity::Warning);
let error = with_vars([(SHELL_LINT_MIN_SEVERITY, Some("ERROR"))], CmdSeverity::minimum);
assert_eq!(error, CmdSeverity::Error);
let invalid = with_vars([(SHELL_LINT_MIN_SEVERITY, Some("unknown"))], CmdSeverity::minimum);
assert_eq!(invalid, CmdSeverity::Hint);
}
#[cfg(feature = "shell-lint")]
#[test]
fn test_diagnostics_at_or_above_severity() {
let source = "if true; then echo hello";
let parse_result = Parser::with_dialect(source, ShellDialect::Bash.parser_dialect()).parse();
let settings = LinterSettings::default().with_shell(ShellDialect::Bash);
let base = lint(AnalysisRequest::from_parse_result(&parse_result, source, &settings))
.first()
.cloned()
.expect("expected at least one diagnostic for malformed bash source");
let mut hint = base.clone();
hint.severity = LintSeverity::Hint;
let mut warning = base.clone();
warning.severity = LintSeverity::Warning;
let mut error = base;
error.severity = LintSeverity::Error;
let diagnostics = vec![hint, warning, error];
let hint_or_above = CmdSeverity::at_or_above(diagnostics.clone(), CmdSeverity::Hint);
assert_eq!(hint_or_above.len(), 3);
let warning_or_above = CmdSeverity::at_or_above(diagnostics.clone(), CmdSeverity::Warning);
assert_eq!(warning_or_above.len(), 2);
assert!(warning_or_above.iter().all(|diagnostic| diagnostic.severity >= LintSeverity::Warning));
let error_or_above = CmdSeverity::at_or_above(diagnostics, CmdSeverity::Error);
assert_eq!(error_or_above.len(), 1);
assert!(error_or_above.iter().all(|diagnostic| diagnostic.severity >= LintSeverity::Error));
}
#[test]
fn test_error() {
assert!(cmd!("not-a-real-command-xyz", ["arg"]).is_err());
assert!(cmd!(sh "not-a-real-command-xyz arg").is_err());
}
#[test]
fn test_output() {
let output = cmd!(sh sh_echo("sh-output")).expect("sh string");
assert!(output.status.success());
assert_eq!(output.stdout(), "sh-output");
let output = cmd!(sh sh_echo("one two three")).expect("sh multi-arg");
assert!(output.status.success());
assert_eq!(output.stdout(), "one two three");
let name = "world";
let output = cmd!(sh sh_echo(&format!("hello-{name}"))).expect("sh format!");
assert!(output.status.success());
assert_eq!(output.stdout(), "hello-world");
let output = if cfg!(unix) {
cmd!("echo", ["hello"]).expect("array literal")
} else {
cmd!("cmd", ["/c", "echo", "hello"]).expect("array literal")
};
assert!(output.status.success());
assert_eq!(output.stdout(), "hello");
let args = echo_args("world");
let output = cmd!(echo_bin(), args).expect("args variable");
assert!(output.status.success());
assert_eq!(output.stdout(), "world");
let output = if cfg!(unix) {
cmd!("echo" "cli-style").expect("cli-style")
} else {
cmd!("cmd" "/c" "echo" "cli-style").expect("cli-style")
};
assert!(output.status.success());
assert_eq!(output.stdout(), "cli-style");
let output = cmd!(stderr_bin(), stderr_args("oops")).expect("stderr");
if cfg!(unix) {
assert_eq!(output.stderr(), "oops");
} else {
assert!(output.stderr().contains("oops") || output.stdout().contains("oops"));
}
let output = cmd!(exit_bin(), exit_args(42)).expect("exit code");
assert!(!output.status.success());
}
#[cfg(feature = "shell-lint")]
#[test]
fn test_shell_lint_blocks_invalid_bash() {
if command_exists("bash") {
let result = cmd!(bash "if true; then echo hello");
assert!(result.is_err());
}
}
#[test]
fn test_shell_selection_output() {
if command_exists("bash") {
match cmd!(status bash "echo shell-bash-check") {
| Ok(status) if status.success() => {
let output = cmd!(bash "echo shell-bash-output").expect("bash output");
assert!(output.status.success());
assert_eq!(output.stdout(), "shell-bash-output");
}
| _ => {}
}
}
if pwsh_is_healthy() {
match cmd!(status pwsh "Write-Output 'shell-pwsh-check'") {
| Ok(status) if status.success() => {
let output = cmd!(pwsh "Write-Output 'shell-pwsh-output'").expect("pwsh output");
assert!(output.status.success());
assert_eq!(output.stdout(), "shell-pwsh-output");
}
| _ => {}
}
}
}
#[test]
fn test_shell_selection_status_and_try() {
if command_exists("bash") {
match cmd!(bash status "echo shell-bash-check") {
| Ok(status) if status.success() => {
let status = cmd!(bash status "echo shell-bash-status").expect("bash status");
assert!(status.success());
let value = cmd!(try bash "echo shell-bash-try").expect("bash try");
assert_eq!(value, "shell-bash-try");
}
| _ => {}
}
}
if pwsh_is_healthy() {
match cmd!(pwsh status "Write-Output 'shell-pwsh-check'") {
| Ok(status) if status.success() => {
let status = cmd!(pwsh status "Write-Output 'shell-pwsh-status'").expect("pwsh status");
assert!(status.success());
let value = cmd!(try pwsh "Write-Output 'shell-pwsh-try'").expect("pwsh try");
assert_eq!(value, "shell-pwsh-try");
}
| _ => {}
}
}
}
#[test]
fn test_status() {
let status = cmd!(sh status sh_echo("sh-status")).expect("sh string");
assert!(status.success());
let arg = "check";
let status = cmd!(sh status sh_echo(arg)).expect("sh format!");
assert!(status.success());
let status = if cfg!(unix) {
cmd!(status "echo", ["hello"]).expect("array literal")
} else {
cmd!(status "cmd", ["/c", "echo", "hello"]).expect("array literal")
};
assert!(status.success());
let args = echo_args("hello");
let status = cmd!(status echo_bin(), args).expect("args variable");
assert!(status.success());
let status = if cfg!(unix) {
cmd!(status "echo" "status-check").expect("cli-style")
} else {
cmd!(status "cmd" "/c" "echo" "status-check").expect("cli-style")
};
assert!(status.success());
let status = cmd!(status exit_bin(), exit_args(42)).expect("exit code");
assert!(!status.success());
}
}