From d3dae057148b20319cf5a09890dfd7d78f563348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20N=2E=20Robalino?= Date: Sun, 12 Jan 2020 16:44:22 -0500 Subject: [PATCH] Groundwork for coverage with Nu internals. (#1205) --- .azure/azure-pipelines.yml | 8 +- Cargo.toml | 17 ++++ crates/nu-test-support/src/bin/chop.rs | 22 ++++ crates/nu-test-support/src/bin/cococo.rs | 17 ++++ crates/nu-test-support/src/bin/fail.rs | 3 + crates/nu-test-support/src/fs.rs | 15 ++- crates/nu-test-support/src/lib.rs | 2 +- src/commands/classified/external.rs | 123 +++++++++++++++++++++-- 8 files changed, 190 insertions(+), 17 deletions(-) create mode 100644 crates/nu-test-support/src/bin/chop.rs create mode 100644 crates/nu-test-support/src/bin/cococo.rs create mode 100644 crates/nu-test-support/src/bin/fail.rs diff --git a/.azure/azure-pipelines.yml b/.azure/azure-pipelines.yml index fb58390aa5..59f0ee58fd 100644 --- a/.azure/azure-pipelines.yml +++ b/.azure/azure-pipelines.yml @@ -42,16 +42,16 @@ steps: echo "##vso[task.prependpath]$HOME/.cargo/bin" rustup component add rustfmt --toolchain "stable" displayName: Install Rust - - bash: RUSTFLAGS="-D warnings" cargo test --all --features=stable + - bash: RUSTFLAGS="-D warnings" cargo test --all --features=stable,nu-dummies condition: eq(variables['style'], 'unflagged') displayName: Run tests - - bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used + - bash: RUSTFLAGS="-D warnings" cargo clippy --all --features=stable,nu-dummies -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used condition: eq(variables['style'], 'unflagged') displayName: Check clippy lints - - bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features=stable + - bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo test --all --features=stable,nu-dummies condition: eq(variables['style'], 'canary') displayName: Run tests - - bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used + - bash: NUSHELL_ENABLE_ALL_FLAGS=1 RUSTFLAGS="-D warnings" cargo clippy --all --features=stable,nu-dummies -- -D clippy::result_unwrap_used -D clippy::option_unwrap_used condition: eq(variables['style'], 'canary') displayName: Check clippy lints - bash: cargo fmt --all -- --check diff --git a/Cargo.toml b/Cargo.toml index b0387c8a55..cee23204b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -142,6 +142,8 @@ users = "0.9" default = ["sys", "ps", "textview", "inc", "str"] stable = ["sys", "ps", "textview", "inc", "str", "starship-prompt", "binaryview", "match", "tree", "average", "sum", "post", "fetch", "clipboard"] +nu-dummies = [] + # Default sys = ["heim", "battery"] ps = ["heim", "futures-timer"] @@ -178,6 +180,21 @@ name = "nu" doctest = false path = "src/lib.rs" +[[bin]] +name = "fail" +path = "crates/nu-test-support/src/bin/fail.rs" +required-features = ["nu-dummies"] + +[[bin]] +name = "chop" +path = "crates/nu-test-support/src/bin/chop.rs" +required-features = ["nu-dummies"] + +[[bin]] +name = "cococo" +path = "crates/nu-test-support/src/bin/cococo.rs" +required-features = ["nu-dummies"] + # Core plugins that ship with `cargo install nu` by default # Currently, Cargo limits us to installing only one binary # unless we use [[bin]], so we use this as a workaround diff --git a/crates/nu-test-support/src/bin/chop.rs b/crates/nu-test-support/src/bin/chop.rs new file mode 100644 index 0000000000..4e9505b00e --- /dev/null +++ b/crates/nu-test-support/src/bin/chop.rs @@ -0,0 +1,22 @@ +use std::io::{self, BufRead}; + +fn main() { + let stdin = io::stdin(); + + let mut input = stdin.lock().lines(); + + if let Some(Ok(given)) = input.next() { + if !given.is_empty() { + println!("{}", chop(&given)); + std::process::exit(0); + } + } + + std::process::exit(0); +} + +fn chop(word: &str) -> &str { + let to = word.len() - 1; + + &word[..to] +} diff --git a/crates/nu-test-support/src/bin/cococo.rs b/crates/nu-test-support/src/bin/cococo.rs new file mode 100644 index 0000000000..baf154444a --- /dev/null +++ b/crates/nu-test-support/src/bin/cococo.rs @@ -0,0 +1,17 @@ +fn main() { + let args: Vec = std::env::args().collect(); + + if args.len() > 1 { + // Write back out all the arguments passed + // if given at least 1 instead of chickens + // speaking co co co. + let mut arguments = args.iter(); + arguments.next(); + + for arg in arguments { + println!("{}", &arg); + } + } else { + println!("cococo"); + } +} diff --git a/crates/nu-test-support/src/bin/fail.rs b/crates/nu-test-support/src/bin/fail.rs new file mode 100644 index 0000000000..081ca869c8 --- /dev/null +++ b/crates/nu-test-support/src/bin/fail.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(1); +} diff --git a/crates/nu-test-support/src/fs.rs b/crates/nu-test-support/src/fs.rs index 5297c222cf..44900d0585 100644 --- a/crates/nu-test-support/src/fs.rs +++ b/crates/nu-test-support/src/fs.rs @@ -220,11 +220,16 @@ pub fn delete_directory_at(full_path: &str) { } pub fn executable_path() -> PathBuf { - let mut buf = PathBuf::new(); - buf.push("target"); - buf.push("debug"); - buf.push("nu"); - buf + let mut path = binaries(); + path.push("nu"); + path +} + +pub fn binaries() -> PathBuf { + let mut path = PathBuf::new(); + path.push("target"); + path.push("debug"); + path } pub fn in_directory(str: impl AsRef) -> String { diff --git a/crates/nu-test-support/src/lib.rs b/crates/nu-test-support/src/lib.rs index 3642c8e723..184cfa0a4b 100644 --- a/crates/nu-test-support/src/lib.rs +++ b/crates/nu-test-support/src/lib.rs @@ -13,7 +13,7 @@ pub fn pipeline(commands: &str) -> String { .to_string() } -#[cfg(tests)] +#[cfg(test)] mod tests { use super::pipeline; diff --git a/src/commands/classified/external.rs b/src/commands/classified/external.rs index 4cde3fa6f6..c55b3d4edf 100644 --- a/src/commands/classified/external.rs +++ b/src/commands/classified/external.rs @@ -159,6 +159,17 @@ async fn run_with_iterator_arg( } } +pub fn argument_is_quoted(argument: &str) -> bool { + (argument.starts_with('"') && argument.ends_with('"') + || (argument.starts_with('\'') && argument.ends_with('\''))) +} + +pub fn remove_quotes(argument: &str) -> &str { + let size = argument.len(); + + &argument[1..size - 1] +} + async fn run_with_stdin( command: ExternalCommand, context: &mut Context, @@ -169,19 +180,17 @@ async fn run_with_stdin( let home_dir = dirs::home_dir(); let mut process = Exec::cmd(&command.name); + for arg in command.args.iter() { // Let's also replace ~ as we shell out let arg = shellexpand::tilde_with_context(arg.deref(), || home_dir.as_ref()); // Strip quotes from a quoted string - if arg.len() > 1 - && ((arg.starts_with('"') && arg.ends_with('"')) - || (arg.starts_with('\'') && arg.ends_with('\''))) - { - process = process.arg(arg.chars().skip(1).take(arg.len() - 2).collect::()); + process = if arg.len() > 1 && (argument_is_quoted(&arg)) { + process.arg(remove_quotes(&arg)) } else { - process = process.arg(arg.as_ref()); - } + process.arg(arg.as_ref()) + }; } process = process.cwd(context.shell_manager.path()?); @@ -315,3 +324,103 @@ async fn run_with_stdin( )) } } + +#[cfg(test)] +mod tests { + use super::{argument_is_quoted, remove_quotes, run_external_command, Context, OutputStream}; + use futures::executor::block_on; + use futures::stream::TryStreamExt; + use nu_errors::ShellError; + use nu_parser::commands::classified::external::{ExternalArgs, ExternalCommand}; + use nu_protocol::{UntaggedValue, Value}; + use nu_source::{Span, SpannedItem, Tag}; + + async fn read(mut stream: OutputStream) -> Option { + match stream.try_next().await { + Ok(val) => { + if let Some(val) = val { + val.raw_value() + } else { + None + } + } + Err(_) => None, + } + } + + fn external(name: &str) -> ExternalCommand { + let mut path = nu_test_support::fs::binaries(); + path.push(name); + + let name = path.to_string_lossy().to_string().spanned(Span::unknown()); + + ExternalCommand { + name: name.to_string(), + name_tag: Tag { + anchor: None, + span: name.span, + }, + args: ExternalArgs { + list: vec![], + span: name.span, + }, + } + } + + async fn non_existent_run() -> Result<(), ShellError> { + let cmd = external("i_dont_exist.exe"); + + let mut ctx = Context::basic().expect("There was a problem creating a basic context."); + + assert!(run_external_command(cmd, &mut ctx, None, false) + .await + .is_err()); + + Ok(()) + } + + async fn failure_run() -> Result<(), ShellError> { + let cmd = external("fail"); + + let mut ctx = Context::basic().expect("There was a problem creating a basic context."); + let stream = run_external_command(cmd, &mut ctx, None, false) + .await? + .expect("There was a problem running the external command."); + + match read(stream.into()).await { + Some(Value { + value: UntaggedValue::Error(_), + .. + }) => {} + None | _ => panic!("Command didn't fail."), + } + + Ok(()) + } + + #[test] + fn identifies_command_failed() -> Result<(), ShellError> { + block_on(failure_run()) + } + + #[test] + fn identifies_command_not_found() -> Result<(), ShellError> { + block_on(non_existent_run()) + } + + #[test] + fn checks_quotes_from_argument_to_be_passed_in() { + assert_eq!(argument_is_quoted("'andrés"), false); + assert_eq!(argument_is_quoted("andrés'"), false); + assert_eq!(argument_is_quoted(r#""andrés"#), false); + assert_eq!(argument_is_quoted(r#"andrés""#), false); + assert_eq!(argument_is_quoted("'andrés'"), true); + assert_eq!(argument_is_quoted(r#""andrés""#), true); + } + + #[test] + fn strips_quotes_from_argument_to_be_passed_in() { + assert_eq!(remove_quotes(r#"'andrés'"#), "andrés"); + assert_eq!(remove_quotes(r#""andrés""#), "andrés"); + } +}