diff --git a/.github/workflows/rust-linting.yml b/.github/workflows/rust-linting.yml index dc51ad6..bff4715 100644 --- a/.github/workflows/rust-linting.yml +++ b/.github/workflows/rust-linting.yml @@ -41,6 +41,8 @@ jobs: toolchain: stable profile: minimal override: true + + - uses: Swatinem/rust-cache@v2 - name: Run cargo clippy run: cargo clippy --all-targets --workspace -- -D warnings diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml index 5cf3a77..73aa07d 100644 --- a/.github/workflows/rust-tests.yml +++ b/.github/workflows/rust-tests.yml @@ -25,7 +25,8 @@ jobs: toolchain: stable profile: minimal override: true + + - uses: Swatinem/rust-cache@v2 - name: Run tests - # TODO: run on whole workspace with "cargo test --workspace --all-targets" - run: cargo test --manifest-path crates/shell/Cargo.toml --all-targets + run: cargo test --workspace --all-targets diff --git a/crates/deno_task_shell/src/parser.rs b/crates/deno_task_shell/src/parser.rs index 947cb2e..414b66a 100644 --- a/crates/deno_task_shell/src/parser.rs +++ b/crates/deno_task_shell/src/parser.rs @@ -1025,7 +1025,6 @@ fn parse_io_file(pair: Pair) -> Result<(RedirectOp, IoFile)> { #[cfg(test)] mod test { use super::*; - use pretty_assertions::assert_eq; #[test] fn test_main() { @@ -1048,373 +1047,6 @@ mod test { assert!(parse("echo \"foo\" > out.txt").is_ok()); } - #[test] - fn test_sequential_list() { - let parse_and_create = |input: &str| -> Result { - let pairs = ShellParser::parse(Rule::complete_command, input) - .map_err(|e| anyhow::Error::msg(e.to_string()))? - .next() - .unwrap(); - // println!("pairs: {:?}", pairs); - parse_complete_command(pairs) - }; - - // Test case 1 - let input = concat!( - "Name=Value OtherVar=Other command arg1 || command2 arg12 arg13 ; ", - "command3 && command4 & command5 ; export ENV6=5 ; ", - "ENV7=other && command8 || command9 ; ", - "cmd10 && (cmd11 || cmd12)" - ); - let result = parse_and_create(input).unwrap(); - let expected = SequentialList { - items: vec![ - SequentialListItem { - is_async: false, - sequence: Sequence::BooleanList(Box::new(BooleanList { - current: SimpleCommand { - env_vars: vec![ - EnvVar::new("Name".to_string(), Word::new_word("Value")), - EnvVar::new("OtherVar".to_string(), Word::new_word("Other")), - ], - args: vec![Word::new_word("command"), Word::new_word("arg1")], - } - .into(), - op: BooleanListOperator::Or, - next: SimpleCommand { - env_vars: vec![], - args: vec![ - Word::new_word("command2"), - Word::new_word("arg12"), - Word::new_word("arg13"), - ], - } - .into(), - })), - }, - SequentialListItem { - is_async: true, - sequence: Sequence::BooleanList(Box::new(BooleanList { - current: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("command3")], - } - .into(), - op: BooleanListOperator::And, - next: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("command4")], - } - .into(), - })), - }, - SequentialListItem { - is_async: false, - sequence: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("command5")], - } - .into(), - }, - SequentialListItem { - is_async: false, - sequence: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("export"), Word::new_word("ENV6=5")], - } - .into(), - }, - SequentialListItem { - is_async: false, - sequence: Sequence::BooleanList(Box::new(BooleanList { - current: Sequence::ShellVar(EnvVar::new( - "ENV7".to_string(), - Word::new_word("other"), - )), - op: BooleanListOperator::And, - next: Sequence::BooleanList(Box::new(BooleanList { - current: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("command8")], - } - .into(), - op: BooleanListOperator::Or, - next: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("command9")], - } - .into(), - })), - })), - }, - SequentialListItem { - is_async: false, - sequence: Sequence::BooleanList(Box::new(BooleanList { - current: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("cmd10")], - } - .into(), - op: BooleanListOperator::And, - next: Command { - inner: CommandInner::Subshell(Box::new(SequentialList { - items: vec![SequentialListItem { - is_async: false, - sequence: Sequence::BooleanList(Box::new(BooleanList { - current: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("cmd11")], - } - .into(), - op: BooleanListOperator::Or, - next: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("cmd12")], - } - .into(), - })), - }], - })), - redirect: None, - } - .into(), - })), - }, - ], - }; - assert_eq!(result, expected); - - // Test case 2 - let input = "command1 ; command2 ; A='b' command3"; - let result = parse_and_create(input).unwrap(); - let expected = SequentialList { - items: vec![ - SequentialListItem { - is_async: false, - sequence: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("command1")], - } - .into(), - }, - SequentialListItem { - is_async: false, - sequence: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("command2")], - } - .into(), - }, - SequentialListItem { - is_async: false, - sequence: SimpleCommand { - env_vars: vec![EnvVar::new("A".to_string(), Word::new_string("b"))], - args: vec![Word::new_word("command3")], - } - .into(), - }, - ], - }; - assert_eq!(result, expected); - - // Test case 3 - let input = "test &&"; - assert!(parse_and_create(input).is_err()); - - // Test case 4 - let input = "command &"; - let result = parse_and_create(input).unwrap(); - let expected = SequentialList { - items: vec![SequentialListItem { - is_async: true, - sequence: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("command")], - } - .into(), - }], - }; - assert_eq!(result, expected); - - // Test case 5 - let input = "test | other"; - let result = parse_and_create(input).unwrap(); - let expected = SequentialList { - items: vec![SequentialListItem { - is_async: false, - sequence: PipeSequence { - current: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("test")], - } - .into(), - op: PipeSequenceOperator::Stdout, - next: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("other")], - } - .into(), - } - .into(), - }], - }; - assert_eq!(result, expected); - - // Test case 6 - let input = "test |& other"; - let result = parse_and_create(input).unwrap(); - let expected = SequentialList { - items: vec![SequentialListItem { - is_async: false, - sequence: PipeSequence { - current: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("test")], - } - .into(), - op: PipeSequenceOperator::StdoutStderr, - next: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("other")], - } - .into(), - } - .into(), - }], - }; - assert_eq!(result, expected); - - // Test case 8 - let input = "echo $MY_ENV;"; - let result = parse_and_create(input).unwrap(); - let expected = SequentialList { - items: vec![SequentialListItem { - is_async: false, - sequence: SimpleCommand { - env_vars: vec![], - args: vec![ - Word::new_word("echo"), - Word(vec![WordPart::Variable("MY_ENV".to_string())]), - ], - } - .into(), - }], - }; - assert_eq!(result, expected); - - // Test case 9 - let input = "! cmd1 | cmd2 && cmd3"; - let result = parse_and_create(input).unwrap(); - let expected = SequentialList { - items: vec![SequentialListItem { - is_async: false, - sequence: Sequence::BooleanList(Box::new(BooleanList { - current: Pipeline { - negated: true, - inner: PipeSequence { - current: SimpleCommand { - args: vec![Word::new_word("cmd1")], - env_vars: vec![], - } - .into(), - op: PipeSequenceOperator::Stdout, - next: SimpleCommand { - args: vec![Word::new_word("cmd2")], - env_vars: vec![], - } - .into(), - } - .into(), - } - .into(), - op: BooleanListOperator::And, - next: SimpleCommand { - args: vec![Word::new_word("cmd3")], - env_vars: vec![], - } - .into(), - })), - }], - }; - assert_eq!(result, expected); - } - - #[test] - fn test_env_var() { - let parse_and_create = |input: &str| -> Result { - let pairs = ShellParser::parse(Rule::ASSIGNMENT_WORD, input) - .map_err(|e| anyhow::anyhow!(e.to_string()))? - .next() - .unwrap(); - parse_env_var(pairs) - }; - - assert_eq!( - parse_and_create("Name=Value").unwrap(), - EnvVar { - name: "Name".to_string(), - value: Word::new_word("Value"), - } - ); - - assert_eq!( - parse_and_create("Name='quoted value'").unwrap(), - EnvVar { - name: "Name".to_string(), - value: Word::new_string("quoted value"), - } - ); - - assert_eq!( - parse_and_create("Name=\"double quoted value\"").unwrap(), - EnvVar { - name: "Name".to_string(), - value: Word::new_string("double quoted value"), - } - ); - - assert_eq!( - parse_and_create("Name=").unwrap(), - EnvVar { - name: "Name".to_string(), - value: Word(vec![]), - } - ); - - assert_eq!( - parse_and_create("Name=$(test)").unwrap(), - EnvVar { - name: "Name".to_string(), - value: Word(vec![WordPart::Command(SequentialList { - items: vec![SequentialListItem { - is_async: false, - sequence: SimpleCommand { - env_vars: vec![], - args: vec![Word::new_word("test")], - } - .into(), - }], - })]), - } - ); - - assert_eq!( - parse_and_create("Name=$(OTHER=5)").unwrap(), - EnvVar { - name: "Name".to_string(), - value: Word(vec![WordPart::Command(SequentialList { - items: vec![SequentialListItem { - is_async: false, - sequence: Sequence::ShellVar(EnvVar { - name: "OTHER".to_string(), - value: Word::new_word("5"), - }), - }], - })]), - } - ); - } - #[cfg(feature = "serialization")] #[test] fn serializes_command_to_json() { diff --git a/crates/deno_task_shell/src/shell/test.rs b/crates/deno_task_shell/src/shell/test.rs index bdb9f73..43293d4 100644 --- a/crates/deno_task_shell/src/shell/test.rs +++ b/crates/deno_task_shell/src/shell/test.rs @@ -7,119 +7,6 @@ use super::types::ExecuteResult; const FOLDER_SEPARATOR: char = if cfg!(windows) { '\\' } else { '/' }; -#[tokio::test] -async fn commands() { - TestBuilder::new() - .command("echo 1") - .assert_stdout("1\n") - .run() - .await; - - TestBuilder::new() - .command("echo 1 2 3") - .assert_stdout("1 2 3\n") - .run() - .await; - - TestBuilder::new() - .command(r#"echo "1 2 3""#) - .assert_stdout("1 2 3\n") - .run() - .await; - - TestBuilder::new() - .command(r"echo 1 2\ \ \ 3") - .assert_stdout("1 2 3\n") - .run() - .await; - - TestBuilder::new() - .command(r#"echo "1 2\ \ \ 3""#) - .assert_stdout("1 2\\ \\ \\ 3\n") - .run() - .await; - - TestBuilder::new() - .command(r#"echo test$(echo "1 2")"#) - .assert_stdout("test1 2\n") - .run() - .await; - - TestBuilder::new() - .command(r#"TEST="1 2" ; echo $TEST"#) - .assert_stdout("1 2\n") - .run() - .await; - - TestBuilder::new() - .command( - r#"VAR=1 deno eval 'console.log(Deno.env.get("VAR"))' && echo $VAR"#, - ) - .assert_stdout("1\n\n") - .run() - .await; - - TestBuilder::new() - .command(r#"VAR=1 VAR2=2 deno eval 'console.log(Deno.env.get("VAR") + Deno.env.get("VAR2"))'"#) - .assert_stdout("12\n") - .run() - .await; - - TestBuilder::new() - .command( - r#"EMPTY= deno eval 'console.log(`EMPTY: ${Deno.env.get("EMPTY")}`)'"#, - ) - .assert_stdout("EMPTY: \n") - .run() - .await; - - TestBuilder::new() - .command(r#""echo" "1""#) - .assert_stdout("1\n") - .run() - .await; - - TestBuilder::new() - .command(r#""echo" "*""#) - .assert_stdout("*\n") - .run() - .await; - - TestBuilder::new() - .command("echo test-dashes") - .assert_stdout("test-dashes\n") - .run() - .await; - - TestBuilder::new() - .command("echo 'a/b'/c") - .assert_stdout("a/b/c\n") - .run() - .await; - - TestBuilder::new() - .command("echo 'a/b'ctest\"te st\"'asdf'") - .assert_stdout("a/bctestte stasdf\n") - .run() - .await; - - TestBuilder::new() - .command("echo --test=\"2\" --test='2' test\"TEST\" TEST'test'TEST 'test''test' test'test'\"test\" \"test\"\"test\"'test'") - .assert_stdout("--test=2 --test=2 testTEST TESTtestTEST testtest testtesttest testtesttest\n") - .run() - .await; - - TestBuilder::new() - .command("deno eval 'console.log(1)'") - .env_var("PATH", "") - .assert_stderr("deno: command not found\n") - .assert_exit_code(127) - .run() - .await; - - TestBuilder::new().command("unset").run().await; -} - #[tokio::test] async fn boolean_logic() { TestBuilder::new() @@ -200,79 +87,6 @@ async fn exit() { .await; } -#[tokio::test] -async fn async_commands() { - TestBuilder::new() - .command("sleep 0.1 && echo 2 & echo 1") - .assert_stdout("1\n2\n") - .run() - .await; - - TestBuilder::new() - .command("(sleep 0.1 && echo 2 &) ; echo 1") - .assert_stdout("1\n2\n") - .run() - .await; - - TestBuilder::new() - .command("(sleep 0.1 && echo 2) & echo 1") - .assert_stdout("1\n2\n") - .run() - .await; - - TestBuilder::new() - .command( - "$(sleep 0.1 && echo 1 & $(sleep 0.2 && echo 2 & echo echo) & echo echo)", - ) - .assert_stdout("1 2\n") - .run() - .await; - - TestBuilder::new() - .command("exit 1 & exit 0") - .assert_exit_code(1) - .run() - .await; - - // should not output because the `exit 1` will cancel the sleep - TestBuilder::new() - .command("sleep 5 && echo 1 & exit 1") - .assert_exit_code(1) - .run() - .await; - - // should fail when async command exits - TestBuilder::new() - .command("exit 1 & exit 0") - .assert_exit_code(1) - .run() - .await; - - // should fail when async command fails and cancel any running command - TestBuilder::new() - .command("deno eval 'Deno.exit(1)' & sleep 5 && echo 2 & echo 1") - .assert_stdout("1\n") - .assert_exit_code(1) - .run() - .await; - - // should cancel running command - TestBuilder::new() - .command("sleep 10 & sleep 0.5 && deno eval 'Deno.exit(2)' & deno eval 'console.log(1); setTimeout(() => { console.log(3) }, 10_000);'") - .assert_stdout("1\n") - .assert_exit_code(2) - .run() - .await; - - // should be able to opt out by doing an `|| exit 0` - TestBuilder::new() - .command("deno eval 'Deno.exit(1)' || exit 0 & echo 1") - .assert_stdout("1\n") - .assert_exit_code(0) - .run() - .await; -} - #[tokio::test] async fn command_substitution() { TestBuilder::new() @@ -301,67 +115,6 @@ async fn command_substitution() { .await; } -#[tokio::test] -async fn shell_variables() { - TestBuilder::new() - .command(r#"echo $VAR && VAR=1 && echo $VAR && deno eval 'console.log(Deno.env.get("VAR"))'"#) - .assert_stdout("\n1\nundefined\n") - .run() - .await; - - TestBuilder::new() - .command(r#"VAR=1 && echo $VAR$VAR"#) - .assert_stdout("11\n") - .run() - .await; - - TestBuilder::new() - .command(r#"VAR=1 && echo Test$VAR && echo $(echo "Test: $VAR") ; echo CommandSub$($VAR); echo $ ; echo \$VAR"#) - .assert_stdout("Test1\nTest: 1\nCommandSub\n$\n$VAR\n") - .assert_stderr("1: command not found\n") - .run() - .await; -} - -#[tokio::test] -async fn env_variables() { - TestBuilder::new() - .command(r#"echo $VAR && export VAR=1 && echo $VAR && deno eval 'console.log(Deno.env.get("VAR"))'"#) - .assert_stdout("\n1\n1\n") - .run() - .await; - - TestBuilder::new() - .command(r#"export VAR=1 VAR2=testing VAR3="test this out" && echo $VAR $VAR2 $VAR3"#) - .assert_stdout("1 testing test this out\n") - .run() - .await; -} - -#[tokio::test] -async fn exit_code_var() { - TestBuilder::new() - .command(r#"echo $? ; echo $? ; false ; echo $?"#) - .assert_stdout("\n0\n1\n") - .run() - .await; - TestBuilder::new() - .command(r#"(false || echo $?) && echo $?"#) - .assert_stdout("1\n0\n") - .run() - .await; - TestBuilder::new() - .command(r#"! false && echo $?"#) - .assert_stdout("0\n") - .run() - .await; - TestBuilder::new() - .command(r#"(deno eval 'Deno.exit(25)') || echo $?"#) - .assert_stdout("25\n") - .run() - .await; -} - #[tokio::test] async fn sequential_lists() { TestBuilder::new() @@ -371,260 +124,6 @@ async fn sequential_lists() { .await; } -#[tokio::test] -async fn pipeline() { - TestBuilder::new() - .command(r#"echo 1 | deno eval 'await Deno.stdin.readable.pipeTo(Deno.stdout.writable)'"#) - .assert_stdout("1\n") - .run() - .await; - - TestBuilder::new() - .command(r#"echo 1 | echo 2 && echo 3"#) - .assert_stdout("2\n3\n") - .run() - .await; - - TestBuilder::new() - .command(r#"echo $(sleep 0.1 && echo 2 & echo 1) | deno eval 'await Deno.stdin.readable.pipeTo(Deno.stdout.writable)'"#) - .assert_stdout("1 2\n") - .run() - .await; - - TestBuilder::new() - .command(r#"echo 2 | echo 1 | deno eval 'await Deno.stdin.readable.pipeTo(Deno.stdout.writable)'"#) - .assert_stdout("1\n") - .run() - .await; - - TestBuilder::new() - .command(r#"deno eval 'console.log(1); console.error(2);' | deno eval 'await Deno.stdin.readable.pipeTo(Deno.stdout.writable)'"#) - .assert_stdout("1\n") - .assert_stderr("2\n") - .run() - .await; - - // stdout and stderr pipeline - - TestBuilder::new() - .command(r#"deno eval 'console.log(1); console.error(2);' |& deno eval 'await Deno.stdin.readable.pipeTo(Deno.stdout.writable)'"#) - .assert_stdout("1\n2\n") - .run() - .await; - - TestBuilder::new() - // add bit of a delay while outputting stdout so that it doesn't race with stderr - .command(r#"deno eval 'console.log(1); console.error(2);' | deno eval 'setTimeout(async () => { await Deno.stdin.readable.pipeTo(Deno.stderr.writable) }, 10)' |& deno eval 'await Deno.stdin.readable.pipeTo(Deno.stderr.writable)'"#) - // still outputs 2 because the first command didn't pipe stderr - .assert_stderr("2\n1\n") - .run() - .await; - - // |& pipeline should still pipe stdout - TestBuilder::new() - .command(r#"echo 1 |& deno eval 'await Deno.stdin.readable.pipeTo(Deno.stdout.writable)'"#) - .assert_stdout("1\n") - .run() - .await; - - // pipeline with redirect - TestBuilder::new() - .command(r#"echo 1 | deno eval 'await Deno.stdin.readable.pipeTo(Deno.stdout.writable)' > output.txt"#) - .assert_file_equals("output.txt", "1\n") - .run() - .await; - - // pipeline with stderr redirect - TestBuilder::new() - .command(r#"echo 1 | deno eval 'await Deno.stdin.readable.pipeTo(Deno.stderr.writable)' 2> output.txt"#) - .assert_file_equals("output.txt", "1\n") - .run() - .await; -} - -#[tokio::test] -async fn negated() { - TestBuilder::new() - .command(r#"! echo 1 && echo 2"#) - .assert_stdout("1\n") - .assert_exit_code(1) - .run() - .await; - TestBuilder::new() - .command(r#"! echo 1 || echo 2"#) - .assert_stdout("1\n2\n") - .run() - .await; - TestBuilder::new() - .command(r#"! (echo 1 | echo 2 && echo 3) || echo 4"#) - .assert_stdout("2\n3\n4\n") - .run() - .await; - TestBuilder::new() - .command(r#"! echo 1 | echo 2 && echo 3"#) - .assert_stdout("2\n") - .assert_exit_code(1) - .run() - .await; - TestBuilder::new() - .command(r#"! (exit 5) && echo 1"#) - .assert_stdout("1\n") - .run() - .await; - TestBuilder::new() - .command(r#"! exit 5 && echo 1"#) - .assert_exit_code(5) - .run() - .await; - TestBuilder::new() - .command(r#"! echo 1 && echo 2 &"#) - .assert_stdout("1\n") - // differing behaviour to shells, where this async command will actually fail - .assert_exit_code(1) - .run() - .await; - - // test no spaces - TestBuilder::new() - .command(r#"!echo 1 && echo 2"#) - .assert_stderr("History expansion is not supported:\n !echo\n ~\n\nPerhaps you meant to add a space after the exclamation point to negate the command?\n ! echo\n") - .assert_exit_code(1) - .run() - .await; -} - -#[tokio::test] -async fn redirects_output() { - TestBuilder::new() - .command(r#"echo 5 6 7 > test.txt"#) - .assert_file_equals("test.txt", "5 6 7\n") - .run() - .await; - - TestBuilder::new() - .command(r#"echo 1 2 3 && echo 1 > test.txt"#) - .assert_stdout("1 2 3\n") - .assert_file_equals("test.txt", "1\n") - .run() - .await; - - // subdir - TestBuilder::new() - .command(r#"mkdir subdir && cd subdir && echo 1 2 3 > test.txt"#) - .assert_file_equals("subdir/test.txt", "1 2 3\n") - .run() - .await; - - // absolute path - TestBuilder::new() - .command(r#"echo 1 2 3 > "$PWD/test.txt""#) - .assert_file_equals("test.txt", "1 2 3\n") - .run() - .await; - - // stdout - TestBuilder::new() - .command(r#"deno eval 'console.log(1); console.error(5)' 1> test.txt"#) - .assert_stderr("5\n") - .assert_file_equals("test.txt", "1\n") - .run() - .await; - - // stderr - TestBuilder::new() - .command(r#"deno eval 'console.log(1); console.error(5)' 2> test.txt"#) - .assert_stdout("1\n") - .assert_file_equals("test.txt", "5\n") - .run() - .await; - - // invalid fd - TestBuilder::new() - .command(r#"echo 2 3> test.txt"#) - .ensure_temp_dir() - .assert_stderr( - "only redirecting to stdout (1) and stderr (2) is supported\n", - ) - .assert_exit_code(1) - .run() - .await; - - // /dev/null - TestBuilder::new() - .command(r#"deno eval 'console.log(1); console.error(5)' 2> /dev/null"#) - .assert_stdout("1\n") - .run() - .await; - - // appending - TestBuilder::new() - .command(r#"echo 1 > test.txt && echo 2 >> test.txt"#) - .assert_file_equals("test.txt", "1\n2\n") - .run() - .await; - - // &> and &>> redirect - TestBuilder::new() - .command( - concat!( - "deno eval 'console.log(1); setTimeout(() => console.error(23), 10)' &> file.txt &&", - "deno eval 'console.log(456); setTimeout(() => console.error(789), 10)' &>> file.txt" - ) - ) - .assert_file_equals("file.txt", "1\n23\n456\n789\n") - .run() - .await; - - // multiple arguments after re-direct - TestBuilder::new() - .command(r"export TwoArgs=testing\ this && echo 1 > $TwoArgs") - .assert_stderr(concat!( - "redirect path must be 1 argument, but found 2 ", - "(testing this). Did you mean to quote it (ex. \"testing this\")?\n" - )) - .assert_exit_code(1) - .run() - .await; - - // zero arguments after re-direct - TestBuilder::new() - .command(r#"echo 1 > $EMPTY"#) - .assert_stderr("redirect path must be 1 argument, but found 0\n") - .assert_exit_code(1) - .run() - .await; - - TestBuilder::new() - .command(r#"echo 1 >&3"#) - .assert_stderr( - "deno_task_shell: output redirecting file descriptors beyond stdout and stderr is not implemented\n", - ) - .assert_exit_code(1) - .run() - .await; - - TestBuilder::new() - .command(r#"echo 1 >&1"#) - .assert_stdout("1\n") - .assert_exit_code(0) - .run() - .await; - - TestBuilder::new() - .command(r#"echo 1 >&2"#) - .assert_stderr("1\n") - .assert_exit_code(0) - .run() - .await; - - TestBuilder::new() - .command(r#"deno eval 'console.error(2)' 2>&1"#) - .assert_stdout("2\n") - .assert_exit_code(0) - .run() - .await; -} - #[tokio::test] async fn redirects_input() { TestBuilder::new() @@ -992,165 +491,6 @@ async fn rm() { .await; } -// Basic integration tests as there are unit tests in the commands -#[tokio::test] -async fn unset() { - // Unset 1 shell variable - TestBuilder::new() - .command( - r#"VAR1=1 && VAR2=2 && VAR3=3 && unset VAR1 && echo $VAR1 $VAR2 $VAR3"#, - ) - .assert_stdout("2 3\n") - .run() - .await; - - // Unset 1 env variable - TestBuilder::new() - .command(r#"export VAR1=1 VAR2=2 VAR3=3 && unset VAR1 && deno eval 'for (let i = 1; i <= 3; i++) { const val = Deno.env.get(`VAR${i}`); console.log(val); }'"#) - .assert_stdout("undefined\n2\n3\n") - .run() - .await; - - // Unset 2 shell variables - TestBuilder::new() - .command( - r#"VAR1=1 && VAR2=2 && VAR3=3 && unset VAR1 VAR2 && echo $VAR1 $VAR2 $VAR3"#, - ) - .assert_stdout("3\n") - .run() - .await; - - // Unset 2 env variables - TestBuilder::new() - .command(r#"export VAR1=1 VAR2=2 VAR3=3 && unset VAR1 VAR2 && deno eval 'for (let i = 1; i <= 3; i++) { const val = Deno.env.get(`VAR${i}`); console.log(val); }'"#) - .assert_stdout("undefined\nundefined\n3\n") - .run() - .await; - - // Unset 2 shell variables with -v enabled - TestBuilder::new() - .command( - r#"VAR1=1 && VAR2=2 && VAR3=3 && unset -v VAR1 VAR2 && echo $VAR1 $VAR2 $VAR3"#, - ) - .assert_stdout("3\n") - .run() - .await; - - // Unset 1 env variable with -v enabled - TestBuilder::new() - .command(r#"export VAR1=1 VAR2=2 VAR3=3 && unset -v VAR1 && deno eval 'for (let i = 1; i <= 3; i++) { const val = Deno.env.get(`VAR${i}`); console.log(val); }'"#) - .assert_stdout("undefined\n2\n3\n") - .run() - .await; - - // Unset 2 env variables with -v enabled - TestBuilder::new() - .command(r#"export VAR1=1 VAR2=2 VAR3=3 && unset -v VAR1 VAR2 && deno eval 'for (let i = 1; i <= 3; i++) { const val = Deno.env.get(`VAR${i}`); console.log(val); }'"#) - .assert_stdout("undefined\nundefined\n3\n") - .run() - .await; - - // Unset 1 shell variable and 1 env variable at the same time - TestBuilder::new() - .command( - r#"VAR=1 && export ENV_VAR=2 && unset VAR ENV_VAR && echo $VAR $ENV_VAR"#, - ) - .assert_stdout("\n") - .run() - .await; - - // -f is not supported - TestBuilder::new() - .command(r#"export VAR=42 && unset -f VAR"#) - .assert_stderr("unset: unsupported flag: -f\n") - .assert_exit_code(1) - .run() - .await; -} - -#[tokio::test] -async fn xargs() { - TestBuilder::new() - .command("echo '1 2 3 ' | xargs") - .assert_stdout("1 2 3\n") - .run() - .await; - - TestBuilder::new() - .command("echo '1 2 \t\t\t3 ' | xargs echo test") - .assert_stdout("test 1 2 3\n") - .run() - .await; - - TestBuilder::new() - .command(r#"deno eval "console.log('testing\nthis')" | xargs"#) - .assert_stdout("testing this\n") - .run() - .await; - - // \n delimiter - TestBuilder::new() - .command(r#"deno eval "console.log('testing this out\n\ntest\n')" | xargs -d \n deno eval "console.log(Deno.args)""#) - .assert_stdout("[ \"testing this out\", \"\", \"test\", \"\" ]\n") - .run() - .await; - - // \0 delimiter - TestBuilder::new() - .command(r#"deno eval "console.log('testing this out\ntest\0other')" | xargs -0 deno eval "console.log(Deno.args)""#) - .assert_stdout("[ \"testing this out\\ntest\", \"other\\n\" ]\n") - .run() - .await; - - // unmatched single quote - TestBuilder::new() - .command(r#"deno eval "console.log(\"'test\")" | xargs"#) - .assert_stderr("xargs: unmatched quote; by default quotes are special to xargs unless you use the -0 option\n") - .assert_exit_code(1) - .run() - .await; - - // unmatched double quote - TestBuilder::new() - .command(r#"deno eval "console.log('\"test')" | xargs"#) - .assert_stderr("xargs: unmatched quote; by default quotes are special to xargs unless you use the -0 option\n") - .assert_exit_code(1) - .run() - .await; - - // test reading env file - TestBuilder::new() - .file( - ".env", - r#"VAR1="testing" -VAR2="other" -"#, - ) - // most likely people would want to do `export $(grep -v '^#' .env | xargs)` though - // in order to remove comments... - .command("export $(cat .env | xargs) && echo $VAR1 $VAR2") - .assert_stdout("testing other\n") - .run() - .await; -} - -#[tokio::test] -async fn stdin() { - TestBuilder::new() - .command(r#"deno eval "const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)" && deno eval "const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)""#) - .stdin("12345") - .assert_stdout("Uint8Array(1) [ 49 ]\nUint8Array(1) [ 50 ]\n") - .run() - .await; - - TestBuilder::new() - .command(r#"echo "12345" | (deno eval "const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)" && deno eval "const b = new Uint8Array(1);Deno.stdin.readSync(b);console.log(b)")"#) - .stdin("55555") // should not use this because stdin is piped from the echo - .assert_stdout("Uint8Array(1) [ 49 ]\nUint8Array(1) [ 50 ]\n") - .run() - .await; -} - #[cfg(windows)] #[tokio::test] async fn windows_resolve_command() { @@ -1194,29 +534,6 @@ async fn custom_command() { .await; } -#[tokio::test] -async fn custom_command_resolve_command_path() { - TestBuilder::new() - .command("$(custom_which deno) eval 'console.log(1)'") - .custom_command( - "custom_which", - Box::new(|mut context| { - async move { - let path = context - .state - .resolve_command_path(&context.args[0]) - .unwrap(); - let _ = context.stdout.write_line(&path.to_string_lossy()); - ExecuteResult::from_exit_code(0) - } - .boxed_local() - }), - ) - .assert_stdout("1\n") - .run() - .await; -} - #[tokio::test] async fn glob_basic() { TestBuilder::new() @@ -1365,54 +682,6 @@ async fn glob_case_insensitive() { .await; } -#[tokio::test] -async fn glob_escapes() { - // no escape - TestBuilder::new() - .file("[test].txt", "test\n") - .file("t.txt", "t\n") - .command("cat [test].txt") - .assert_stdout("t\n") - .run() - .await; - - // escape - TestBuilder::new() - .file("[test].txt", "test\n") - .file("t.txt", "t\n") - .command("cat [[]test[]].txt") - .assert_stdout("test\n") - .run() - .await; - - // single quotes - TestBuilder::new() - .file("[test].txt", "test\n") - .file("t.txt", "t\n") - .command("cat '[test].txt'") - .assert_stdout("test\n") - .run() - .await; - - // double quotes - TestBuilder::new() - .file("[test].txt", "test\n") - .file("t.txt", "t\n") - .command("cat \"[test].txt\"") - .assert_stdout("test\n") - .run() - .await; - - // mix - TestBuilder::new() - .file("[test].txt", "test\n") - .file("t.txt", "t\n") - .command("cat \"[\"test\"]\".txt") - .assert_stdout("test\n") - .run() - .await; -} - #[tokio::test] async fn paren_escapes() { TestBuilder::new() @@ -1422,58 +691,6 @@ async fn paren_escapes() { .await; } -#[tokio::test] -async fn cross_platform_shebang() { - // with -S - TestBuilder::new() - .file("file.ts", "#!/usr/bin/env -S deno run\nconsole.log(5)") - .command("./file.ts") - .assert_stdout("5\n") - .run() - .await; - - // without -S and invalid - TestBuilder::new() - .file("file.ts", "#!/usr/bin/env deno run\nconsole.log(5)") - .command("./file.ts") - .assert_stderr("deno run: command not found\n") - .assert_exit_code(127) - .run() - .await; - - // without -S, but valid - TestBuilder::new() - .file("file.ts", "#!/usr/bin/env ./echo_stdin.ts\nconsole.log('Hello')") - .file("echo_stdin.ts", "#!/usr/bin/env -S deno run --allow-run\nawait new Deno.Command('deno', { args: ['run', ...Deno.args] }).spawn();") - .command("./file.ts") - .assert_stdout("Hello\n") - .run() - .await; - - // sub dir - TestBuilder::new() - .directory("sub") - .file("sub/file.ts", "#!/usr/bin/env ../echo_stdin.ts\nconsole.log('Hello')") - .file("echo_stdin.ts", "#!/usr/bin/env -S deno run --allow-run\nawait new Deno.Command('deno', { args: ['run', ...Deno.args] }).spawn();") - .command("./sub/file.ts") - .assert_stdout("Hello\n") - .run() - .await; - - // arguments - TestBuilder::new() - .file( - "file.ts", - "#!/usr/bin/env -S deno run --allow-read\nconsole.log(Deno.args)\nconst text = Deno.readTextFileSync(import.meta.filename);\nconsole.log(text.length)\n", - ) - .command("./file.ts 1 2 3") - .assert_stdout(r#"[ "1", "2", "3" ] -146 -"#) - .run() - .await; -} - fn no_such_file_error_text() -> &'static str { if cfg!(windows) { "The system cannot find the file specified. (os error 2)" diff --git a/crates/deno_task_shell/src/shell/test_builder.rs b/crates/deno_task_shell/src/shell/test_builder.rs index 0aacc65..e3acb33 100644 --- a/crates/deno_task_shell/src/shell/test_builder.rs +++ b/crates/deno_task_shell/src/shell/test_builder.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. MIT license. -use anyhow::Context; use futures::future::LocalBoxFuture; use pretty_assertions::assert_eq; use std::collections::HashMap; @@ -40,7 +39,6 @@ impl ShellCommand for FnShellCommand { enum TestAssertion { FileExists(String), FileNotExists(String), - FileTextEquals(String, String), } struct TempDir { @@ -134,11 +132,6 @@ impl TestBuilder { self } - pub fn env_var(&mut self, name: &str, value: &str) -> &mut Self { - self.env_vars.insert(name.to_string(), value.to_string()); - self - } - pub fn custom_command( &mut self, name: &str, @@ -187,19 +180,6 @@ impl TestBuilder { self } - pub fn assert_file_equals( - &mut self, - path: &str, - file_text: &str, - ) -> &mut Self { - self.ensure_temp_dir(); - self.assertions.push(TestAssertion::FileTextEquals( - path.to_string(), - file_text.to_string(), - )); - self - } - pub async fn run(&mut self) { let list = parse(&self.command).unwrap(); let cwd = if let Some(temp_dir) = &self.temp_dir { @@ -263,16 +243,6 @@ impl TestBuilder { path, ) } - TestAssertion::FileTextEquals(path, text) => { - let actual_text = std::fs::read_to_string(cwd.join(path)) - .with_context(|| format!("Error reading {path}")) - .unwrap(); - assert_eq!( - &actual_text, text, - "\n\nFailed for: {}\nPath: {}", - self.command, path, - ) - } } } }