use crate::common::*; test! { name: alias_listing, justfile: " foo: echo foo alias f := foo ", args: ("--list"), stdout: " Available recipes: foo f # alias for `foo` ", } test! { name: alias_listing_multiple_aliases, justfile: "foo:\n echo foo\nalias f := foo\nalias fo := foo", args: ("--list"), stdout: " Available recipes: foo f # alias for `foo` fo # alias for `foo` ", } test! { name: alias_listing_parameters, justfile: "foo PARAM='foo':\n echo {{PARAM}}\nalias f := foo", args: ("--list"), stdout: " Available recipes: foo PARAM='foo' f PARAM='foo' # alias for `foo` ", } test! { name: alias_listing_private, justfile: "foo PARAM='foo':\n echo {{PARAM}}\nalias _f := foo", args: ("--list"), stdout: " Available recipes: foo PARAM='foo' ", } test! { name: alias, justfile: "foo:\n echo foo\nalias f := foo", args: ("f"), stdout: "foo\n", stderr: "echo foo\n", } test! { name: alias_with_parameters, justfile: "foo value='foo':\n echo {{value}}\nalias f := foo", args: ("f", "bar"), stdout: "bar\n", stderr: "echo bar\n", } test! { name: bad_setting, justfile: " set foo ", stderr: " error: Expected ':=', but found end of line | 1 | set foo | ^ ", status: EXIT_FAILURE, } test! { name: alias_with_dependencies, justfile: "foo:\n echo foo\nbar: foo\nalias b := bar", args: ("b"), stdout: "foo\n", stderr: "echo foo\n", } test! { name: duplicate_alias, justfile: "alias foo := bar\nalias foo := baz\n", stderr: " error: Alias `foo` first defined on line 1 is redefined on line 2 | 2 | alias foo := baz | ^^^ ", status: EXIT_FAILURE, } test! { name: unknown_alias_target, justfile: "alias foo := bar\n", stderr: " error: Alias `foo` has an unknown target `bar` | 1 | alias foo := bar | ^^^ ", status: EXIT_FAILURE, } test! { name: alias_shadows_recipe, justfile: "bar:\n echo bar\nalias foo := bar\nfoo:\n echo foo", stderr: " error: Alias `foo` defined on line 3 shadows recipe `foo` defined on line 4 | 3 | alias foo := bar | ^^^ ", status: EXIT_FAILURE, } test! { name: alias_show, justfile: "foo:\n bar\nalias f := foo", args: ("--show", "f"), stdout: " alias f := foo foo: bar ", } test! { name: alias_show_missing_target, justfile: "alias f := foo", args: ("--show", "f"), stderr: " error: Alias `f` has an unknown target `foo` | 1 | alias f := foo | ^ ", status: EXIT_FAILURE, } test! { name: default, justfile: "default:\n echo hello\nother: \n echo bar", stdout: "hello\n", stderr: "echo hello\n", } test! { name: quiet, justfile: "default:\n @echo hello", stdout: "hello\n", } test! { name: verbose, justfile: "default:\n @echo hello", args: ("--verbose"), stdout: "hello\n", stderr: "===> Running recipe `default`...\necho hello\n", } test! { name: order, justfile: " b: a echo b @mv a b a: echo a @touch F @touch a d: c echo d @rm c c: b echo c @mv b c", args: ("a", "d"), stdout: "a\nb\nc\nd\n", stderr: "echo a\necho b\necho c\necho d\n", } test! { name: summary, justfile: "b: a a: d: c c: b _z: _y _y: ", args: ("--summary"), stdout: "a b c d\n", } test! { name: summary_sorted, justfile: " b: c: a: ", args: ("--summary"), stdout: "a b c\n", } test! { name: summary_unsorted, justfile: " b: c: a: ", args: ("--summary", "--unsorted"), stdout: "b c a\n", } test! { name: select, justfile: "b: @echo b a: @echo a d: @echo d c: @echo c", args: ("d", "c"), stdout: "d\nc\n", } test! { name: print, justfile: "b: echo b a: echo a d: echo d c: echo c", args: ("d", "c"), stdout: "d\nc\n", stderr: "echo d\necho c\n", } test! { name: show, justfile: r#"hello := "foo" bar := hello + hello recipe: echo {{hello + "bar" + bar}}"#, args: ("--show", "recipe"), stdout: r#" recipe: echo {{ hello + "bar" + bar }} "#, } test! { name: status_passthrough, justfile: " hello: recipe: @exit 100", args: ("recipe"), stderr: "error: Recipe `recipe` failed on line 5 with exit code 100\n", status: 100, } test! { name: unknown_dependency, justfile: "bar:\nhello:\nfoo: bar baaaaaaaz hello", stderr: " error: Recipe `foo` has unknown dependency `baaaaaaaz` | 3 | foo: bar baaaaaaaz hello | ^^^^^^^^^ ", status: EXIT_FAILURE, } test! { name: backtick_success, justfile: "a := `printf Hello,`\nbar:\n printf '{{a + `printf ' world.'`}}'", stdout: "Hello, world.", stderr: "printf 'Hello, world.'\n", } test! { name: backtick_trimming, justfile: "a := `echo Hello,`\nbar:\n echo '{{a + `echo ' world.'`}}'", stdout: "Hello, world.\n", stderr: "echo 'Hello, world.'\n", } test! { name: backtick_code_assignment, justfile: "b := a\na := `exit 100`\nbar:\n echo '{{`exit 200`}}'", stderr: " error: Backtick failed with exit code 100 | 2 | a := `exit 100` | ^^^^^^^^^^ ", status: 100, } test! { name: backtick_code_interpolation, justfile: "b := a\na := `echo hello`\nbar:\n echo '{{`exit 200`}}'", stderr: " error: Backtick failed with exit code 200 | 4 | echo '{{`exit 200`}}' | ^^^^^^^^^^ ", status: 200, } test! { name: backtick_code_interpolation_mod, justfile: "f:\n 無{{`exit 200`}}", stderr: " error: Backtick failed with exit code 200 | 2 | 無{{`exit 200`}} | ^^^^^^^^^^ ", status: 200, } test! { name: backtick_code_interpolation_tab, justfile: " backtick-fail: \techo {{`exit 200`}} ", stderr: " error: Backtick failed with exit code 200 | 2 | echo {{`exit 200`}} | ^^^^^^^^^^ ", status: 200, } test! { name: backtick_code_interpolation_tabs, justfile: " backtick-fail: \techo {{\t`exit 200`}} ", stderr: "error: Backtick failed with exit code 200 | 2 | echo {{ `exit 200`}} | ^^^^^^^^^^ ", status: 200, } test! { name: backtick_code_interpolation_inner_tab, justfile: " backtick-fail: \techo {{\t`exit\t\t200`}} ", stderr: " error: Backtick failed with exit code 200 | 2 | echo {{ `exit 200`}} | ^^^^^^^^^^^^^^^^^ ", status: 200, } test! { name: backtick_code_interpolation_leading_emoji, justfile: " backtick-fail: \techo 😬{{`exit 200`}} ", stderr: " error: Backtick failed with exit code 200 | 2 | echo 😬{{`exit 200`}} | ^^^^^^^^^^ ", status: 200, } test! { name: backtick_code_interpolation_unicode_hell, justfile: " backtick-fail: \techo \t\t\t😬鎌鼬{{\t\t`exit 200 # \t\t\tabc`}}\t\t\t😬鎌鼬 ", stderr: " error: Backtick failed with exit code 200 | 2 | echo 😬鎌鼬{{ `exit 200 # abc`}} 😬鎌鼬 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ", status: 200, } test! { name: backtick_code_long, justfile: " b := a a := `echo hello` bar: echo '{{`exit 200`}}' ", stderr: " error: Backtick failed with exit code 200 | 10 | echo '{{`exit 200`}}' | ^^^^^^^^^^ ", status: 200, } test! { name: shebang_backtick_failure, justfile: "foo: #!/bin/sh echo hello echo {{`exit 123`}}", stdout: "", stderr: " error: Backtick failed with exit code 123 | 4 | echo {{`exit 123`}} | ^^^^^^^^^^ ", status: 123, } test! { name: command_backtick_failure, justfile: "foo: echo hello echo {{`exit 123`}}", stdout: "hello\n", stderr: " echo hello error: Backtick failed with exit code 123 | 3 | echo {{`exit 123`}} | ^^^^^^^^^^ ", status: 123, } test! { name: assignment_backtick_failure, justfile: "foo: echo hello echo {{`exit 111`}} a := `exit 222`", stdout: "", stderr: " error: Backtick failed with exit code 222 | 4 | a := `exit 222` | ^^^^^^^^^^ ", status: 222, } test! { name: unknown_override_options, justfile: "foo: echo hello echo {{`exit 111`}} a := `exit 222`", args: ("--set", "foo", "bar", "--set", "baz", "bob", "--set", "a", "b", "a", "b"), stderr: "error: Variables `baz` and `foo` overridden on the command line but not present \ in justfile\n", status: EXIT_FAILURE, } test! { name: unknown_override_args, justfile: "foo: echo hello echo {{`exit 111`}} a := `exit 222`", args: ("foo=bar", "baz=bob", "a=b", "a", "b"), stderr: "error: Variables `baz` and `foo` overridden on the command line but not present \ in justfile\n", status: EXIT_FAILURE, } test! { name: unknown_override_arg, justfile: "foo: echo hello echo {{`exit 111`}} a := `exit 222`", args: ("foo=bar", "a=b", "a", "b"), stderr: "error: Variable `foo` overridden on the command line but not present in justfile\n", status: EXIT_FAILURE, } test! { name: overrides_first, justfile: r#" foo := "foo" a := "a" baz := "baz" recipe arg: echo arg={{arg}} echo {{foo + a + baz}}"#, args: ("foo=bar", "a=b", "recipe", "baz=bar"), stdout: "arg=baz=bar\nbarbbaz\n", stderr: "echo arg=baz=bar\necho barbbaz\n", } test! { name: overrides_not_evaluated, justfile: r#" foo := `exit 1` a := "a" baz := "baz" recipe arg: echo arg={{arg}} echo {{foo + a + baz}}"#, args: ("foo=bar", "a=b", "recipe", "baz=bar"), stdout: "arg=baz=bar\nbarbbaz\n", stderr: "echo arg=baz=bar\necho barbbaz\n", } test! { name: dry_run, justfile: r#" var := `echo stderr 1>&2; echo backtick` command: @touch /this/is/not/a/file {{var}} echo {{`echo command interpolation`}} shebang: #!/bin/sh touch /this/is/not/a/file {{var}} echo {{`echo shebang interpolation`}}"#, args: ("--dry-run", "shebang", "command"), stdout: "", stderr: "#!/bin/sh touch /this/is/not/a/file `echo stderr 1>&2; echo backtick` echo `echo shebang interpolation` touch /this/is/not/a/file `echo stderr 1>&2; echo backtick` echo `echo command interpolation` ", } test! { name: line_error_spacing, justfile: r#" ??? "#, stdout: "", stderr: "error: Unknown start of token: | 10 | ??? | ^ ", status: EXIT_FAILURE, } test! { name: argument_single, justfile: " foo A: echo {{A}} ", args: ("foo", "ARGUMENT"), stdout: "ARGUMENT\n", stderr: "echo ARGUMENT\n", } test! { name: argument_multiple, justfile: " foo A B: echo A:{{A}} B:{{B}} ", args: ("foo", "ONE", "TWO"), stdout: "A:ONE B:TWO\n", stderr: "echo A:ONE B:TWO\n", } test! { name: argument_mismatch_more, justfile: " foo A B: echo A:{{A}} B:{{B}} ", args: ("foo", "ONE", "TWO", "THREE"), stdout: "", stderr: "error: Justfile does not contain recipe `THREE`.\n", status: EXIT_FAILURE, } test! { name: argument_mismatch_fewer, justfile: " foo A B: echo A:{{A}} B:{{B}} ", args: ("foo", "ONE"), stdout: "", stderr: "error: Recipe `foo` got 1 argument but takes 2\nusage:\n just foo A B\n", status: EXIT_FAILURE, } test! { name: argument_mismatch_more_with_default, justfile: " foo A B='B': echo A:{{A}} B:{{B}} ", args: ("foo", "ONE", "TWO", "THREE"), stdout: "", stderr: "error: Justfile does not contain recipe `THREE`.\n", status: EXIT_FAILURE, } test! { name: argument_mismatch_fewer_with_default, justfile: " foo A B C='C': echo A:{{A}} B:{{B}} C:{{C}} ", args: ("foo", "bar"), stdout: "", stderr: " error: Recipe `foo` got 1 argument but takes at least 2 usage: just foo A B C='C' ", status: EXIT_FAILURE, } test! { name: unknown_recipe, justfile: "hello:", args: ("foo"), stdout: "", stderr: "error: Justfile does not contain recipe `foo`.\n", status: EXIT_FAILURE, } 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! { 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! { 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! { 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! { name: colors_no_context, justfile: " recipe: @exit 100", args: ("--color=always"), stdout: "", stderr: "\u{1b}[1;31merror\u{1b}[0m: \u{1b}[1m\ Recipe `recipe` failed on line 2 with exit code 100\u{1b}[0m\n", status: 100, } test! { name: dump, justfile: r#" # this recipe does something recipe a b +d: @exit 100"#, args: ("--dump"), stdout: "# this recipe does something recipe a b +d: @exit 100 ", } test! { name: mixed_whitespace, justfile: "bar:\n\t echo hello", 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! { name: extra_leading_whitespace, justfile: "bar:\n\t\techo hello\n\t\t\techo goodbye", stdout: "", stderr: "error: Recipe line has extra leading whitespace | 3 | echo goodbye | ^^^^^^^^^^^^^^^^ ", status: EXIT_FAILURE, } test! { name: inconsistent_leading_whitespace, justfile: "bar:\n\t\techo hello\n\t echo goodbye", stdout: "", stderr: "error: Recipe line has inconsistent leading whitespace. \ Recipe started with `␉␉` but found line with `␉␠` | 3 | echo goodbye | ^^^^^ ", status: EXIT_FAILURE, } test! { name: required_after_default, justfile: "bar:\nhello baz arg='foo' bar:", stdout: "", stderr: "error: Non-default parameter `bar` follows default parameter | 2 | hello baz arg='foo' bar: | ^^^ ", status: EXIT_FAILURE, } test! { name: required_after_plus_variadic, justfile: "bar:\nhello baz +arg bar:", stdout: "", stderr: "error: Parameter `bar` follows variadic parameter | 2 | hello baz +arg bar: | ^^^ ", status: EXIT_FAILURE, } test! { name: required_after_star_variadic, justfile: "bar:\nhello baz *arg bar:", stdout: "", stderr: "error: Parameter `bar` follows variadic parameter | 2 | hello baz *arg bar: | ^^^ ", status: EXIT_FAILURE, } test! { name: use_string_default, justfile: r#" bar: hello baz arg="XYZ\t\" ": echo '{{baz}}...{{arg}}' "#, args: ("hello", "ABC"), stdout: "ABC...XYZ\t\"\t\n", stderr: "echo 'ABC...XYZ\t\"\t'\n", } test! { name: use_raw_string_default, justfile: r#" bar: hello baz arg='XYZ" ': printf '{{baz}}...{{arg}}' "#, args: ("hello", "ABC"), stdout: "ABC...XYZ\"\t", stderr: "printf 'ABC...XYZ\"\t'\n", } test! { name: supply_use_default, justfile: r#" hello a b='B' c='C': echo {{a}} {{b}} {{c}} "#, args: ("hello", "0", "1"), stdout: "0 1 C\n", stderr: "echo 0 1 C\n", } test! { name: supply_defaults, justfile: r#" hello a b='B' c='C': echo {{a}} {{b}} {{c}} "#, args: ("hello", "0", "1", "2"), stdout: "0 1 2\n", stderr: "echo 0 1 2\n", } test! { name: list, justfile: r#" # this does a thing hello a b='B ' c='C': echo {{a}} {{b}} {{c}} # this comment will be ignored a Z="\t z": # this recipe will not appear _private-recipe: "#, args: ("--list"), stdout: r#" Available recipes: a Z="\t z" hello a b='B ' c='C' # this does a thing "#, } test! { name: list_alignment, justfile: r#" # this does a thing hello a b='B ' c='C': echo {{a}} {{b}} {{c}} # something else a Z="\t z": # this recipe will not appear _private-recipe: "#, args: ("--list"), stdout: r#" Available recipes: a Z="\t z" # something else hello a b='B ' c='C' # this does a thing "#, } test! { name: list_alignment_long, justfile: r#" # this does a thing hello a b='B ' c='C': echo {{a}} {{b}} {{c}} # this does another thing x a b='B ' c='C': echo {{a}} {{b}} {{c}} # something else this-recipe-is-very-very-very-important Z="\t z": # this recipe will not appear _private-recipe: "#, args: ("--list"), stdout: r#" Available recipes: hello a b='B ' c='C' # this does a thing this-recipe-is-very-very-very-important Z="\t z" # something else x a b='B ' c='C' # this does another thing "#, } test! { name: list_sorted, justfile: r#" alias c := b b: a: "#, args: ("--list"), stdout: r#" Available recipes: a b c # alias for `b` "#, } test! { name: list_unsorted, justfile: r#" alias c := b b: a: "#, args: ("--list", "--unsorted"), stdout: r#" Available recipes: b c # alias for `b` a "#, } test! { name: list_heading, justfile: r#" a: b: "#, args: ("--list", "--list-heading", "Cool stuff…\n"), stdout: r#" Cool stuff… a b "#, } test! { name: list_prefix, justfile: r#" a: b: "#, args: ("--list", "--list-prefix", "····"), stdout: r#" Available recipes: ····a ····b "#, } test! { name: list_empty_prefix_and_heading, justfile: r#" a: b: "#, args: ("--list", "--list-heading", "", "--list-prefix", ""), stdout: r#" a b "#, } test! { name: show_suggestion, justfile: r#" hello a b='B ' c='C': echo {{a}} {{b}} {{c}} a Z="\t z": "#, args: ("--show", "hell"), stdout: "", stderr: "Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n", status: EXIT_FAILURE, } test! { name: show_alias_suggestion, justfile: r#" hello a b='B ' c='C': echo {{a}} {{b}} {{c}} alias foo := hello a Z="\t z": "#, args: ("--show", "fo"), stdout: "", stderr: "Justfile does not contain recipe `fo`.\nDid you mean `foo`, an alias for `hello`?\n", status: EXIT_FAILURE, } test! { name: show_no_suggestion, justfile: r#" helloooooo a b='B ' c='C': echo {{a}} {{b}} {{c}} a Z="\t z": "#, args: ("--show", "hell"), stdout: "", stderr: "Justfile does not contain recipe `hell`.\n", status: EXIT_FAILURE, } test! { name: show_no_alias_suggestion, justfile: r#" hello a b='B ' c='C': echo {{a}} {{b}} {{c}} alias foo := hello a Z="\t z": "#, args: ("--show", "fooooooo"), stdout: "", stderr: "Justfile does not contain recipe `fooooooo`.\n", status: EXIT_FAILURE, } test! { name: run_suggestion, justfile: r#" hello a b='B ' c='C': echo {{a}} {{b}} {{c}} a Z="\t z": "#, args: ("hell"), stdout: "", stderr: "error: Justfile does not contain recipe `hell`.\nDid you mean `hello`?\n", status: EXIT_FAILURE, } test! { name: line_continuation_with_space, justfile: r#" foo: echo a\ b \ c "#, stdout: "ab c\n", stderr: "echo ab c\n", } test! { name: line_continuation_with_quoted_space, justfile: r#" foo: echo 'a\ b \ c' "#, stdout: "ab c\n", stderr: "echo 'ab c'\n", } test! { name: line_continuation_no_space, justfile: r#" foo: echo a\ b\ c "#, stdout: "abc\n", stderr: "echo abc\n", } test! { name: test_os_arch_functions_in_interpolation, justfile: r#" foo: echo {{arch()}} {{os()}} {{os_family()}} "#, stdout: format!("{} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(), stderr: format!("echo {} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(), } test! { name: test_os_arch_functions_in_expression, justfile: r#" a := arch() o := os() f := os_family() foo: echo {{a}} {{o}} {{f}} "#, stdout: format!("{} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(), stderr: format!("echo {} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(), } #[cfg(not(windows))] test! { name: env_var_functions, justfile: r#" p := env_var('USER') b := env_var_or_default('ZADDY', 'HTAP') x := env_var_or_default('XYZ', 'ABC') foo: /bin/echo '{{p}}' '{{b}}' '{{x}}' "#, stdout: format!("{} HTAP ABC\n", env::var("USER").unwrap()).as_str(), stderr: format!("/bin/echo '{}' 'HTAP' 'ABC'\n", env::var("USER").unwrap()).as_str(), } #[cfg(not(windows))] test! { name: path_functions, justfile: r#" we := without_extension('/foo/bar/baz.hello') fs := file_stem('/foo/bar/baz.hello') fn := file_name('/foo/bar/baz.hello') dir := parent_directory('/foo/bar/baz.hello') ext := extension('/foo/bar/baz.hello') foo: /bin/echo '{{we}}' '{{fs}}' '{{fn}}' '{{dir}}' '{{ext}}' "#, stdout: "/foo/bar/baz baz baz.hello /foo/bar hello\n", stderr: "/bin/echo '/foo/bar/baz' 'baz' 'baz.hello' '/foo/bar' 'hello'\n", } #[cfg(not(windows))] test! { name: path_functions2, justfile: r#" we := without_extension('/foo/bar/baz') fs := file_stem('/foo/bar/baz.hello.ciao') fn := file_name('/bar/baz.hello.ciao') dir := parent_directory('/foo/') ext := extension('/foo/bar/baz.hello.ciao') foo: /bin/echo '{{we}}' '{{fs}}' '{{fn}}' '{{dir}}' '{{ext}}' "#, stdout: "/foo/bar/baz baz.hello baz.hello.ciao / ciao\n", stderr: "/bin/echo '/foo/bar/baz' 'baz.hello' 'baz.hello.ciao' '/' 'ciao'\n", } #[cfg(not(windows))] test! { name: broken_without_extension_function, justfile: r#" we := without_extension('') foo: /bin/echo '{{we}}' "#, stdout: "", stderr: format!("{} {}\n{}\n{}\n{}\n", "error: Call to function `without_extension` failed:", "Could not extract parent from ``", " |", "1 | we := without_extension(\'\')", " | ^^^^^^^^^^^^^^^^^").as_str(), status: EXIT_FAILURE, } #[cfg(not(windows))] test! { name: broken_extension_function, justfile: r#" we := extension('') foo: /bin/echo '{{we}}' "#, stdout: "", stderr: format!("{}\n{}\n{}\n{}\n", "error: Call to function `extension` failed: Could not extract extension from ``", " |", "1 | we := extension(\'\')", " | ^^^^^^^^^").as_str(), status: EXIT_FAILURE, } #[cfg(not(windows))] test! { name: broken_extension_function2, justfile: r#" we := extension('foo') foo: /bin/echo '{{we}}' "#, stdout: "", stderr: format!("{}\n{}\n{}\n{}\n", "error: Call to function `extension` failed: Could not extract extension from `foo`", " |", "1 | we := extension(\'foo\')", " | ^^^^^^^^^").as_str(), status: EXIT_FAILURE, } #[cfg(not(windows))] test! { name: broken_file_stem_function, justfile: r#" we := file_stem('') foo: /bin/echo '{{we}}' "#, stdout: "", stderr: format!("{}\n{}\n{}\n{}\n", "error: Call to function `file_stem` failed: Could not extract file stem from ``", " |", "1 | we := file_stem(\'\')", " | ^^^^^^^^^").as_str(), status: EXIT_FAILURE, } #[cfg(not(windows))] test! { name: broken_file_name_function, justfile: r#" we := file_name('') foo: /bin/echo '{{we}}' "#, stdout: "", stderr: format!("{}\n{}\n{}\n{}\n", "error: Call to function `file_name` failed: Could not extract file name from ``", " |", "1 | we := file_name(\'\')", " | ^^^^^^^^^").as_str(), status: EXIT_FAILURE, } #[cfg(not(windows))] test! { name: broken_directory_function, justfile: r#" we := parent_directory('') foo: /bin/echo '{{we}}' "#, stdout: "", stderr: format!("{} {}\n{}\n{}\n{}\n", "error: Call to function `parent_directory` failed:", "Could not extract parent directory from ``", " |", "1 | we := parent_directory(\'\')", " | ^^^^^^^^^^^^^^^^").as_str(), status: EXIT_FAILURE, } #[cfg(not(windows))] test! { name: broken_directory_function2, justfile: r#" we := parent_directory('/') foo: /bin/echo '{{we}}' "#, stdout: "", stderr: format!("{} {}\n{}\n{}\n{}\n", "error: Call to function `parent_directory` failed:", "Could not extract parent directory from `/`", " |", "1 | we := parent_directory(\'/\')", " | ^^^^^^^^^^^^^^^^").as_str(), status: EXIT_FAILURE, } #[cfg(windows)] test! { name: env_var_functions, justfile: r#" p := env_var('USERNAME') b := env_var_or_default('ZADDY', 'HTAP') x := env_var_or_default('XYZ', 'ABC') foo: /bin/echo '{{p}}' '{{b}}' '{{x}}' "#, stdout: format!("{} HTAP ABC\n", env::var("USERNAME").unwrap()).as_str(), stderr: format!("/bin/echo '{}' 'HTAP' 'ABC'\n", env::var("USERNAME").unwrap()).as_str(), } test! { name: env_var_failure, justfile: "a:\n echo {{env_var('ZADDY')}}", args: ("a"), stdout: "", stderr: "error: Call to function `env_var` failed: environment variable `ZADDY` not present | 2 | echo {{env_var('ZADDY')}} | ^^^^^^^ ", status: EXIT_FAILURE, } test! { name: test_just_executable_function, justfile: " a: @printf 'Executable path is: %s\\n' '{{ just_executable() }}' ", args: ("a"), stdout: format!("Executable path is: {}\n", executable_path("just").to_str().unwrap()).as_str(), stderr: "", status: EXIT_SUCCESS, } test! { name: infallable_command, justfile: r#" infallable: -exit 101 "#, stderr: "exit 101\n", status: EXIT_SUCCESS, } test! { name: infallable_with_failing, justfile: r#" infallable: -exit 101 exit 202 "#, stderr: r#"exit 101 exit 202 error: Recipe `infallable` failed on line 3 with exit code 202 "#, status: 202, } test! { name: quiet_recipe, justfile: r#" @quiet: # a # b @echo c "#, stdout: "c\n", stderr: "echo c\n", } test! { name: quiet_shebang_recipe, justfile: r#" @quiet: #!/bin/sh echo hello "#, stdout: "hello\n", stderr: "#!/bin/sh\necho hello\n", } test! { name: shebang_line_numbers, justfile: r#" quiet: #!/usr/bin/env cat a b c "#, stdout: " #!/usr/bin/env cat a b c ", } test! { name: complex_dependencies, justfile: r#" a: b b: c: b a "#, args: ("b"), stdout: "", } 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! { name: unknown_function_in_assignment, justfile: r#"foo := foo() + "hello" bar:"#, args: ("bar"), stdout: "", stderr: r#"error: Call to unknown function `foo` | 1 | foo := foo() + "hello" | ^^^ "#, status: EXIT_FAILURE, } test! { name: dependency_takes_arguments_exact, justfile: " a FOO: b: a ", args: ("b"), stdout: "", stderr: "error: Dependency `a` got 0 arguments but takes 1 argument | 2 | b: a | ^ ", status: EXIT_FAILURE, } test! { name: dependency_takes_arguments_at_least, justfile: " a FOO LUZ='hello': b: a ", args: ("b"), stdout: "", stderr: "error: Dependency `a` got 0 arguments but takes at least 1 argument | 2 | b: a | ^ ", status: EXIT_FAILURE, } test! { name: dependency_takes_arguments_at_most, justfile: " a FOO LUZ='hello': b: (a '0' '1' '2') ", args: ("b"), stdout: "", stderr: "error: Dependency `a` got 3 arguments but takes at most 2 arguments | 2 | b: (a '0' '1' '2') | ^ ", status: EXIT_FAILURE, } 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! { 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! { 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! { name: unexpected_token_in_dependency_position, justfile: "foo: 'bar'", args: ("foo"), stdout: "", stderr: "error: Expected comment, end of file, end of line, \ identifier, or '(', but found string | 1 | foo: 'bar' | ^^^^^ ", status: EXIT_FAILURE, } test! { name: unexpected_token_after_name, justfile: "foo 'bar'", args: ("foo"), stdout: "", stderr: "error: Expected '*', ':', '$', identifier, or '+', but found string | 1 | foo 'bar' | ^^^^^ ", status: EXIT_FAILURE, } test! { name: self_dependency, justfile: "a: a", args: ("a"), stdout: "", stderr: "error: Recipe `a` depends on itself | 1 | a: a | ^ ", status: EXIT_FAILURE, } 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! { 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! { 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! { name: plus_variadic_recipe, justfile: " a x y +z: echo {{x}} {{y}} {{z}} ", args: ("a", "0", "1", "2", "3", " 4 "), stdout: "0 1 2 3 4\n", stderr: "echo 0 1 2 3 4 \n", } test! { name: plus_variadic_ignore_default, justfile: " a x y +z='HELLO': echo {{x}} {{y}} {{z}} ", args: ("a", "0", "1", "2", "3", " 4 "), stdout: "0 1 2 3 4\n", stderr: "echo 0 1 2 3 4 \n", } test! { name: plus_variadic_use_default, justfile: " a x y +z='HELLO': echo {{x}} {{y}} {{z}} ", args: ("a", "0", "1"), stdout: "0 1 HELLO\n", stderr: "echo 0 1 HELLO\n", } test! { name: plus_variadic_too_few, justfile: " a x y +z: echo {{x}} {{y}} {{z}} ", args: ("a", "0", "1"), stdout: "", stderr: "error: Recipe `a` got 2 arguments but takes at least 3\nusage:\n just a x y +z\n", status: EXIT_FAILURE, } test! { name: star_variadic_recipe, justfile: " a x y *z: echo {{x}} {{y}} {{z}} ", args: ("a", "0", "1", "2", "3", " 4 "), stdout: "0 1 2 3 4\n", stderr: "echo 0 1 2 3 4 \n", } test! { name: star_variadic_none, justfile: " a x y *z: echo {{x}} {{y}} {{z}} ", args: ("a", "0", "1"), stdout: "0 1\n", stderr: "echo 0 1 \n", } test! { name: star_variadic_ignore_default, justfile: " a x y *z='HELLO': echo {{x}} {{y}} {{z}} ", args: ("a", "0", "1", "2", "3", " 4 "), stdout: "0 1 2 3 4\n", stderr: "echo 0 1 2 3 4 \n", } test! { name: star_variadic_use_default, justfile: " a x y *z='HELLO': echo {{x}} {{y}} {{z}} ", args: ("a", "0", "1"), stdout: "0 1 HELLO\n", stderr: "echo 0 1 HELLO\n", } test! { name: star_then_plus_variadic, justfile: " foo *a +b: echo {{a}} {{b}} ", stdout: "", stderr: "error: Expected \':\' or \'=\', but found \'+\' | 1 | foo *a +b: | ^ ", status: EXIT_FAILURE, } test! { name: plus_then_star_variadic, justfile: " foo +a *b: echo {{a}} {{b}} ", stdout: "", stderr: "error: Expected \':\' or \'=\', but found \'*\' | 1 | foo +a *b: | ^ ", status: EXIT_FAILURE, } test! { name: argument_grouping, justfile: " FOO A B='blarg': echo foo: {{A}} {{B}} BAR X: echo bar: {{X}} BAZ +Z: echo baz: {{Z}} ", 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", } test! { name: missing_second_dependency, justfile: " x: a: x y ", stdout: "", stderr: "error: Recipe `a` has unknown dependency `y` | 3 | a: x y | ^ ", status: EXIT_FAILURE, } test! { name: list_colors, justfile: " # comment a B C +D='hello': echo {{B}} {{C}} {{D}} ", args: ("--color", "always", "--list"), stdout: " Available recipes: a \ \u{1b}[36mB\u{1b}[0m \u{1b}[36mC\u{1b}[0m \u{1b}[35m+\ \u{1b}[0m\u{1b}[36mD\u{1b}[0m=\u{1b}[32m'hello'\u{1b}[0m \ \u{1b}[34m#\u{1b}[0m \u{1b}[34mcomment\u{1b}[0m ", } test! { name: run_colors, justfile: " # comment a: echo hi ", args: ("--color", "always", "--highlight", "--verbose"), stdout: "hi\n", stderr: "\u{1b}[1;36m===> Running recipe `a`...\u{1b}[0m\n\u{1b}[1mecho hi\u{1b}[0m\n", } test! { name: no_highlight, justfile: " # comment a: echo hi ", args: ("--color", "always", "--highlight", "--no-highlight", "--verbose"), stdout: "hi\n", stderr: "\u{1b}[1;36m===> Running recipe `a`...\u{1b}[0m\necho hi\n", } test! { name: trailing_flags, justfile: " echo A B C: echo {{A}} {{B}} {{C}} ", args: ("echo", "--some", "--awesome", "--flags"), stdout: "--some --awesome --flags\n", stderr: "echo --some --awesome --flags\n", } test! { name: comment_before_variable, justfile: " # A:='1' echo: echo {{A}} ", args: ("echo"), stdout: "1\n", stderr: "echo 1\n", } test! { name: dotenv_variable_in_recipe, justfile: " # echo: echo $DOTENV_KEY ", stdout: "dotenv-value\n", stderr: "echo $DOTENV_KEY\n", } test! { name: dotenv_variable_in_backtick, justfile: " # X:=`echo $DOTENV_KEY` echo: echo {{X}} ", stdout: "dotenv-value\n", stderr: "echo dotenv-value\n", } test! { name: dotenv_variable_in_function_in_recipe, justfile: " # echo: echo {{env_var_or_default('DOTENV_KEY', 'foo')}} echo {{env_var('DOTENV_KEY')}} ", stdout: "dotenv-value\ndotenv-value\n", stderr: "echo dotenv-value\necho dotenv-value\n", } test! { name: dotenv_variable_in_function_in_backtick, justfile: " # X:=env_var_or_default('DOTENV_KEY', 'foo') Y:=env_var('DOTENV_KEY') echo: echo {{X}} echo {{Y}} ", stdout: "dotenv-value\ndotenv-value\n", stderr: "echo dotenv-value\necho dotenv-value\n", } test! { name: no_dotenv, justfile: " # X:=env_var_or_default('DOTENV_KEY', 'DEFAULT') echo: echo {{X}} ", args: ("--no-dotenv"), stdout: "DEFAULT\n", stderr: "echo DEFAULT\n", } test! { name: dotenv_env_var_override, justfile: " # echo: echo $DOTENV_KEY ", env: {"DOTENV_KEY": "not-the-dotenv-value",}, stdout: "not-the-dotenv-value\n", stderr: "echo $DOTENV_KEY\n", } test! { name: invalid_escape_sequence_message, justfile: r#" X := "\'" "#, stdout: "", stderr: r#"error: `\'` is not a valid escape sequence | 1 | X := "\'" | ^^^^ "#, status: EXIT_FAILURE, } test! { name: unknown_variable_in_default, justfile: " foo x=bar: ", stdout: "", stderr: r#"error: Variable `bar` not defined | 1 | foo x=bar: | ^^^ "#, status: EXIT_FAILURE, } test! { name: unknown_function_in_default, justfile: " foo x=bar(): ", stdout: "", stderr: r#"error: Call to unknown function `bar` | 1 | foo x=bar(): | ^^^ "#, status: EXIT_FAILURE, } test! { name: default_string, justfile: " foo x='bar': echo {{x}} ", stdout: "bar\n", stderr: "echo bar\n", } test! { name: default_concatination, justfile: " foo x=(`echo foo` + 'bar'): echo {{x}} ", stdout: "foobar\n", stderr: "echo foobar\n", } test! { name: default_backtick, justfile: " foo x=`echo foo`: echo {{x}} ", stdout: "foo\n", stderr: "echo foo\n", } test! { name: default_variable, justfile: " y := 'foo' foo x=y: echo {{x}} ", stdout: "foo\n", stderr: "echo foo\n", } test! { name: test_os_arch_functions_in_default, justfile: r#" foo a=arch() o=os() f=os_family(): echo {{a}} {{o}} {{f}} "#, stdout: format!("{} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(), stderr: format!("echo {} {} {}\n", target::arch(), target::os(), target::os_family()).as_str(), } test! { name: unterminated_interpolation_eol, justfile: " foo: echo {{ ", stderr: r#" error: Unterminated interpolation | 2 | echo {{ | ^^ "#, status: EXIT_FAILURE, } test! { name: unterminated_interpolation_eof, justfile: " foo: echo {{ ", stderr: r#" error: Unterminated interpolation | 2 | echo {{ | ^^ "#, status: EXIT_FAILURE, } test! { name: unknown_start_of_token, justfile: " assembly_source_files = %(wildcard src/arch/$(arch)/*.s) ", stderr: r#" error: Unknown start of token: | 1 | assembly_source_files = %(wildcard src/arch/$(arch)/*.s) | ^ "#, status: EXIT_FAILURE, } test! { name: backtick_variable_cat, justfile: " stdin := `cat` default: echo {{stdin}} ", stdin: "STDIN", stdout: "STDIN\n", stderr: "echo STDIN\n", } test! { name: backtick_default_cat_stdin, justfile: " default stdin = `cat`: echo {{stdin}} ", stdin: "STDIN", stdout: "STDIN\n", stderr: "echo STDIN\n", } test! { name: backtick_default_cat_justfile, justfile: " default stdin = `cat justfile`: echo '{{stdin}}' ", stdout: " default stdin = `cat justfile`: echo {{stdin}} ", stderr: " echo 'default stdin = `cat justfile`: echo '{{stdin}}'' ", } test! { name: backtick_variable_read_single, justfile: " password := `read PW && echo $PW` default: echo {{password}} ", stdin: "foobar\n", stdout: "foobar\n", stderr: "echo foobar\n", } test! { name: backtick_variable_read_multiple, justfile: " a := `read A && echo $A` b := `read B && echo $B` default: echo {{a}} echo {{b}} ", stdin: "foo\nbar\n", stdout: "foo\nbar\n", stderr: "echo foo\necho bar\n", } test! { name: backtick_default_read_multiple, justfile: " default a=`read A && echo $A` b=`read B && echo $B`: echo {{a}} echo {{b}} ", stdin: "foo\nbar\n", stdout: "foo\nbar\n", stderr: "echo foo\necho bar\n", } test! { name: equals_deprecated_assignment, justfile: " foo = 'bar' default: echo {{foo}} ", stderr: " error: `=` in assignments, exports, and aliases has been phased out on favor of `:=` Please see this issue for more details: https://github.com/casey/just/issues/379 | 1 | foo = 'bar' | ^ ", status: EXIT_FAILURE, } test! { name: equals_deprecated_export, justfile: " export FOO = 'bar' default: echo $FOO ", stderr: " error: `=` in assignments, exports, and aliases has been phased out on favor of `:=` Please see this issue for more details: https://github.com/casey/just/issues/379 | 1 | export FOO = 'bar' | ^ ", status: EXIT_FAILURE, } test! { name: equals_deprecated_alias, justfile: " alias foo = default default: echo default ", args: ("foo"), stderr: " error: `=` in assignments, exports, and aliases has been phased out on favor of `:=` Please see this issue for more details: https://github.com/casey/just/issues/379 | 1 | alias foo = default | ^ ", status: EXIT_FAILURE, } test! { name: dependency_argument_string, justfile: " release: (build 'foo') (build 'bar') build target: echo 'Building {{target}}...' ", args: (), stdout: "Building foo...\nBuilding bar...\n", stderr: "echo 'Building foo...'\necho 'Building bar...'\n", shell: false, } test! { name: dependency_argument_parameter, justfile: " default: (release '1.0') release version: (build 'foo' version) (build 'bar' version) build target version: echo 'Building {{target}}@{{version}}...' ", args: (), stdout: "Building foo@1.0...\nBuilding bar@1.0...\n", stderr: "echo 'Building foo@1.0...'\necho 'Building bar@1.0...'\n", shell: false, } test! { name: dependency_argument_function, justfile: " foo: (bar env_var_or_default('x', 'y')) bar arg: echo {{arg}} ", args: (), stdout: "y\n", stderr: "echo y\n", shell: false, } test! { name: dependency_argument_backtick, justfile: " export X := 'X' foo: (bar `echo $X`) bar arg: echo {{arg}} echo $X ", args: (), stdout: "X\nX\n", stderr: "echo X\necho $X\n", shell: false, } test! { name: dependency_argument_assignment, justfile: " v := '1.0' default: (release v) release version: echo Release {{version}}... ", args: (), stdout: "Release 1.0...\n", stderr: "echo Release 1.0...\n", shell: false, } test! { name: dependency_argument_plus_variadic, justfile: " foo: (bar 'A' 'B' 'C') bar +args: echo {{args}} ", args: (), stdout: "A B C\n", stderr: "echo A B C\n", shell: false, } test! { name: duplicate_dependency_no_args, justfile: " foo: bar bar bar bar bar: echo BAR ", args: (), stdout: "BAR\n", stderr: "echo BAR\n", shell: false, } test! { name: duplicate_dependency_argument, justfile: " foo: (bar 'BAR') (bar `echo BAR`) bar bar: echo {{bar}} ", args: (), stdout: "BAR\n", stderr: "echo BAR\n", shell: false, } test! { name: parameter_cross_reference_error, justfile: " foo: bar a b=a: ", args: (), stdout: "", stderr: " error: Variable `a` not defined | 3 | bar a b=a: | ^ ", status: EXIT_FAILURE, shell: false, } #[cfg(windows)] test! { name: pwsh_invocation_directory, justfile: r#" set shell := ["pwsh", "-NoProfile", "-c"] pwd: @Test-Path {{invocation_directory()}} > result.txt "#, args: (), stdout: "", stderr: "", status: EXIT_SUCCESS, shell: false, } test! { name: variables, justfile: " z := 'a' a := 'z' ", args: ("--variables"), stdout: "a z\n", stderr: "", shell: false, } test! { name: interpolation_evaluation_ignore_quiet, justfile: r#" foo: {{"@echo foo 2>/dev/null"}} "#, args: (), stdout: "", stderr: " @echo foo 2>/dev/null error: Recipe `foo` failed on line 2 with exit code 127 ", status: 127, shell: false, } test! { name: interpolation_evaluation_ignore_quiet_continuation, justfile: r#" foo: {{""}}\ @echo foo 2>/dev/null "#, args: (), stdout: "", stderr: " @echo foo 2>/dev/null error: Recipe `foo` failed on line 3 with exit code 127 ", status: 127, shell: false, } test! { name: brace_escape, justfile: " foo: echo '{{{{' ", stdout: "{{\n", stderr: " echo '{{' ", } test! { name: brace_escape_extra, justfile: " foo: echo '{{{{{' ", stdout: "{{{\n", stderr: " echo '{{{' ", } test! { name: multi_line_string_in_interpolation, justfile: " foo: echo {{'a echo b echo c'}}z echo baz ", stdout: "a\nb\ncz\nbaz\n", stderr: "echo a\n echo b\n echo cz\necho baz\n", } #[cfg(windows)] test! { name: windows_interpreter_path_no_base, justfile: r#" foo: #!powershell exit 0 "#, args: (), }