From b0c5fa23ee723a0516d983fc3f2af369fd44b3ec Mon Sep 17 00:00:00 2001 From: Casey Rodarmor Date: Sat, 22 Apr 2017 16:15:15 -0700 Subject: [PATCH] Multi shell integration tests (#181) * Run integration tests on multiple shells To make sure that I don't break tests for shells other than my dev-box's `sh` and the `sh` on travis, each integration test case now runs using `sh`, `dash`, and `bash. --- src/integration.rs | 1849 ++++++++++++++++++-------------------------- src/lib.rs | 3 + src/search.rs | 142 ++++ 3 files changed, 908 insertions(+), 1086 deletions(-) create mode 100644 src/search.rs diff --git a/src/integration.rs b/src/integration.rs index c1502ab..20901ce 100644 --- a/src/integration.rs +++ b/src/integration.rs @@ -3,14 +3,45 @@ extern crate brev; use ::prelude::*; use tempdir::TempDir; -use std::{path, str}; +use std::str; + +/// Instantiate integration tests for a given test case using +/// sh, dash, and bash. +/// +/// Although `sh` is likely to be dash or bash, we include it +/// in case it's a different version or a different shell entirely. +/// +/// For example, on FreeBSD, `sh` is ash. +macro_rules! integration_test { + ( + name: $name:ident, + justfile: $text:tt, + args: ($($arg:tt)*), + stdout: $stdout:tt, + stderr: $stderr:tt, + status: $status:tt, + ) => { + mod $name { + use ::prelude::*; + use super::integration_test; + + // silence unused import warnings + const __: i32 = EXIT_SUCCESS; + + #[test] fn sh() { integration_test("sh", $text, &[$($arg)*], $stdout, $stderr, $status); } + #[test] fn dash() { integration_test("dash", $text, &[$($arg)*], $stdout, $stderr, $status); } + #[test] fn bash() { integration_test("bash", $text, &[$($arg)*], $stdout, $stderr, $status); } + } + } +} fn integration_test( - args: &[&str], + shell: &str, justfile: &str, - expected_status: i32, + args: &[&str], expected_stdout: &str, expected_stderr: &str, + expected_status: i32, ) { let tmp = TempDir::new("just-integration") .unwrap_or_else( @@ -20,9 +51,11 @@ fn integration_test( brev::dump(path, justfile); let mut binary = env::current_dir().unwrap(); binary.push("./target/debug/just"); - let output = process::Command::new(binary) + + let output = process::Command::new(&binary) .current_dir(tmp.path()) .args(args) + .args(&["--shell", shell]) .output() .expect("just invocation failed"); @@ -51,180 +84,36 @@ fn integration_test( } } -fn search_test>(path: P, args: &[&str]) { - let mut binary = env::current_dir().unwrap(); - binary.push("./target/debug/just"); - let output = process::Command::new(binary) - .current_dir(path) - .args(args) - .output() - .expect("just invocation failed"); - - assert_eq!(output.status.code().unwrap(), 0); - - let stdout = str::from_utf8(&output.stdout).unwrap(); - assert_eq!(stdout, "ok\n"); - - let stderr = str::from_utf8(&output.stderr).unwrap(); - assert_eq!(stderr, "echo ok\n"); +integration_test! { + name: default, + justfile: "default:\n echo hello\nother: \n echo bar", + args: (), + stdout: "hello\n", + stderr: "echo hello\n", + status: EXIT_SUCCESS, } -#[test] -fn test_justfile_search() { - let tmp = TempDir::new("just-test-justfile-search") - .expect("test justfile search: failed to create temporary directory"); - let mut path = tmp.path().to_path_buf(); - path.push("justfile"); - brev::dump(&path, "default:\n\techo ok"); - path.pop(); - - path.push("a"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - path.push("b"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - path.push("c"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - path.push("d"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - - search_test(path, &[]); +integration_test! { + name: quiet, + justfile: "default:\n @echo hello", + args: (), + stdout: "hello\n", + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn test_capitalized_justfile_search() { - let tmp = TempDir::new("just-test-justfile-search") - .expect("test justfile search: failed to create temporary directory"); - let mut path = tmp.path().to_path_buf(); - path.push("Justfile"); - brev::dump(&path, "default:\n\techo ok"); - path.pop(); - - path.push("a"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - path.push("b"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - path.push("c"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - path.push("d"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - - search_test(path, &[]); +integration_test! { + name: verbose, + justfile: "default:\n @echo hello", + args: ("--verbose"), + stdout: "hello\n", + stderr: "===> Running recipe `default`...\necho hello\n", + status: EXIT_SUCCESS, } -#[test] -fn test_capitalization_priority() { - let tmp = TempDir::new("just-test-justfile-search") - .expect("test justfile search: failed to create temporary directory"); - let mut path = tmp.path().to_path_buf(); - path.push("justfile"); - brev::dump(&path, "default:\n\techo ok"); - path.pop(); - path.push("Justfile"); - brev::dump(&path, "default:\n\techo fail"); - path.pop(); - - // if we see "default\n\techo fail" in `justfile` then we're running - // in a case insensitive filesystem, so just bail - path.push("justfile"); - if brev::slurp(&path) == "default:\n\techo fail" { - return; - } - path.pop(); - - path.push("a"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - path.push("b"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - path.push("c"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - path.push("d"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - - search_test(path, &[]); -} - -#[test] -fn test_upwards_path_argument() { - let tmp = TempDir::new("just-test-justfile-search") - .expect("test justfile search: failed to create temporary directory"); - let mut path = tmp.path().to_path_buf(); - path.push("justfile"); - brev::dump(&path, "default:\n\techo ok"); - path.pop(); - - path.push("a"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - - path.push("justfile"); - brev::dump(&path, "default:\n\techo bad"); - path.pop(); - - search_test(&path, &["../"]); - search_test(&path, &["../default"]); -} - -#[test] -fn test_downwards_path_argument() { - let tmp = TempDir::new("just-test-justfile-search") - .expect("test justfile search: failed to create temporary directory"); - let mut path = tmp.path().to_path_buf(); - path.push("justfile"); - brev::dump(&path, "default:\n\techo bad"); - path.pop(); - - path.push("a"); - fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); - - path.push("justfile"); - brev::dump(&path, "default:\n\techo ok"); - path.pop(); - path.pop(); - - search_test(&path, &["a/"]); - search_test(&path, &["a/default"]); - search_test(&path, &["./a/"]); - search_test(&path, &["./a/default"]); - search_test(&path, &["./a/"]); - search_test(&path, &["./a/default"]); -} - -#[test] -fn default() { - integration_test( - &[], - "default:\n echo hello\nother: \n echo bar", - EXIT_SUCCESS, - "hello\n", - "echo hello\n", - ) -} - -#[test] -fn quiet() { - integration_test( - &[], - "default:\n @echo hello", - EXIT_SUCCESS, - "hello\n", - "", - ) -} - -#[test] -fn verbose() { - integration_test( - &["--verbose"], - "default:\n @echo hello", - EXIT_SUCCESS, - "hello\n", - "===> Running recipe `default`...\necho hello\n", - ) -} - - -#[test] -fn order() { - let text = " +integration_test! { + name: order, + justfile: " b: a echo b @mv a b @@ -240,383 +129,323 @@ d: c c: b echo c - @mv b c"; - integration_test( - &["a", "d"], - text, - EXIT_SUCCESS, - "a\nb\nc\nd\n", - "echo a\necho b\necho c\necho d\n", - ); + @mv b c", + args: ("a", "d"), + stdout: "a\nb\nc\nd\n", + stderr: "echo a\necho b\necho c\necho d\n", + status: EXIT_SUCCESS, } -#[test] -fn summary() { - let text = -"b: a +integration_test! { + name: summary, + justfile: "b: a a: d: c -c: b"; - integration_test( - &["--summary"], - text, - EXIT_SUCCESS, - "a b c d\n", - "", - ); +c: b", + args: ("--summary"), + stdout: "a b c d\n", + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn select() { - let text = -"b: +integration_test! { + name: select, + justfile: "b: @echo b a: @echo a d: @echo d c: - @echo c"; - integration_test( - &["d", "c"], - text, - EXIT_SUCCESS, - "d\nc\n", - "", - ); + @echo c", + args: ("d", "c"), + stdout: "d\nc\n", + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn print() { - let text = -"b: +integration_test! { + name: print, + justfile: "b: echo b a: echo a d: echo d c: - echo c"; - integration_test( - &["d", "c"], - text, - EXIT_SUCCESS, - "d\nc\n", - "echo d\necho c\n", - ); + echo c", + args: ("d", "c"), + stdout: "d\nc\n", + stderr: "echo d\necho c\n", + status: EXIT_SUCCESS, } - -#[test] -fn show() { - let text = -r#"hello = "foo" +integration_test! { + name: show, + justfile: r#"hello = "foo" bar = hello + hello recipe: - echo {{hello + "bar" + bar}}"#; - integration_test( - &["--show", "recipe"], - text, - EXIT_SUCCESS, - r#"recipe: + echo {{hello + "bar" + bar}}"#, + args: ("--show", "recipe"), + stdout: r#"recipe: echo {{hello + "bar" + bar}} "#, - "", - ); + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn status_passthrough() { - let text = -" +integration_test! { + name: status_passthrough, + justfile: " hello: recipe: - @exit 100"; - integration_test( - &["recipe"], - text, - 100, - "", - "error: Recipe `recipe` failed on line 6 with exit code 100\n", - ); + @exit 100", + args: ("recipe"), + stdout: "", + stderr: "error: Recipe `recipe` failed on line 6 with exit code 100\n", + status: 100, } -#[test] -fn unknown_dependency() { - integration_test( - &[], - "bar:\nhello:\nfoo: bar baaaaaaaz hello", - EXIT_FAILURE, - "", - "error: Recipe `foo` has unknown dependency `baaaaaaaz` +integration_test! { + name: unknown_dependency, + justfile: "bar:\nhello:\nfoo: bar baaaaaaaz hello", + args: (), + stdout: "", + stderr: "error: Recipe `foo` has unknown dependency `baaaaaaaz` | 3 | foo: bar baaaaaaaz hello | ^^^^^^^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn backtick_success() { - integration_test( - &[], - "a = `printf Hello,`\nbar:\n printf '{{a + `printf ' world!'`}}'", - EXIT_SUCCESS, - "Hello, world!", - "printf 'Hello, world!'\n", - ); +integration_test! { + name: backtick_success, + justfile: "a = `printf Hello,`\nbar:\n printf '{{a + `printf ' world!'`}}'", + args: (), + stdout: "Hello, world!", + stderr: "printf 'Hello, world!'\n", + status: EXIT_SUCCESS, } -#[test] -fn backtick_trimming() { - integration_test( - &[], - "a = `echo Hello,`\nbar:\n echo '{{a + `echo ' world!'`}}'", - EXIT_SUCCESS, - "Hello, world!\n", - "echo 'Hello, world!'\n", - ); +integration_test! { + name: backtick_trimming, + justfile: "a = `echo Hello,`\nbar:\n echo '{{a + `echo ' world!'`}}'", + args: (), + stdout: "Hello, world!\n", + stderr: "echo 'Hello, world!'\n", + status: EXIT_SUCCESS, } -#[test] -fn backtick_code_assignment() { - integration_test( - &[], - "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", - 100, - "", - "error: Backtick failed with exit code 100 +integration_test! { + name: backtick_code_assignment, + justfile: "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 100 | 2 | a = `exit 100` | ^^^^^^^^^^ ", - ); + status: 100, } -#[test] -fn backtick_code_interpolation() { - integration_test( - &[], - "b = a\na = `echo hello`\nbar:\n echo '{{`exit 200`}}'", - 200, - "", - "error: Backtick failed with exit code 200 +integration_test! { + name: backtick_code_interpolation, + justfile: "b = a\na = `echo hello`\nbar:\n echo '{{`exit 200`}}'", + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 200 | 4 | echo '{{`exit 200`}}' | ^^^^^^^^^^ ", - ); + status: 200, } -#[test] -fn backtick_code_interpolation_tab() { - integration_test( - &[], - " +integration_test! { + name: backtick_code_interpolation_tab, + justfile: " backtick-fail: \techo {{`exit 1`}} ", - 1, - "", - "error: Backtick failed with exit code 1 + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 1 | 3 | echo {{`exit 1`}} | ^^^^^^^^ ", - ); + status: 1, } -#[test] -fn backtick_code_interpolation_tabs() { - integration_test( - &[], - " +integration_test! { + name: backtick_code_interpolation_tabs, + justfile: " backtick-fail: \techo {{\t`exit 1`}} ", - 1, - "", - "error: Backtick failed with exit code 1 + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 1 | 3 | echo {{ `exit 1`}} | ^^^^^^^^ ", - ); + status: 1, } -#[test] -fn backtick_code_interpolation_inner_tab() { - integration_test( - &[], - " +integration_test! { + name: backtick_code_interpolation_inner_tab, + justfile: " backtick-fail: \techo {{\t`exit\t\t1`}} ", - 1, - "", - "error: Backtick failed with exit code 1 + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 1 | 3 | echo {{ `exit 1`}} | ^^^^^^^^^^^^^^^ ", - ); + status: 1, } -#[test] -fn backtick_code_interpolation_leading_emoji() { - integration_test( - &[], - " +integration_test! { + name: backtick_code_interpolation_leading_emoji, + justfile: " backtick-fail: \techo 😬{{`exit 1`}} ", - 1, - "", - "error: Backtick failed with exit code 1 + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 1 | 3 | echo 😬{{`exit 1`}} | ^^^^^^^^ ", - ); + status: 1, } -#[test] -fn backtick_code_interpolation_unicode_hell() { - integration_test( - &[], - " +integration_test! { + name: backtick_code_interpolation_unicode_hell, + justfile: " backtick-fail: \techo \t\t\t😬鎌鼬{{\t\t`exit 1 # \t\t\t😬鎌鼬`}}\t\t\t😬鎌鼬 ", - 1, - "", - "error: Backtick failed with exit code 1 + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 1 | 3 | echo 😬鎌鼬{{ `exit 1 # 😬鎌鼬`}} 😬鎌鼬 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ", - ); + status: 1, } -#[test] -fn backtick_code_long() { - integration_test( - &[], - "\n\n\n\n\n\nb = a\na = `echo hello`\nbar:\n echo '{{`exit 200`}}'", - 200, - "", - "error: Backtick failed with exit code 200 +integration_test! { + name: backtick_code_long, + justfile: "\n\n\n\n\n\nb = a\na = `echo hello`\nbar:\n echo '{{`exit 200`}}'", + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 200 | 10 | echo '{{`exit 200`}}' | ^^^^^^^^^^ ", - ); + status: 200, } -#[test] -fn shebang_backtick_failure() { - integration_test( - &[], - "foo: +integration_test! { + name: shebang_backtick_failure, + justfile: "foo: #!/bin/sh echo hello echo {{`exit 123`}}", - 123, - "", - "error: Backtick failed with exit code 123 + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 123 | 4 | echo {{`exit 123`}} | ^^^^^^^^^^ ", - ); + status: 123, } -#[test] -fn command_backtick_failure() { - integration_test( - &[], - "foo: +integration_test! { + name: command_backtick_failure, + justfile: "foo: echo hello echo {{`exit 123`}}", - 123, - "hello\n", - "echo hello\nerror: Backtick failed with exit code 123 + args: (), + stdout: "hello\n", + stderr: "echo hello\nerror: Backtick failed with exit code 123 | 3 | echo {{`exit 123`}} | ^^^^^^^^^^ ", - ); + status: 123, } -#[test] -fn assignment_backtick_failure() { - integration_test( - &[], - "foo: +integration_test! { + name: assignment_backtick_failure, + justfile: "foo: echo hello echo {{`exit 111`}} a = `exit 222`", - 222, - "", - "error: Backtick failed with exit code 222 + args: (), + stdout: "", + stderr: "error: Backtick failed with exit code 222 | 4 | a = `exit 222` | ^^^^^^^^^^ ", - ); + status: 222, } -#[test] -fn unknown_override_options() { - integration_test( - &["--set", "foo", "bar", "a", "b", "--set", "baz", "bob", "--set", "a", "b"], - "foo: +integration_test! { + name: unknown_override_options, + justfile: "foo: echo hello echo {{`exit 111`}} a = `exit 222`", - EXIT_FAILURE, - "", - "error: Variables `baz` and `foo` overridden on the command line but not present \ + args: ("--set", "foo", "bar", "a", "b", "--set", "baz", "bob", "--set", "a", "b"), + stdout: "", + stderr: "error: Variables `baz` and `foo` overridden on the command line but not present \ in justfile\n", - ); + status: EXIT_FAILURE, } -#[test] -fn unknown_override_args() { - integration_test( - &["foo=bar", "baz=bob", "a=b", "a", "b"], - "foo: +integration_test! { + name: unknown_override_args, + justfile: "foo: echo hello echo {{`exit 111`}} a = `exit 222`", - EXIT_FAILURE, - "", - "error: Variables `baz` and `foo` overridden on the command line but not present \ + args: ("foo=bar", "baz=bob", "a=b", "a", "b"), + stdout: "", + stderr: "error: Variables `baz` and `foo` overridden on the command line but not present \ in justfile\n", - ); + status: EXIT_FAILURE, } -#[test] -fn unknown_override_arg() { - integration_test( - &["foo=bar", "a=b", "a", "b"], - "foo: +integration_test! { + name: unknown_override_arg, + justfile: "foo: echo hello echo {{`exit 111`}} a = `exit 222`", - EXIT_FAILURE, - "", - "error: Variable `foo` overridden on the command line but not present in justfile\n", - ); + args: ("foo=bar", "a=b", "a", "b"), + stdout: "", + stderr: "error: Variable `foo` overridden on the command line but not present in justfile\n", + status: EXIT_FAILURE, } -#[test] -fn overrides_first() { - integration_test( - &["foo=bar", "a=b", "recipe", "baz=bar"], - r#" +integration_test! { + name: overrides_first, + justfile: r#" foo = "foo" a = "a" baz = "baz" @@ -624,17 +453,15 @@ baz = "baz" recipe arg: echo arg={{arg}} echo {{foo + a + baz}}"#, - EXIT_SUCCESS, - "arg=baz=bar\nbarbbaz\n", - "echo arg=baz=bar\necho barbbaz\n", - ); + args: ("foo=bar", "a=b", "recipe", "baz=bar"), + stdout: "arg=baz=bar\nbarbbaz\n", + stderr: "echo arg=baz=bar\necho barbbaz\n", + status: EXIT_SUCCESS, } -#[test] -fn overrides_not_evaluated() { - integration_test( - &["foo=bar", "a=b", "recipe", "baz=bar"], - r#" +integration_test! { + name: overrides_not_evaluated, + justfile: r#" foo = `exit 1` a = "a" baz = "baz" @@ -642,17 +469,15 @@ baz = "baz" recipe arg: echo arg={{arg}} echo {{foo + a + baz}}"#, - EXIT_SUCCESS, - "arg=baz=bar\nbarbbaz\n", - "echo arg=baz=bar\necho barbbaz\n", - ); + args: ("foo=bar", "a=b", "recipe", "baz=bar"), + stdout: "arg=baz=bar\nbarbbaz\n", + stderr: "echo arg=baz=bar\necho barbbaz\n", + status: EXIT_SUCCESS, } -#[test] -fn dry_run() { - integration_test( - &["--dry-run", "shebang", "command"], - r#" +integration_test! { + name: dry_run, + justfile: r#" var = `echo stderr 1>&2; echo backtick` command: @@ -665,9 +490,9 @@ shebang: touch /this/is/not/a/file {{var}} echo {{`echo shebang interpolation`}}"#, - EXIT_SUCCESS, - "", - "stderr + args: ("--dry-run", "shebang", "command"), + stdout: "", + stderr: "stderr #!/bin/sh touch /this/is/not/a/file backtick @@ -676,14 +501,12 @@ touch /this/is/not/a/file backtick echo command interpolation ", - ); + status: EXIT_SUCCESS, } -#[test] -fn evaluate() { - integration_test( - &["--evaluate"], - r#" +integration_test! { + name: evaluate, + justfile: r#" foo = "a\t" hello = "c" bar = "b\t" @@ -692,21 +515,19 @@ ab = foo + bar + hello wut: touch /this/is/not/a/file "#, - EXIT_SUCCESS, - r#"ab = "a b c" + args: ("--evaluate"), + stdout: r#"ab = "a b c" bar = "b " foo = "a " hello = "c" "#, - "", - ); + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn export_success() { - integration_test( - &[], - r#" +integration_test! { + name: export_success, + justfile: r#" export foo = "a" baz = "c" export bar = "b" @@ -715,17 +536,15 @@ export abc = foo + bar + baz wut: echo $foo $bar $abc "#, - EXIT_SUCCESS, - "a b abc\n", - "echo $foo $bar $abc\n", - ); + args: (), + stdout: "a b abc\n", + stderr: "echo $foo $bar $abc\n", + status: EXIT_SUCCESS, } -#[test] -fn export_override() { - integration_test( - &["foo=hello", "--set", "bar", "bye"], - r#" +integration_test! { + name: export_override, + justfile: r#" export foo = "a" baz = "c" export bar = "b" @@ -734,17 +553,15 @@ export abc = foo + "-" + bar + "-" + baz wut: echo $foo $bar $abc "#, - EXIT_SUCCESS, - "hello bye hello-bye-c\n", - "echo $foo $bar $abc\n", - ); + args: ("foo=hello", "--set", "bar", "bye"), + stdout: "hello bye hello-bye-c\n", + stderr: "echo $foo $bar $abc\n", + status: EXIT_SUCCESS, } -#[test] -fn outer_shebang() { - integration_test( - &[], - r#"#!/lol/wut +integration_test! { + name: outer_shebang, + justfile: r#"#!/lol/wut export foo = "a" baz = "c" export bar = "b" @@ -754,22 +571,20 @@ wut: #!/bin/sh echo $foo $bar $abc "#, - EXIT_FAILURE, - "", - "error: `#!` is reserved syntax outside of recipes + args: (), + stdout: "", + stderr: "error: `#!` is reserved syntax outside of recipes | 1 | #!/lol/wut | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn export_shebang() { - integration_test( - &[], - r#" +integration_test! { + name: export_shebang, + justfile: r#" export foo = "a" baz = "c" export bar = "b" @@ -779,49 +594,43 @@ wut: #!/bin/sh echo $foo $bar $abc "#, - EXIT_SUCCESS, - "a b abc\n", - "", - ); + args: (), + stdout: "a b abc\n", + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn export_recipe_backtick() { - integration_test( - &[], - r#" +integration_test! { + name: export_recipe_backtick, + justfile: r#" export exported_variable = "A-IS-A" recipe: echo {{`echo recipe $exported_variable`}} "#, - EXIT_SUCCESS, - "recipe A-IS-A\n", - "echo recipe A-IS-A\n", - ); + args: (), + stdout: "recipe A-IS-A\n", + stderr: "echo recipe A-IS-A\n", + status: EXIT_SUCCESS, } -#[test] -fn raw_string() { - integration_test( - &[], - r#" -export exported_variable = '\\\\\\"' +integration_test! { + name: raw_string, + justfile: r#" +export EXPORTED_VARIABLE = '\z' recipe: - echo {{`echo recipe $exported_variable`}} + printf "$EXPORTED_VARIABLE" "#, - EXIT_SUCCESS, - "recipe \\\"\n", - "echo recipe \\\\\\\"\n", - ); + args: (), + stdout: "\\z", + stderr: "printf \"$EXPORTED_VARIABLE\"\n", + status: EXIT_SUCCESS, } -#[test] -fn line_error_spacing() { - integration_test( - &[], - r#" +integration_test! { + name: line_error_spacing, + justfile: r#" @@ -832,440 +641,378 @@ fn line_error_spacing() { ??? "#, - EXIT_FAILURE, - "", - "error: Unknown start of token: + args: (), + stdout: "", + stderr: "error: Unknown start of token: | 10 | ??? | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn quiet_flag_no_stdout() { - integration_test( - &["--quiet"], - r#" +integration_test! { + name: quiet_flag_no_stdout, + justfile: r#" default: @echo hello "#, - EXIT_SUCCESS, - "", - "", - ); + args: ("--quiet"), + stdout: "", + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn quiet_flag_no_stderr() { - integration_test( - &["--quiet"], - r#" +integration_test! { + name: quiet_flag_no_stderr, + justfile: r#" default: @echo hello 1>&2 "#, - EXIT_SUCCESS, - "", - "", - ); + args: ("--quiet"), + stdout: "", + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn quiet_flag_no_command_echoing() { - integration_test( - &["--quiet"], - r#" +integration_test! { + name: quiet_flag_no_command_echoing, + justfile: r#" default: exit "#, - EXIT_SUCCESS, - "", - "", - ); + args: ("--quiet"), + stdout: "", + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn quiet_flag_no_error_messages() { - integration_test( - &["--quiet"], - r#" +integration_test! { + name: quiet_flag_no_error_messages, + justfile: r#" default: exit 100 "#, - 100, - "", - "", - ); + args: ("--quiet"), + stdout: "", + stderr: "", + status: 100, } -#[test] -fn quiet_flag_no_assignment_backtick_stderr() { - integration_test( - &["--quiet"], - r#" +integration_test! { + name: quiet_flag_no_assignment_backtick_stderr, + justfile: r#" a = `echo hello 1>&2` default: exit 100 "#, - 100, - "", - "", - ); + args: ("--quiet"), + stdout: "", + stderr: "", + status: 100, } -#[test] -fn quiet_flag_no_interpolation_backtick_stderr() { - integration_test( - &["--quiet"], - r#" +integration_test! { + name: quiet_flag_no_interpolation_backtick_stderr, + justfile: r#" default: echo `echo hello 1>&2` exit 100 "#, - 100, - "", - "", - ); + args: ("--quiet"), + stdout: "", + stderr: "", + status: 100, } -#[test] -fn quiet_flag_or_dry_run_flag() { - integration_test( - &["--quiet", "--dry-run"], - "", - 1, - "", - "error: The argument '--dry-run' cannot be used with '--quiet' +integration_test! { + name: quiet_flag_or_dry_run_flag, + justfile: "", + args: ("--quiet", "--dry-run"), + stdout: "", + stderr: "error: The argument '--dry-run' cannot be used with '--quiet' USAGE: just --color --quiet --shell For more information try --help\n", - ); + status: 1, } -#[test] -fn argument_single() { - integration_test( - &["foo", "ARGUMENT"], - " +integration_test! { + name: argument_single, + justfile: " foo A: echo {{A}} ", - EXIT_SUCCESS, - "ARGUMENT\n", - "echo ARGUMENT\n", - ); + args: ("foo", "ARGUMENT"), + stdout: "ARGUMENT\n", + stderr: "echo ARGUMENT\n", + status: EXIT_SUCCESS, } -#[test] -fn argument_multiple() { - integration_test( - &["foo", "ONE", "TWO"], - " +integration_test! { + name: argument_multiple, + justfile: " foo A B: echo A:{{A}} B:{{B}} ", - EXIT_SUCCESS, - "A:ONE B:TWO\n", - "echo A:ONE B:TWO\n", - ); + args: ("foo", "ONE", "TWO"), + stdout: "A:ONE B:TWO\n", + stderr: "echo A:ONE B:TWO\n", + status: EXIT_SUCCESS, } -#[test] -fn argument_mismatch_more() { - integration_test( - &["foo", "ONE", "TWO", "THREE"], - " +integration_test! { + name: argument_mismatch_more, + justfile: " foo A B: echo A:{{A}} B:{{B}} ", - EXIT_FAILURE, - "", - "error: Justfile does not contain recipe `THREE`.\n", - ); + args: ("foo", "ONE", "TWO", "THREE"), + stdout: "", + stderr: "error: Justfile does not contain recipe `THREE`.\n", + status: EXIT_FAILURE, } -#[test] -fn argument_mismatch_fewer() { - integration_test( - &["foo", "ONE"], - " +integration_test! { + name: argument_mismatch_fewer, + justfile: " foo A B: echo A:{{A}} B:{{B}} ", - EXIT_FAILURE, - "", - "error: Recipe `foo` got 1 argument but takes 2\n" - ); + args: ("foo", "ONE"), + stdout: "", + stderr: "error: Recipe `foo` got 1 argument but takes 2\n", + status: EXIT_FAILURE, } -#[test] -fn argument_mismatch_more_with_default() { - integration_test( - &["foo", "ONE", "TWO", "THREE"], - " +integration_test! { + name: argument_mismatch_more_with_default, + justfile: " foo A B='B': echo A:{{A}} B:{{B}} ", - EXIT_FAILURE, - "", - "error: Justfile does not contain recipe `THREE`.\n", - ); + args: ("foo", "ONE", "TWO", "THREE"), + stdout: "", + stderr: "error: Justfile does not contain recipe `THREE`.\n", + status: EXIT_FAILURE, } -#[test] -fn argument_mismatch_fewer_with_default() { - integration_test( - &["foo", "bar"], - " +integration_test! { + name: argument_mismatch_fewer_with_default, + justfile: " foo A B C='C': echo A:{{A}} B:{{B}} C:{{C}} ", - EXIT_FAILURE, - "", - "error: Recipe `foo` got 1 argument but takes at least 2\n", - ); + args: ("foo", "bar"), + stdout: "", + stderr: "error: Recipe `foo` got 1 argument but takes at least 2\n", + status: EXIT_FAILURE, } -#[test] -fn unknown_recipe() { - integration_test( - &["foo"], - "hello:", - EXIT_FAILURE, - "", - "error: Justfile does not contain recipe `foo`.\n", - ); +integration_test! { + name: unknown_recipe, + justfile: "hello:", + args: ("foo"), + stdout: "", + stderr: "error: Justfile does not contain recipe `foo`.\n", + status: EXIT_FAILURE, } -#[test] -fn unknown_recipes() { - integration_test( - &["foo", "bar"], - "hello:", - EXIT_FAILURE, - "", - "error: Justfile does not contain recipes `foo` or `bar`.\n", - ); +integration_test! { + name: unknown_recipes, + justfile: "hello:", + args: ("foo", "bar"), + stdout: "", + stderr: "error: Justfile does not contain recipes `foo` or `bar`.\n", + status: EXIT_FAILURE, } -#[test] -fn color_always() { - integration_test( - &["--color", "always"], - "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", - 100, - "", - "\u{1b}[1;31merror:\u{1b}[0m \u{1b}[1mBacktick failed with exit code 100 +integration_test! { + name: color_always, + justfile: "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", + args: ("--color", "always"), + stdout: "", + stderr: "\u{1b}[1;31merror:\u{1b}[0m \u{1b}[1mBacktick failed with exit code 100 \u{1b}[0m |\n2 | a = `exit 100`\n | \u{1b}[1;31m^^^^^^^^^^\u{1b}[0m\n", - ); + status: 100, } -#[test] -fn color_never() { - integration_test( - &["--color", "never"], - "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", - 100, - "", - "error: Backtick failed with exit code 100 +integration_test! { + name: color_never, + justfile: "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", + args: ("--color", "never"), + stdout: "", + stderr: "error: Backtick failed with exit code 100 | 2 | a = `exit 100` | ^^^^^^^^^^ ", - ); + status: 100, } -#[test] -fn color_auto() { - integration_test( - &["--color", "auto"], - "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", - 100, - "", - "error: Backtick failed with exit code 100 +integration_test! { + name: color_auto, + justfile: "b = a\na = `exit 100`\nbar:\n echo '{{`exit 200`}}'", + args: ("--color", "auto"), + stdout: "", + stderr: "error: Backtick failed with exit code 100 | 2 | a = `exit 100` | ^^^^^^^^^^ ", - ); + status: 100, } -#[test] -fn colors_no_context() { - let text =" +integration_test! { + name: colors_no_context, + justfile: " recipe: - @exit 100"; - integration_test( - &["--color=always"], - text, - 100, - "", - "\u{1b}[1;31merror:\u{1b}[0m \u{1b}[1m\ + @exit 100", + args: ("--color=always"), + stdout: "", + stderr: "\u{1b}[1;31merror:\u{1b}[0m \u{1b}[1m\ Recipe `recipe` failed on line 3 with exit code 100\u{1b}[0m\n", - ); + status: 100, } -#[test] -fn dump() { - let text = r#" +integration_test! { + name: dump, + justfile: r#" # this recipe does something recipe a b +d: - @exit 100"#; - integration_test( - &["--dump"], - text, - EXIT_SUCCESS, - "# this recipe does something + @exit 100"#, + args: ("--dump"), + stdout: "# this recipe does something recipe a b +d: @exit 100 ", - "", - ); + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn mixed_whitespace() { - integration_test( - &[], - "bar:\n\t echo hello", - EXIT_FAILURE, - "", - "error: Found a mix of tabs and spaces in leading whitespace: `␉␠` +integration_test! { + name: mixed_whitespace, + justfile: "bar:\n\t echo hello", + args: (), + stdout: "", + stderr: "error: Found a mix of tabs and spaces in leading whitespace: `␉␠` Leading whitespace may consist of tabs or spaces, but not both | 2 | echo hello | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn extra_leading_whitespace() { - integration_test( - &[], - "bar:\n\t\techo hello\n\t\t\techo goodbye", - EXIT_FAILURE, - "", - "error: Recipe line has extra leading whitespace +integration_test! { + name: extra_leading_whitespace, + justfile: "bar:\n\t\techo hello\n\t\t\techo goodbye", + args: (), + stdout: "", + stderr: "error: Recipe line has extra leading whitespace | 3 | echo goodbye | ^^^^^^^^^^^^^^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn inconsistent_leading_whitespace() { - integration_test( - &[], - "bar:\n\t\techo hello\n\t echo goodbye", - EXIT_FAILURE, - "", - "error: Recipe line has inconsistent leading whitespace. Recipe started with `␉␉` but found line with `␉␠` +integration_test! { + name: inconsistent_leading_whitespace, + justfile: "bar:\n\t\techo hello\n\t echo goodbye", + args: (), + stdout: "", + stderr: "error: Recipe line has inconsistent leading whitespace. Recipe started with `␉␉` but found line with `␉␠` | 3 | echo goodbye | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn required_after_default() { - integration_test( - &[], - "bar:\nhello baz arg='foo' bar:", - EXIT_FAILURE, - "", - "error: Non-default parameter `bar` follows default parameter +integration_test! { + name: required_after_default, + justfile: "bar:\nhello baz arg='foo' bar:", + args: (), + stdout: "", + stderr: "error: Non-default parameter `bar` follows default parameter | 2 | hello baz arg='foo' bar: | ^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn required_after_variadic() { - integration_test( - &[], - "bar:\nhello baz +arg bar:", - EXIT_FAILURE, - "", - "error: Parameter `bar` follows variadic parameter +integration_test! { + name: required_after_variadic, + justfile: "bar:\nhello baz +arg bar:", + args: (), + stdout: "", + stderr: "error: Parameter `bar` follows variadic parameter | 2 | hello baz +arg bar: | ^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn use_string_default() { - integration_test( - &["hello", "ABC"], - r#" +integration_test! { + name: use_string_default, + justfile: r#" bar: hello baz arg="XYZ\t\" ": echo '{{baz}}...{{arg}}' "#, - EXIT_SUCCESS, - "ABC...XYZ\t\"\t\n", - "echo 'ABC...XYZ\t\"\t'\n", - ); + args: ("hello", "ABC"), + stdout: "ABC...XYZ\t\"\t\n", + stderr: "echo 'ABC...XYZ\t\"\t'\n", + status: EXIT_SUCCESS, } -#[test] -fn use_raw_string_default() { - integration_test( - &["hello", "ABC"], - r#" +integration_test! { + name: use_raw_string_default, + justfile: r#" bar: -hello baz arg='XYZ\t\" ': - echo '{{baz}}...{{arg}}' +hello baz arg='XYZ\t" ': + printf '{{baz}}...{{arg}}' "#, - EXIT_SUCCESS, - "ABC...XYZ\t\\\"\t\n", - "echo 'ABC...XYZ\\t\\\"\t'\n", - ); + args: ("hello", "ABC"), + stdout: "ABC...XYZ\t\"\t", + stderr: "printf 'ABC...XYZ\\t\"\t'\n", + status: EXIT_SUCCESS, } -#[test] -fn supply_use_default() { - integration_test( - &["hello", "0", "1"], - r#" +integration_test! { + name: supply_use_default, + justfile: r#" hello a b='B' c='C': echo {{a}} {{b}} {{c}} "#, - EXIT_SUCCESS, - "0 1 C\n", - "echo 0 1 C\n", - ); + args: ("hello", "0", "1"), + stdout: "0 1 C\n", + stderr: "echo 0 1 C\n", + status: EXIT_SUCCESS, } -#[test] -fn supply_defaults() { - integration_test( - &["hello", "0", "1", "2"], - r#" +integration_test! { + name: supply_defaults, + justfile: r#" hello a b='B' c='C': echo {{a}} {{b}} {{c}} "#, - EXIT_SUCCESS, - "0 1 2\n", - "echo 0 1 2\n", - ); + args: ("hello", "0", "1", "2"), + stdout: "0 1 2\n", + stderr: "echo 0 1 2\n", + status: EXIT_SUCCESS, } -#[test] -fn list() { - integration_test( - &["--list"], - r#" +integration_test! { + name: list, + justfile: r#" # this does a thing hello a b='B ' c='C': @@ -1275,147 +1022,129 @@ hello a b='B ' c='C': a Z="\t z": "#, - EXIT_SUCCESS, - r"Available recipes: + args: ("--list"), + stdout: r"Available recipes: a Z='\t z' hello a b='B\t' c='C' # this does a thing ", - "", - ); + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn show_suggestion() { - integration_test( - &["--show", "hell"], - r#" +integration_test! { + name: show_suggestion, + justfile: r#" hello a b='B ' c='C': echo {{a}} {{b}} {{c}} a Z="\t z": "#, - EXIT_FAILURE, - "", - "Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n", - ); + args: ("--show", "hell"), + stdout: "", + stderr: "Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n", + status: EXIT_FAILURE, } -#[test] -fn show_no_suggestion() { - integration_test( - &["--show", "hell"], - r#" +integration_test! { + name: show_no_suggestion, + justfile: r#" helloooooo a b='B ' c='C': echo {{a}} {{b}} {{c}} a Z="\t z": "#, - EXIT_FAILURE, - "", - "Justfile does not contain recipe `hell`.\n", - ); + args: ("--show", "hell"), + stdout: "", + stderr: "Justfile does not contain recipe `hell`.\n", + status: EXIT_FAILURE, } -#[test] -fn run_suggestion() { - integration_test( - &["hell"], - r#" +integration_test! { + name: run_suggestion, + justfile: r#" hello a b='B ' c='C': echo {{a}} {{b}} {{c}} a Z="\t z": "#, - EXIT_FAILURE, - "", - "error: Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n", - ); + args: ("hell"), + stdout: "", + stderr: "error: Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n", + status: EXIT_FAILURE, } -#[test] -fn line_continuation_with_space() { - integration_test( - &[], - r#" +integration_test! { + name: line_continuation_with_space, + justfile: r#" foo: echo a\ b \ c "#, - EXIT_SUCCESS, - "a b c\n", - "echo a b c\n", - ); + args: (), + stdout: "a b c\n", + stderr: "echo a b c\n", + status: EXIT_SUCCESS, } -#[test] -fn line_continuation_with_quoted_space() { - integration_test( - &[], - r#" +integration_test! { + name: line_continuation_with_quoted_space, + justfile: r#" foo: echo 'a\ b \ c' "#, - EXIT_SUCCESS, - "a b c\n", - "echo 'a b c'\n", - ); + args: (), + stdout: "a b c\n", + stderr: "echo 'a b c'\n", + status: EXIT_SUCCESS, } -#[test] -fn line_continuation_no_space() { - integration_test( - &[], - r#" +integration_test! { + name: line_continuation_no_space, + justfile: r#" foo: echo a\ b\ c "#, - EXIT_SUCCESS, - "abc\n", - "echo abc\n", - ); + args: (), + stdout: "abc\n", + stderr: "echo abc\n", + status: EXIT_SUCCESS, } -#[test] -fn quiet_recipe() { - integration_test( - &[], - r#" +integration_test! { + name: quiet_recipe, + justfile: r#" @quiet: # a # b @echo c "#, - EXIT_SUCCESS, - "c\n", - "echo c\n", - ); + args: (), + stdout: "c\n", + stderr: "echo c\n", + status: EXIT_SUCCESS, } -#[test] -fn quiet_shebang_recipe() { - integration_test( - &[], - r#" +integration_test! { + name: quiet_shebang_recipe, + justfile: r#" @quiet: #!/bin/sh echo hello "#, - EXIT_SUCCESS, - "hello\n", - "#!/bin/sh\necho hello\n", - ); + args: (), + stdout: "hello\n", + stderr: "#!/bin/sh\necho hello\n", + status: EXIT_SUCCESS, } -#[test] -fn shebang_line_numbers() { - integration_test( - &[], - r#" +integration_test! { + name: shebang_line_numbers, + justfile: r#" quiet: #!/usr/bin/env cat @@ -1428,8 +1157,8 @@ quiet: "#, - EXIT_SUCCESS, - "#!/usr/bin/env cat + args: (), + stdout: "#!/usr/bin/env cat @@ -1442,235 +1171,205 @@ c ", - "", - ); + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn complex_dependencies() { - integration_test( - &["b"], - r#" +integration_test! { + name: complex_dependencies, + justfile: r#" a: b b: c: b a "#, - EXIT_SUCCESS, - "", - "" - ); + args: ("b"), + stdout: "", + stderr: "", + status: EXIT_SUCCESS, } -#[test] -fn parameter_shadows_variable() { - integration_test( - &["a"], - "FOO = 'hello'\na FOO:", - EXIT_FAILURE, - "", - "error: Parameter `FOO` shadows variable of the same name +integration_test! { + name: parameter_shadows_variable, + justfile: "FOO = 'hello'\na FOO:", + args: ("a"), + stdout: "", + stderr: "error: Parameter `FOO` shadows variable of the same name | 2 | a FOO: | ^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn dependency_takes_arguments() { - integration_test( - &["b"], - "b: a\na FOO:", - EXIT_FAILURE, - "", - "error: Recipe `b` depends on `a` which requires arguments. Dependencies may not require arguments +integration_test! { + name: dependency_takes_arguments, + justfile: "b: a\na FOO:", + args: ("b"), + stdout: "", + stderr: "error: Recipe `b` depends on `a` which requires arguments. Dependencies may not require arguments | 1 | b: a | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn duplicate_parameter() { - integration_test( - &["a"], - "a foo foo:", - EXIT_FAILURE, - "", - "error: Recipe `a` has duplicate parameter `foo` +integration_test! { + name: duplicate_parameter, + justfile: "a foo foo:", + args: ("a"), + stdout: "", + stderr: "error: Recipe `a` has duplicate parameter `foo` | 1 | a foo foo: | ^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn duplicate_dependency() { - integration_test( - &["a"], - "b:\na: b b", - EXIT_FAILURE, - "", - "error: Recipe `a` has duplicate dependency `b` +integration_test! { + name: duplicate_dependency, + justfile: "b:\na: b b", + args: ("a"), + stdout: "", + stderr: "error: Recipe `a` has duplicate dependency `b` | 2 | a: b b | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn duplicate_recipe() { - integration_test( - &["b"], - "b:\nb:", - EXIT_FAILURE, - "", - "error: Recipe `b` first defined on line 1 is redefined on line 2 +integration_test! { + name: duplicate_recipe, + justfile: "b:\nb:", + args: ("b"), + stdout: "", + stderr: "error: Recipe `b` first defined on line 1 is redefined on line 2 | 2 | b: | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn duplicate_variable() { - integration_test( - &["foo"], - "a = 'hello'\na = 'hello'\nfoo:", - EXIT_FAILURE, - "", - "error: Variable `a` has multiple definitions +integration_test! { + name: duplicate_variable, + justfile: "a = 'hello'\na = 'hello'\nfoo:", + args: ("foo"), + stdout: "", + stderr: "error: Variable `a` has multiple definitions | 2 | a = 'hello' | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn unexpected_token() { - integration_test( - &["foo"], - "foo: 'bar'", - EXIT_FAILURE, - "", - "error: Expected name, end of line, or end of file, but found raw string +integration_test! { + name: unexpected_token, + justfile: "foo: 'bar'", + args: ("foo"), + stdout: "", + stderr: "error: Expected name, end of line, or end of file, but found raw string | 1 | foo: 'bar' | ^^^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn self_dependency() { - integration_test( - &["a"], - "a: a", - EXIT_FAILURE, - "", - "error: Recipe `a` depends on itself +integration_test! { + name: self_dependency, + justfile: "a: a", + args: ("a"), + stdout: "", + stderr: "error: Recipe `a` depends on itself | 1 | a: a | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn long_circular_recipe_dependency() { - integration_test( - &["a"], - "a: b\nb: c\nc: d\nd: a", - EXIT_FAILURE, - "", - "error: Recipe `d` has circular dependency `a -> b -> c -> d -> a` +integration_test! { + name: long_circular_recipe_dependency, + justfile: "a: b\nb: c\nc: d\nd: a", + args: ("a"), + stdout: "", + stderr: "error: Recipe `d` has circular dependency `a -> b -> c -> d -> a` | 4 | d: a | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn variable_self_dependency() { - integration_test( - &["a"], - "z = z\na:", - EXIT_FAILURE, - "", - "error: Variable `z` is defined in terms of itself +integration_test! { + name: variable_self_dependency, + justfile: "z = z\na:", + args: ("a"), + stdout: "", + stderr: "error: Variable `z` is defined in terms of itself | 1 | z = z | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn variable_circular_dependency() { - integration_test( - &["a"], - "x = y\ny = z\nz = x\na:", - EXIT_FAILURE, - "", - "error: Variable `x` depends on its own value: `x -> y -> z -> x` +integration_test! { + name: variable_circular_dependency, + justfile: "x = y\ny = z\nz = x\na:", + args: ("a"), + stdout: "", + stderr: "error: Variable `x` depends on its own value: `x -> y -> z -> x` | 1 | x = y | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn invalid_escape_sequence() { - integration_test( - &["a"], - r#"x = "\q" +integration_test! { + name: invalid_escape_sequence, + justfile: r#"x = "\q" a:"#, - EXIT_FAILURE, - "", - "error: `\\q` is not a valid escape sequence + args: ("a"), + stdout: "", + stderr: "error: `\\q` is not a valid escape sequence | 1 | x = \"\\q\" | ^^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn multiline_raw_string() { - integration_test( - &["a"], - " +integration_test! { + name: multiline_raw_string, + justfile: " string = 'hello whatever' a: echo '{{string}}' ", - EXIT_SUCCESS, - "hello + args: ("a"), + stdout: "hello whatever ", - "echo 'hello + stderr: "echo 'hello whatever' ", - ); + status: EXIT_SUCCESS, } -#[test] -fn error_line_after_multiline_raw_string() { - integration_test( - &["a"], - " +integration_test! { + name: error_line_after_multiline_raw_string, + justfile: " string = 'hello whatever' + 'yo' @@ -1678,21 +1377,19 @@ whatever' + 'yo' a: echo '{{foo}}' ", - EXIT_FAILURE, - "", - "error: Variable `foo` not defined + args: ("a"), + stdout: "", + stderr: "error: Variable `foo` not defined | 7 | echo '{{foo}}' | ^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn error_column_after_multiline_raw_string() { - integration_test( - &["a"], - " +integration_test! { + name: error_column_after_multiline_raw_string, + justfile: " string = 'hello whatever' + bar @@ -1700,151 +1397,133 @@ whatever' + bar a: echo '{{string}}' ", - EXIT_FAILURE, - "", - "error: Variable `bar` not defined + args: ("a"), + stdout: "", + stderr: "error: Variable `bar` not defined | 4 | whatever' + bar | ^^^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn multiline_raw_string_in_interpolation() { - integration_test( - &["a"], - r#" +integration_test! { + name: multiline_raw_string_in_interpolation, + justfile: r#" a: echo '{{"a" + ' ' + "b"}}' "#, - EXIT_SUCCESS, - "a + args: ("a"), + stdout: "a b ", - "echo 'a + stderr: "echo 'a b' ", - ); + status: EXIT_SUCCESS, } -#[test] -fn error_line_after_multiline_raw_string_in_interpolation() { - integration_test( - &["a"], - r#" +integration_test! { + name: error_line_after_multiline_raw_string_in_interpolation, + justfile: r#" a: echo '{{"a" + ' ' + "b"}}' echo {{b}} "#, - EXIT_FAILURE, - "", - "error: Variable `b` not defined + args: ("a"), + stdout: "", + stderr: "error: Variable `b` not defined | 6 | echo {{b}} | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn unterminated_raw_string() { - integration_test( - &["a"], - " +integration_test! { + name: unterminated_raw_string, + justfile: " a b=': ", - EXIT_FAILURE, - "", - "error: Unterminated string + args: ("a"), + stdout: "", + stderr: "error: Unterminated string | 2 | a b=': | ^ ", - ); + status: EXIT_FAILURE, } -#[test] -fn unterminated_string() { - integration_test( - &["a"], - r#" +integration_test! { + name: unterminated_string, + justfile: r#" a b=": "#, - EXIT_FAILURE, - "", - r#"error: Unterminated string + args: ("a"), + stdout: "", + stderr: r#"error: Unterminated string | 2 | a b=": | ^ "#, - ); + status: EXIT_FAILURE, } -#[test] -fn variadic_recipe() { - integration_test( - &["a", "0", "1", "2", "3", " 4 "], - " +integration_test! { + name: variadic_recipe, + justfile: " a x y +z: echo {{x}} {{y}} {{z}} ", - EXIT_SUCCESS, - "0 1 2 3 4\n", - "echo 0 1 2 3 4 \n", - ); + args: ("a", "0", "1", "2", "3", " 4 "), + stdout: "0 1 2 3 4\n", + stderr: "echo 0 1 2 3 4 \n", + status: EXIT_SUCCESS, } -#[test] -fn variadic_ignore_default() { - integration_test( - &["a", "0", "1", "2", "3", " 4 "], - " +integration_test! { + name: variadic_ignore_default, + justfile: " a x y +z='HELLO': echo {{x}} {{y}} {{z}} ", - EXIT_SUCCESS, - "0 1 2 3 4\n", - "echo 0 1 2 3 4 \n", - ); + args: ("a", "0", "1", "2", "3", " 4 "), + stdout: "0 1 2 3 4\n", + stderr: "echo 0 1 2 3 4 \n", + status: EXIT_SUCCESS, } -#[test] -fn variadic_use_default() { - integration_test( - &["a", "0", "1"], - " +integration_test! { + name: variadic_use_default, + justfile: " a x y +z='HELLO': echo {{x}} {{y}} {{z}} ", - EXIT_SUCCESS, - "0 1 HELLO\n", - "echo 0 1 HELLO\n", - ); + args: ("a", "0", "1"), + stdout: "0 1 HELLO\n", + stderr: "echo 0 1 HELLO\n", + status: EXIT_SUCCESS, } -#[test] -fn variadic_too_few() { - integration_test( - &["a", "0", "1"], - " +integration_test! { + name: variadic_too_few, + justfile: " a x y +z: echo {{x}} {{y}} {{z}} ", - EXIT_FAILURE, - "", - "error: Recipe `a` got 2 arguments but takes at least 3\n", - ); + args: ("a", "0", "1"), + stdout: "", + stderr: "error: Recipe `a` got 2 arguments but takes at least 3\n", + status: EXIT_FAILURE, } -#[test] -fn argument_grouping() { - integration_test( - &["BAR", "0", "FOO", "1", "2", "BAZ", "3", "4", "5"], - " +integration_test! { + name: argument_grouping, + justfile: " FOO A B='blarg': echo foo: {{A}} {{B}} @@ -1854,27 +1533,25 @@ BAR X: BAZ +Z: echo baz: {{Z}} ", - EXIT_SUCCESS, - "bar: 0\nfoo: 1 2\nbaz: 3 4 5\n", - "echo bar: 0\necho foo: 1 2\necho baz: 3 4 5\n", - ); + args: ("BAR", "0", "FOO", "1", "2", "BAZ", "3", "4", "5"), + stdout: "bar: 0\nfoo: 1 2\nbaz: 3 4 5\n", + stderr: "echo bar: 0\necho foo: 1 2\necho baz: 3 4 5\n", + status: EXIT_SUCCESS, } -#[test] -fn missing_second_dependency() { - integration_test( - &[], - " +integration_test! { + name: missing_second_dependency, + justfile: " x: a: x y ", - EXIT_FAILURE, - "", - "error: Recipe `a` has unknown dependency `y` + args: (), + stdout: "", + stderr: "error: Recipe `a` has unknown dependency `y` | 4 | a: x y | ^ ", - ); + status: EXIT_FAILURE, } diff --git a/src/lib.rs b/src/lib.rs index 957c04d..2d518b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,6 +14,9 @@ mod unit; #[cfg(test)] mod integration; +#[cfg(test)] +mod search; + mod platform; mod app; diff --git a/src/search.rs b/src/search.rs new file mode 100644 index 0000000..2263507 --- /dev/null +++ b/src/search.rs @@ -0,0 +1,142 @@ +extern crate brev; + +use ::prelude::*; +use tempdir::TempDir; +use std::{path, str}; + +fn search_test>(path: P, args: &[&str]) { + let mut binary = env::current_dir().unwrap(); + binary.push("./target/debug/just"); + let output = process::Command::new(binary) + .current_dir(path) + .args(args) + .output() + .expect("just invocation failed"); + + assert_eq!(output.status.code().unwrap(), 0); + + let stdout = str::from_utf8(&output.stdout).unwrap(); + assert_eq!(stdout, "ok\n"); + + let stderr = str::from_utf8(&output.stderr).unwrap(); + assert_eq!(stderr, "echo ok\n"); +} + +#[test] +fn test_justfile_search() { + let tmp = TempDir::new("just-test-justfile-search") + .expect("test justfile search: failed to create temporary directory"); + let mut path = tmp.path().to_path_buf(); + path.push("justfile"); + brev::dump(&path, "default:\n\techo ok"); + path.pop(); + + path.push("a"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + path.push("b"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + path.push("c"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + path.push("d"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + + search_test(path, &[]); +} + +#[test] +fn test_capitalized_justfile_search() { + let tmp = TempDir::new("just-test-justfile-search") + .expect("test justfile search: failed to create temporary directory"); + let mut path = tmp.path().to_path_buf(); + path.push("Justfile"); + brev::dump(&path, "default:\n\techo ok"); + path.pop(); + + path.push("a"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + path.push("b"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + path.push("c"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + path.push("d"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + + search_test(path, &[]); +} + +#[test] +fn test_capitalization_priority() { + let tmp = TempDir::new("just-test-justfile-search") + .expect("test justfile search: failed to create temporary directory"); + let mut path = tmp.path().to_path_buf(); + path.push("justfile"); + brev::dump(&path, "default:\n\techo ok"); + path.pop(); + path.push("Justfile"); + brev::dump(&path, "default:\n\techo fail"); + path.pop(); + + // if we see "default\n\techo fail" in `justfile` then we're running + // in a case insensitive filesystem, so just bail + path.push("justfile"); + if brev::slurp(&path) == "default:\n\techo fail" { + return; + } + path.pop(); + + path.push("a"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + path.push("b"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + path.push("c"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + path.push("d"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + + search_test(path, &[]); +} + +#[test] +fn test_upwards_path_argument() { + let tmp = TempDir::new("just-test-justfile-search") + .expect("test justfile search: failed to create temporary directory"); + let mut path = tmp.path().to_path_buf(); + path.push("justfile"); + brev::dump(&path, "default:\n\techo ok"); + path.pop(); + + path.push("a"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + + path.push("justfile"); + brev::dump(&path, "default:\n\techo bad"); + path.pop(); + + search_test(&path, &["../"]); + search_test(&path, &["../default"]); +} + +#[test] +fn test_downwards_path_argument() { + let tmp = TempDir::new("just-test-justfile-search") + .expect("test justfile search: failed to create temporary directory"); + let mut path = tmp.path().to_path_buf(); + path.push("justfile"); + brev::dump(&path, "default:\n\techo bad"); + path.pop(); + + path.push("a"); + fs::create_dir(&path).expect("test justfile search: failed to create intermediary directory"); + + path.push("justfile"); + brev::dump(&path, "default:\n\techo ok"); + path.pop(); + path.pop(); + + search_test(&path, &["a/"]); + search_test(&path, &["a/default"]); + search_test(&path, &["./a/"]); + search_test(&path, &["./a/default"]); + search_test(&path, &["./a/"]); + search_test(&path, &["./a/default"]); +}