diff --git a/Cargo.lock b/Cargo.lock index dbac886008..0e77f31646 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3844,6 +3844,7 @@ dependencies = [ "rmp", "roxmltree", "rstest", + "rstest_reuse", "rusqlite", "scopeguard", "serde", @@ -6309,6 +6310,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "rstest_reuse" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a8fb4672e840a587a66fc577a5491375df51ddb88f2a2c2a792598c326fe14" +dependencies = [ + "quote", + "rand", + "syn 2.0.90", +] + [[package]] name = "rusqlite" version = "0.31.0" diff --git a/Cargo.toml b/Cargo.toml index 1692c44ed2..70b3fb369f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,6 +145,7 @@ rmp = "0.8" rmp-serde = "1.3" roxmltree = "0.20" rstest = { version = "0.23", default-features = false } +rstest_reuse = "0.7" rusqlite = "0.31" rust-embed = "8.5.0" scopeguard = { version = "1.2.0" } diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index 42ad13c41d..bfbd091847 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -196,6 +196,7 @@ mockito = { workspace = true, default-features = false } quickcheck = { workspace = true } quickcheck_macros = { workspace = true } rstest = { workspace = true, default-features = false } +rstest_reuse = { workspace = true } pretty_assertions = { workspace = true } tempfile = { workspace = true } -rand_chacha = { workspace = true } \ No newline at end of file +rand_chacha = { workspace = true } diff --git a/crates/nu-command/src/system/exec.rs b/crates/nu-command/src/system/exec.rs index ca0ae4d216..014f83dea8 100644 --- a/crates/nu-command/src/system/exec.rs +++ b/crates/nu-command/src/system/exec.rs @@ -1,3 +1,5 @@ +use std::borrow::Cow; + use nu_engine::{command_prelude::*, env_to_strings}; #[derive(Clone)] @@ -11,7 +13,11 @@ impl Command for Exec { fn signature(&self) -> Signature { Signature::build("exec") .input_output_types(vec![(Type::Nothing, Type::Any)]) - .required("command", SyntaxShape::String, "The command to execute.") + .rest( + "command", + SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Any]), + "External command to run, with arguments.", + ) .allows_unknown_args() .category(Category::System) } @@ -33,15 +39,29 @@ On Windows based systems, Nushell will wait for the command to finish and then e _input: PipelineData, ) -> Result { let cwd = engine_state.cwd(Some(stack))?; + let rest = call.rest::(engine_state, stack, 0)?; + let name_args = rest.split_first(); + + let Some((name, call_args)) = name_args else { + return Err(ShellError::MissingParameter { + param_name: "no command given".into(), + span: call.head, + }); + }; + + let name_str: Cow = match &name { + Value::Glob { val, .. } => Cow::Borrowed(val), + Value::String { val, .. } => Cow::Borrowed(val), + _ => Cow::Owned(name.clone().coerce_into_string()?), + }; // Find the absolute path to the executable. If the command is not // found, display a helpful error message. - let name: Spanned = call.req(engine_state, stack, 0)?; let executable = { let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let Some(executable) = crate::which(&name.item, &paths, cwd.as_ref()) else { + let Some(executable) = crate::which(name_str.as_ref(), &paths, cwd.as_ref()) else { return Err(crate::command_not_found( - &name.item, + &name_str, call.head, engine_state, stack, @@ -73,7 +93,7 @@ On Windows based systems, Nushell will wait for the command to finish and then e } // Configure args. - let args = crate::eval_arguments_from_call(engine_state, stack, call)?; + let args = crate::eval_external_arguments(engine_state, stack, call_args.to_vec())?; command.args(args.into_iter().map(|s| s.item)); // Execute the child process, replacing/terminating the current process diff --git a/crates/nu-command/src/system/mod.rs b/crates/nu-command/src/system/mod.rs index a87aac8a07..57a314d383 100644 --- a/crates/nu-command/src/system/mod.rs +++ b/crates/nu-command/src/system/mod.rs @@ -33,7 +33,7 @@ pub use nu_check::NuCheck; pub use ps::Ps; #[cfg(windows)] pub use registry_query::RegistryQuery; -pub use run_external::{command_not_found, eval_arguments_from_call, which, External}; +pub use run_external::{command_not_found, eval_external_arguments, which, External}; pub use sys::*; pub use uname::UName; pub use which_::Which; diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index e4ac0ea88f..b917577ba9 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -34,15 +34,10 @@ impl Command for External { fn signature(&self) -> nu_protocol::Signature { Signature::build(self.name()) .input_output_types(vec![(Type::Any, Type::Any)]) - .required( - "command", - SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]), - "External command to run.", - ) .rest( - "args", + "command", SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::Any]), - "Arguments for external command.", + "External command to run, with arguments.", ) .category(Category::System) } @@ -55,7 +50,15 @@ impl Command for External { input: PipelineData, ) -> Result { let cwd = engine_state.cwd(Some(stack))?; - let name: Value = call.req(engine_state, stack, 0)?; + let rest = call.rest::(engine_state, stack, 0)?; + let name_args = rest.split_first(); + + let Some((name, call_args)) = name_args else { + return Err(ShellError::MissingParameter { + param_name: "no command given".into(), + span: call.head, + }); + }; let name_str: Cow = match &name { Value::Glob { val, .. } => Cow::Borrowed(val), @@ -151,7 +154,7 @@ impl Command for External { command.envs(envs); // Configure args. - let args = eval_arguments_from_call(engine_state, stack, call)?; + let args = eval_external_arguments(engine_state, stack, call_args.to_vec())?; #[cfg(windows)] if is_cmd_internal_command(&name_str) || potential_nuscript_in_windows { // The /D flag disables execution of AutoRun commands from registry. @@ -290,14 +293,13 @@ impl Command for External { } } -/// Evaluate all arguments from a call, performing expansions when necessary. -pub fn eval_arguments_from_call( +/// Evaluate all arguments, performing expansions when necessary. +pub fn eval_external_arguments( engine_state: &EngineState, stack: &mut Stack, - call: &Call, + call_args: Vec, ) -> Result>, ShellError> { let cwd = engine_state.cwd(Some(stack))?; - let call_args = call.rest::(engine_state, stack, 1)?; let mut args: Vec> = Vec::with_capacity(call_args.len()); for arg in call_args { diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 856214456c..5dc68405a9 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -1,23 +1,34 @@ use nu_test_support::fs::Stub::EmptyFile; +use nu_test_support::nu; use nu_test_support::playground::Playground; -use nu_test_support::{nu, pipeline}; +use rstest::rstest; +use rstest_reuse::*; -#[test] -fn better_empty_redirection() { +// Template for run-external test to ensure tests work when calling +// the binary directly, using the caret operator, and when using +// the run-external command +#[template] +#[rstest] +#[case("")] +#[case("^")] +#[case("run-external ")] +fn run_external_prefixes(#[case] prefix: &str) {} + +#[apply(run_external_prefixes)] +fn better_empty_redirection(prefix: &str) { let actual = nu!( - cwd: "tests/fixtures/formats", pipeline( - " - ls | each { |it| nu --testbin cococo $it.name } | ignore - " - )); + cwd: "tests/fixtures/formats", + "ls | each {{ |it| {}nu `--testbin` cococo $it.name }} | ignore", + prefix + ); eprintln!("out: {}", actual.out); assert!(!actual.out.contains('2')); } -#[test] -fn explicit_glob() { +#[apply(run_external_prefixes)] +fn explicit_glob(prefix: &str) { Playground::setup("external with explicit glob", |dirs, sandbox| { sandbox.with_files(&[ EmptyFile("D&D_volume_1.txt"), @@ -26,19 +37,20 @@ fn explicit_glob() { ]); let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - ^nu --testbin cococo ('*.txt' | into glob) - "# - )); + {}nu `--testbin` cococo ('*.txt' | into glob) + "#, + prefix + ); assert!(actual.out.contains("D&D_volume_1.txt")); assert!(actual.out.contains("D&D_volume_2.txt")); }) } -#[test] -fn bare_word_expand_path_glob() { +#[apply(run_external_prefixes)] +fn bare_word_expand_path_glob(prefix: &str) { Playground::setup("bare word should do the expansion", |dirs, sandbox| { sandbox.with_files(&[ EmptyFile("D&D_volume_1.txt"), @@ -47,19 +59,20 @@ fn bare_word_expand_path_glob() { ]); let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), " - ^nu --testbin cococo *.txt - " - )); + {}nu `--testbin` cococo *.txt + ", + prefix + ); assert!(actual.out.contains("D&D_volume_1.txt")); assert!(actual.out.contains("D&D_volume_2.txt")); }) } -#[test] -fn backtick_expand_path_glob() { +#[apply(run_external_prefixes)] +fn backtick_expand_path_glob(prefix: &str) { Playground::setup("backtick should do the expansion", |dirs, sandbox| { sandbox.with_files(&[ EmptyFile("D&D_volume_1.txt"), @@ -68,19 +81,20 @@ fn backtick_expand_path_glob() { ]); let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - ^nu --testbin cococo `*.txt` - "# - )); + {}nu `--testbin` cococo `*.txt` + "#, + prefix + ); assert!(actual.out.contains("D&D_volume_1.txt")); assert!(actual.out.contains("D&D_volume_2.txt")); }) } -#[test] -fn single_quote_does_not_expand_path_glob() { +#[apply(run_external_prefixes)] +fn single_quote_does_not_expand_path_glob(prefix: &str) { Playground::setup("single quote do not run the expansion", |dirs, sandbox| { sandbox.with_files(&[ EmptyFile("D&D_volume_1.txt"), @@ -89,18 +103,19 @@ fn single_quote_does_not_expand_path_glob() { ]); let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - ^nu --testbin cococo '*.txt' - "# - )); + {}nu `--testbin` cococo '*.txt' + "#, + prefix + ); assert_eq!(actual.out, "*.txt"); }) } -#[test] -fn double_quote_does_not_expand_path_glob() { +#[apply(run_external_prefixes)] +fn double_quote_does_not_expand_path_glob(prefix: &str) { Playground::setup("double quote do not run the expansion", |dirs, sandbox| { sandbox.with_files(&[ EmptyFile("D&D_volume_1.txt"), @@ -109,44 +124,50 @@ fn double_quote_does_not_expand_path_glob() { ]); let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - ^nu --testbin cococo "*.txt" - "# - )); + {}nu `--testbin` cococo "*.txt" + "#, + prefix + ); assert_eq!(actual.out, "*.txt"); }) } -#[test] -fn failed_command_with_semicolon_will_not_execute_following_cmds() { +#[apply(run_external_prefixes)] +fn failed_command_with_semicolon_will_not_execute_following_cmds(prefix: &str) { Playground::setup("external failed command with semicolon", |dirs, _| { let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), " - nu --testbin fail; echo done - " - )); + {}nu `--testbin` fail; echo done + ", + prefix + ); assert!(!actual.out.contains("done")); }) } -#[test] -fn external_args_with_quoted() { +#[apply(run_external_prefixes)] +fn external_args_with_quoted(prefix: &str) { Playground::setup("external failed command with semicolon", |dirs, _| { let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - nu --testbin cococo "foo=bar 'hi'" - "# - )); + {}nu `--testbin` cococo "foo=bar 'hi'" + "#, + prefix + ); assert_eq!(actual.out, "foo=bar 'hi'"); }) } +// don't use template for this once since echo with no prefix is an internal command +// and arguments flags are treated as arguments to run-external +// (but wrapping them in quotes defeats the point of test) #[cfg(not(windows))] #[test] fn external_arg_with_option_like_embedded_quotes() { @@ -155,58 +176,67 @@ fn external_arg_with_option_like_embedded_quotes() { "external arg with option like embedded quotes", |dirs, _| { let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - ^echo --foo='bar' -foo='bar' - "# - )); + ^echo --foo='bar' -foo='bar' + "#, + ); assert_eq!(actual.out, "--foo=bar -foo=bar"); }, ) } -#[test] -fn external_arg_with_non_option_like_embedded_quotes() { +// FIXME: parser complains about invalid characters after single quote +#[rstest] +#[case("")] +#[case("^")] +fn external_arg_with_non_option_like_embedded_quotes(#[case] prefix: &str) { Playground::setup( "external arg with non option like embedded quotes", |dirs, _| { let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - ^nu --testbin cococo foo='bar' 'foo'=bar - "# - )); + {}nu `--testbin` cococo foo='bar' 'foo'=bar + "#, + prefix + ); assert_eq!(actual.out, "foo=bar foo=bar"); }, ) } -#[test] -fn external_arg_with_string_interpolation() { +// FIXME: parser bug prevents expressions from appearing within GlobPattern substrings +#[rstest] +#[case("")] +#[case("^")] +fn external_arg_with_string_interpolation(#[case] prefix: &str) { Playground::setup("external arg with string interpolation", |dirs, _| { let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - ^nu --testbin cococo foo=(2 + 2) $"foo=(2 + 2)" foo=$"(2 + 2)" - "# - )); + {}nu `--testbin` cococo foo=(2 + 2) $"foo=(2 + 2)" foo=$"(2 + 2)" + "#, + prefix + ); assert_eq!(actual.out, "foo=4 foo=4 foo=4"); }) } -#[test] -fn external_arg_with_variable_name() { +#[apply(run_external_prefixes)] +fn external_arg_with_variable_name(prefix: &str) { Playground::setup("external failed command with semicolon", |dirs, _| { let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), 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 - "# - )); + {}nu `--testbin` nonu $dump_command + "#, + prefix + ); assert_eq!( actual.out, @@ -215,25 +245,29 @@ fn external_arg_with_variable_name() { }) } -#[test] -fn external_command_escape_args() { +#[apply(run_external_prefixes)] +fn external_command_escape_args(prefix: &str) { Playground::setup("external failed command with semicolon", |dirs, _| { let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - nu --testbin cococo "\"abcd" - "# - )); + {}nu `--testbin` cococo "\"abcd" + "#, + prefix + ); assert_eq!(actual.out, r#""abcd"#); }) } -#[test] -fn external_command_ndots_args() { - let actual = nu!(r#" - nu --testbin cococo foo/. foo/.. foo/... foo/./bar foo/../bar foo/.../bar ./bar ../bar .../bar - "#); +#[apply(run_external_prefixes)] +fn external_command_ndots_args(prefix: &str) { + let actual = nu!( + r#" + {}nu `--testbin` cococo foo/. foo/.. foo/... foo/./bar foo/../bar foo/.../bar ./bar ../bar .../bar + "#, + prefix + ); assert_eq!( actual.out, @@ -247,23 +281,29 @@ fn external_command_ndots_args() { ); } -#[test] -fn external_command_ndots_leading_dot_slash() { +#[apply(run_external_prefixes)] +fn external_command_ndots_leading_dot_slash(prefix: &str) { // Don't expand ndots with a leading `./` - let actual = nu!(r#" - nu --testbin cococo ./... ./.... - "#); + let actual = nu!( + r#" + {}nu `--testbin` cococo ./... ./.... + "#, + prefix + ); assert_eq!(actual.out, "./... ./...."); } -#[test] -fn external_command_url_args() { +#[apply(run_external_prefixes)] +fn external_command_url_args(prefix: &str) { // If ndots is not handled correctly, we can lose the double forward slashes that are needed // here - let actual = nu!(r#" - nu --testbin cococo http://example.com http://example.com/.../foo //foo - "#); + let actual = nu!( + r#" + {}nu `--testbin` cococo http://example.com http://example.com/.../foo //foo + "#, + prefix + ); assert_eq!( actual.out, @@ -271,12 +311,12 @@ fn external_command_url_args() { ); } -#[test] +#[apply(run_external_prefixes)] #[cfg_attr( not(target_os = "linux"), ignore = "only runs on Linux, where controlling the HOME var is reliable" )] -fn external_command_expand_tilde() { +fn external_command_expand_tilde(prefix: &str) { Playground::setup("external command expand tilde", |dirs, _| { // Make a copy of the nu executable that we can use let mut src = std::fs::File::open(nu_test_support::fs::binaries().join("nu")) @@ -302,22 +342,27 @@ fn external_command_expand_tilde() { ("HOME".to_string(), dirs.test().to_string_lossy().into_owned()), ], r#" - ^~/test_nu --testbin cococo hello - "# + {}~/test_nu `--testbin` cococo hello + "#, + prefix ); assert_eq!(actual.out, "hello"); }) } -#[test] -fn external_arg_expand_tilde() { +// FIXME: parser bug prevents expressions from appearing within GlobPattern substrings +#[rstest] +#[case("")] +#[case("^")] +fn external_arg_expand_tilde(#[case] prefix: &str) { Playground::setup("external arg expand tilde", |dirs, _| { let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - ^nu --testbin cococo ~/foo ~/(2 + 2) - "# - )); + {}nu `--testbin` cococo ~/foo ~/(2 + 2) + "#, + prefix + ); let home = dirs::home_dir().expect("failed to find home dir"); @@ -332,40 +377,43 @@ fn external_arg_expand_tilde() { }) } -#[test] -fn external_command_not_expand_tilde_with_quotes() { +#[apply(run_external_prefixes)] +fn external_command_not_expand_tilde_with_quotes(prefix: &str) { Playground::setup( "external command not expand tilde with quotes", |dirs, _| { - let actual = nu!(cwd: dirs.test(), pipeline(r#"nu --testbin nonu "~""#)); + let actual = nu!(cwd: dirs.test(), r#"{}nu `--testbin` nonu "~""#, prefix); assert_eq!(actual.out, r#"~"#); }, ) } -#[test] -fn external_command_expand_tilde_with_back_quotes() { +#[apply(run_external_prefixes)] +fn external_command_expand_tilde_with_back_quotes(prefix: &str) { Playground::setup( "external command not expand tilde with quotes", |dirs, _| { - let actual = nu!(cwd: dirs.test(), pipeline(r#"nu --testbin nonu `~`"#)); + let actual = nu!(cwd: dirs.test(), r#"{}nu `--testbin` nonu `~`"#, prefix); assert!(!actual.out.contains('~')); }, ) } -#[test] -fn external_command_receives_raw_binary_data() { +#[apply(run_external_prefixes)] +fn external_command_receives_raw_binary_data(prefix: &str) { Playground::setup("external command receives raw binary data", |dirs, _| { - let actual = - nu!(cwd: dirs.test(), pipeline("0x[deadbeef] | nu --testbin input_bytes_length")); + let actual = nu!( + cwd: dirs.test(), + "0x[deadbeef] | {}nu `--testbin` input_bytes_length", + prefix + ); assert_eq!(actual.out, r#"4"#); }) } #[cfg(windows)] -#[test] -fn can_run_cmd_files() { +#[apply(run_external_prefixes)] +fn can_run_cmd_files(prefix: &str) { use nu_test_support::fs::Stub::FileWithContent; Playground::setup("run a Windows cmd file", |dirs, sandbox| { sandbox.with_files(&[FileWithContent( @@ -376,14 +424,14 @@ fn can_run_cmd_files() { "#, )]); - let actual = nu!(cwd: dirs.test(), pipeline("foo.cmd")); + let actual = nu!(cwd: dirs.test(), "{}foo.cmd", prefix); assert!(actual.out.contains("Hello World")); }); } #[cfg(windows)] -#[test] -fn can_run_batch_files() { +#[apply(run_external_prefixes)] +fn can_run_batch_files(prefix: &str) { use nu_test_support::fs::Stub::FileWithContent; Playground::setup("run a Windows batch file", |dirs, sandbox| { sandbox.with_files(&[FileWithContent( @@ -394,14 +442,14 @@ fn can_run_batch_files() { "#, )]); - let actual = nu!(cwd: dirs.test(), pipeline("foo.bat")); + let actual = nu!(cwd: dirs.test(), "{}foo.bat", prefix); assert!(actual.out.contains("Hello World")); }); } #[cfg(windows)] -#[test] -fn can_run_batch_files_without_cmd_extension() { +#[apply(run_external_prefixes)] +fn can_run_batch_files_without_cmd_extension(prefix: &str) { use nu_test_support::fs::Stub::FileWithContent; Playground::setup( "run a Windows cmd file without specifying the extension", @@ -414,15 +462,15 @@ fn can_run_batch_files_without_cmd_extension() { "#, )]); - let actual = nu!(cwd: dirs.test(), pipeline("foo")); + let actual = nu!(cwd: dirs.test(), "{}foo", prefix); assert!(actual.out.contains("Hello World")); }, ); } #[cfg(windows)] -#[test] -fn can_run_batch_files_without_bat_extension() { +#[apply(run_external_prefixes)] +fn can_run_batch_files_without_bat_extension(prefix: &str) { use nu_test_support::fs::Stub::FileWithContent; Playground::setup( "run a Windows batch file without specifying the extension", @@ -435,34 +483,36 @@ fn can_run_batch_files_without_bat_extension() { "#, )]); - let actual = nu!(cwd: dirs.test(), pipeline("foo")); + let actual = nu!(cwd: dirs.test(), "{}foo", prefix); assert!(actual.out.contains("Hello World")); }, ); } -#[test] -fn quotes_trimmed_when_shelling_out() { +#[apply(run_external_prefixes)] +fn quotes_trimmed_when_shelling_out(prefix: &str) { // regression test for a bug where we weren't trimming quotes around string args before shelling out to cmd.exe - let actual = nu!(pipeline( + let actual = nu!( r#" - nu --testbin cococo "foo" - "# - )); + {}nu `--testbin` cococo "foo" + "#, + prefix + ); assert_eq!(actual.out, "foo"); } #[cfg(not(windows))] -#[test] -fn redirect_combine() { +#[apply(run_external_prefixes)] +fn redirect_combine(prefix: &str) { Playground::setup("redirect_combine", |dirs, _| { let actual = nu!( - cwd: dirs.test(), pipeline( + cwd: dirs.test(), r#" - run-external sh ...[-c 'echo Foo; echo >&2 Bar'] o+e>| print - "# - )); + {}sh ...[-c 'echo Foo; echo >&2 Bar'] o+e>| print + "#, + prefix + ); // Lines are collapsed in the nu! macro assert_eq!(actual.out, "FooBar"); @@ -470,8 +520,8 @@ fn redirect_combine() { } #[cfg(windows)] -#[test] -fn can_run_ps1_files() { +#[apply(run_external_prefixes)] +fn can_run_ps1_files(prefix: &str) { use nu_test_support::fs::Stub::FileWithContent; Playground::setup("run_a_windows_ps_file", |dirs, sandbox| { sandbox.with_files(&[FileWithContent( @@ -481,7 +531,7 @@ fn can_run_ps1_files() { "#, )]); - let actual = nu!(cwd: dirs.test(), pipeline("foo.ps1")); + let actual = nu!(cwd: dirs.test(), "{}foo.ps1", prefix); assert!(actual.out.contains("Hello World")); }); }