nushell/crates/nu-command/tests/commands/run_external.rs
Henry Jetmundsen 253b223e65
pipe binary data to external commands (#8058)
Fixes #7615 

# Description

When calling external commands, we create a table from the pipeline data
to handle external commands expecting paginated input. When a binary
value is made into a table, we convert the vector of bytes representing
the binary bytes into a pretty formatted string. This results in the
pretty formatted string being sent to external commands instead of the
actual binary bytes. By checking whether the stdout of the call is being
redirected, we can decide whether to send the raw binary bytes or the
pretty formatted output when creating a table command.

# User-Facing Changes

When passing binary values to external commands, the external command
will receive the actual bytes instead of the pretty printed string. Use
cases that don't involve piping a binary value into an external command
are unchanged.


![new_behavior](https://user-images.githubusercontent.com/32406734/218349172-24cd12f2-d563-4957-bdf1-6aa804b174b2.png)

# Tests + Formatting

Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

cargo fmt --all -- --check to check standard code formatting (cargo fmt
--all applies these changes)
cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect to check that you're using the standard code
style
    cargo test --workspace to check that all tests pass

# After Submitting

If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
2023-02-24 21:39:52 +01:00

312 lines
7.9 KiB
Rust

#[cfg(not(windows))]
use nu_test_support::fs::Stub::EmptyFile;
use nu_test_support::playground::Playground;
use nu_test_support::{nu, pipeline};
#[test]
fn better_empty_redirection() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
ls | each { |it| nu --testbin cococo $it.name } | ignore
"#
));
eprintln!("out: {}", actual.out);
assert!(!actual.out.contains('2'));
}
#[cfg(not(windows))]
#[test]
fn explicit_glob() {
Playground::setup("external with explicit glob", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("D&D_volume_1.txt"),
EmptyFile("D&D_volume_2.txt"),
EmptyFile("foo.sh"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^ls | glob '*.txt' | length
"#
));
assert_eq!(actual.out, "2");
})
}
#[cfg(not(windows))]
#[test]
fn bare_word_expand_path_glob() {
Playground::setup("bare word should do the expansion", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("D&D_volume_1.txt"),
EmptyFile("D&D_volume_2.txt"),
EmptyFile("foo.sh"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^ls *.txt
"#
));
assert!(actual.out.contains("D&D_volume_1.txt"));
assert!(actual.out.contains("D&D_volume_2.txt"));
})
}
#[cfg(not(windows))]
#[test]
fn backtick_expand_path_glob() {
Playground::setup("backtick should do the expansion", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("D&D_volume_1.txt"),
EmptyFile("D&D_volume_2.txt"),
EmptyFile("foo.sh"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^ls `*.txt`
"#
));
assert!(actual.out.contains("D&D_volume_1.txt"));
assert!(actual.out.contains("D&D_volume_2.txt"));
})
}
#[cfg(not(windows))]
#[test]
fn single_quote_does_not_expand_path_glob() {
Playground::setup("single quote do not run the expansion", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("D&D_volume_1.txt"),
EmptyFile("D&D_volume_2.txt"),
EmptyFile("foo.sh"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^ls '*.txt'
"#
));
assert!(actual.err.contains("No such file or directory"));
})
}
#[cfg(not(windows))]
#[test]
fn double_quote_does_not_expand_path_glob() {
Playground::setup("double quote do not run the expansion", |dirs, sandbox| {
sandbox.with_files(vec![
EmptyFile("D&D_volume_1.txt"),
EmptyFile("D&D_volume_2.txt"),
EmptyFile("foo.sh"),
]);
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^ls "*.txt"
"#
));
assert!(actual.err.contains("No such file or directory"));
})
}
#[cfg(not(windows))]
#[test]
fn failed_command_with_semicolon_will_not_execute_following_cmds() {
Playground::setup("external failed command with semicolon", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^ls *.abc; echo done
"#
));
assert!(!actual.out.contains("done"));
})
}
#[cfg(not(windows))]
#[test]
fn external_args_with_quoted() {
Playground::setup("external failed command with semicolon", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^echo "foo=bar 'hi'"
"#
));
assert_eq!(actual.out, "foo=bar 'hi'");
})
}
#[cfg(not(windows))]
#[test]
fn external_arg_with_long_flag_value_quoted() {
Playground::setup("external failed command with semicolon", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^echo --foo='bar'
"#
));
assert_eq!(actual.out, "--foo=bar");
})
}
#[test]
fn external_arg_with_variable_name() {
Playground::setup("external failed command with semicolon", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
let dump_command = "PGPASSWORD='db_secret' pg_dump -Fc -h 'db.host' -p '$db.port' -U postgres -d 'db_name' > '/tmp/dump_name'";
nu --testbin nonu $dump_command
"#
));
assert_eq!(
actual.out,
r#"PGPASSWORD='db_secret' pg_dump -Fc -h 'db.host' -p '$db.port' -U postgres -d 'db_name' > '/tmp/dump_name'"#
);
})
}
#[cfg(not(windows))]
#[test]
fn external_command_escape_args() {
Playground::setup("external failed command with semicolon", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^echo "\"abcd"
"#
));
assert_eq!(actual.out, r#""abcd"#);
})
}
#[test]
fn external_command_not_expand_tilde_with_quotes() {
Playground::setup(
"external command not expand tilde with quotes",
|dirs, _| {
let actual = nu!(cwd: dirs.test(), pipeline(r#"nu --testbin nonu "~""#));
assert_eq!(actual.out, r#"~"#);
},
)
}
#[test]
fn external_command_receives_raw_binary_data() {
Playground::setup("external command receives raw binary data", |dirs, _| {
let actual =
nu!(cwd: dirs.test(), pipeline(r#"0x[deadbeef] | nu --testbin input_bytes_length"#));
assert_eq!(actual.out, r#"4"#);
})
}
#[cfg(windows)]
#[test]
fn failed_command_with_semicolon_will_not_execute_following_cmds_windows() {
Playground::setup("external failed command with semicolon", |dirs, _| {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
^cargo asdf; echo done
"#
));
assert!(!actual.out.contains("done"));
})
}
#[cfg(windows)]
#[test]
fn can_run_batch_files() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup("run a Windows batch file", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"foo.cmd",
r#"
@echo off
echo Hello World
"#,
)]);
let actual = nu!(cwd: dirs.test(), pipeline("foo.cmd"));
assert!(actual.out.contains("Hello World"));
});
}
#[cfg(windows)]
#[test]
fn can_run_batch_files_without_cmd_extension() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup(
"run a Windows batch file without specifying the extension",
|dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"foo.cmd",
r#"
@echo off
echo Hello World
"#,
)]);
let actual = nu!(cwd: dirs.test(), pipeline("foo"));
assert!(actual.out.contains("Hello World"));
},
);
}
#[cfg(windows)]
#[test]
fn can_run_batch_files_without_bat_extension() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup(
"run a Windows batch file without specifying the extension",
|dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"foo.bat",
r#"
@echo off
echo Hello World
"#,
)]);
let actual = nu!(cwd: dirs.test(), pipeline("foo"));
assert!(actual.out.contains("Hello World"));
},
);
}
#[cfg(windows)]
#[test]
fn quotes_trimmed_when_shelling_out() {
// regression test for a bug where we weren't trimming quotes around string args before shelling out to cmd.exe
let actual = nu!(cwd: ".", pipeline(
r#"
^echo "foo"
"#
));
assert_eq!(actual.out, "foo");
}