mirror of
https://github.com/casey/just.git
synced 2024-11-26 23:27:26 +03:00
1b0fafea75
This commit adds a `Loader` type, which can be used to load multiple source strings. This was done to support the work on modules, but coincidentally enabled consolidating errors, since now `Config::run` can take a `&Loader`, and in the event of an error, return and `Error` that borrows from loaded strings. Multiple error types have been consolidated, and a bunch of ad-hoc error printing was removed.
2135 lines
37 KiB
Rust
2135 lines
37 KiB
Rust
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: 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: 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: 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: 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: variable_circular_dependency_with_additional_variable,
|
|
justfile: "
|
|
a := ''
|
|
x := y
|
|
y := x
|
|
|
|
a:
|
|
",
|
|
args: ("a"),
|
|
stdout: "",
|
|
stderr: "error: Variable `x` depends on its own value: `x -> y -> x`
|
|
|
|
|
2 | 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: 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: (),
|
|
}
|