f0404d434c
Integration tests run with bash, dash, and whatever's installed as `sh`, to ensure compatibility with a wide range of systems. This commit changed the way that dash escapes special characters, which broke the integration tests: https://git.kernel.org/pub/scm/utils/dash/dash.git/commit/?id=6900ff60ef7347a8c1445853a8f4689808e0976e This commit modifies our tests to be compatible with dash before and after the changes, and should fix the Travis build.
1837 lines
35 KiB
Rust
1837 lines
35 KiB
Rust
extern crate brev;
|
|
extern crate executable_path;
|
|
extern crate libc;
|
|
extern crate target;
|
|
extern crate tempdir;
|
|
|
|
use executable_path::executable_path;
|
|
use libc::{EXIT_FAILURE, EXIT_SUCCESS};
|
|
use std::env;
|
|
use std::process;
|
|
use std::str;
|
|
use tempdir::TempDir;
|
|
|
|
/// 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:expr,
|
|
stderr: $stderr:expr,
|
|
status: $status:expr,
|
|
) => {
|
|
mod $name {
|
|
use super::*;
|
|
|
|
// 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(
|
|
shell: &str,
|
|
justfile: &str,
|
|
args: &[&str],
|
|
expected_stdout: &str,
|
|
expected_stderr: &str,
|
|
expected_status: i32,
|
|
) {
|
|
let tmp = TempDir::new("just-integration")
|
|
.unwrap_or_else(
|
|
|err| panic!("integration test: failed to create temporary directory: {}", err));
|
|
|
|
let mut justfile_path = tmp.path().to_path_buf();
|
|
justfile_path.push("justfile");
|
|
brev::dump(justfile_path, justfile);
|
|
|
|
let mut dotenv_path = tmp.path().to_path_buf();
|
|
dotenv_path.push(".env");
|
|
brev::dump(dotenv_path, "DOTENV_KEY=dotenv-value");
|
|
|
|
let output = process::Command::new(&executable_path("just"))
|
|
.current_dir(tmp.path())
|
|
.args(&["--shell", shell])
|
|
.args(args)
|
|
.output()
|
|
.expect("just invocation failed");
|
|
|
|
let mut failure = false;
|
|
|
|
let status = output.status.code().unwrap();
|
|
if status != expected_status {
|
|
println!("bad status: {} != {}", status, expected_status);
|
|
failure = true;
|
|
}
|
|
|
|
let stdout = str::from_utf8(&output.stdout).unwrap();
|
|
if stdout != expected_stdout {
|
|
println!("bad stdout:\ngot:\n{}\n\nexpected:\n{}", stdout, expected_stdout);
|
|
failure = true;
|
|
}
|
|
|
|
let stderr = str::from_utf8(&output.stderr).unwrap();
|
|
if stderr != expected_stderr {
|
|
println!("bad stderr:\ngot:\n{}\n\nexpected:\n{}", stderr, expected_stderr);
|
|
failure = true;
|
|
}
|
|
|
|
if failure {
|
|
panic!("test failed");
|
|
}
|
|
}
|
|
|
|
integration_test! {
|
|
name: default,
|
|
justfile: "default:\n echo hello\nother: \n echo bar",
|
|
args: (),
|
|
stdout: "hello\n",
|
|
stderr: "echo hello\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: quiet,
|
|
justfile: "default:\n @echo hello",
|
|
args: (),
|
|
stdout: "hello\n",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: verbose,
|
|
justfile: "default:\n @echo hello",
|
|
args: ("--verbose"),
|
|
stdout: "hello\n",
|
|
stderr: "===> Running recipe `default`...\necho hello\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: summary,
|
|
justfile: "b: a
|
|
a:
|
|
d: c
|
|
c: b
|
|
_z: _y
|
|
_y:
|
|
",
|
|
args: ("--summary"),
|
|
stdout: "a b c d\n",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: select,
|
|
justfile: "b:
|
|
@echo b
|
|
a:
|
|
@echo a
|
|
d:
|
|
@echo d
|
|
c:
|
|
@echo c",
|
|
args: ("d", "c"),
|
|
stdout: "d\nc\n",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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}}
|
|
"#,
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: status_passthrough,
|
|
justfile: "
|
|
|
|
hello:
|
|
|
|
recipe:
|
|
@exit 100",
|
|
args: ("recipe"),
|
|
stdout: "",
|
|
stderr: "error: Recipe `recipe` failed on line 6 with exit code 100\n",
|
|
status: 100,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
integration_test! {
|
|
name: backtick_code_interpolation_tab,
|
|
justfile: "
|
|
backtick-fail:
|
|
\techo {{`exit 1`}}
|
|
",
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: Backtick failed with exit code 1
|
|
|
|
|
3 | echo {{`exit 1`}}
|
|
| ^^^^^^^^
|
|
",
|
|
status: 1,
|
|
}
|
|
|
|
integration_test! {
|
|
name: backtick_code_interpolation_tabs,
|
|
justfile: "
|
|
backtick-fail:
|
|
\techo {{\t`exit 1`}}
|
|
",
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: Backtick failed with exit code 1
|
|
|
|
|
3 | echo {{ `exit 1`}}
|
|
| ^^^^^^^^
|
|
",
|
|
status: 1,
|
|
}
|
|
|
|
integration_test! {
|
|
name: backtick_code_interpolation_inner_tab,
|
|
justfile: "
|
|
backtick-fail:
|
|
\techo {{\t`exit\t\t1`}}
|
|
",
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: Backtick failed with exit code 1
|
|
|
|
|
3 | echo {{ `exit 1`}}
|
|
| ^^^^^^^^^^^^^^^
|
|
",
|
|
status: 1,
|
|
}
|
|
|
|
integration_test! {
|
|
name: backtick_code_interpolation_leading_emoji,
|
|
justfile: "
|
|
backtick-fail:
|
|
\techo 😬{{`exit 1`}}
|
|
",
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: Backtick failed with exit code 1
|
|
|
|
|
3 | echo 😬{{`exit 1`}}
|
|
| ^^^^^^^^
|
|
",
|
|
status: 1,
|
|
}
|
|
|
|
integration_test! {
|
|
name: backtick_code_interpolation_unicode_hell,
|
|
justfile: "
|
|
backtick-fail:
|
|
\techo \t\t\t😬鎌鼬{{\t\t`exit 1 # \t\t\tabc`}}\t\t\t😬鎌鼬
|
|
",
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: Backtick failed with exit code 1
|
|
|
|
|
3 | echo 😬鎌鼬{{ `exit 1 # abc`}} 😬鎌鼬
|
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
",
|
|
status: 1,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
integration_test! {
|
|
name: shebang_backtick_failure,
|
|
justfile: "foo:
|
|
#!/bin/sh
|
|
echo hello
|
|
echo {{`exit 123`}}",
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: Backtick failed with exit code 123
|
|
|
|
|
4 | echo {{`exit 123`}}
|
|
| ^^^^^^^^^^
|
|
",
|
|
status: 123,
|
|
}
|
|
|
|
integration_test! {
|
|
name: command_backtick_failure,
|
|
justfile: "foo:
|
|
echo hello
|
|
echo {{`exit 123`}}",
|
|
args: (),
|
|
stdout: "hello\n",
|
|
stderr: "echo hello\nerror: Backtick failed with exit code 123
|
|
|
|
|
3 | echo {{`exit 123`}}
|
|
| ^^^^^^^^^^
|
|
",
|
|
status: 123,
|
|
}
|
|
|
|
integration_test! {
|
|
name: assignment_backtick_failure,
|
|
justfile: "foo:
|
|
echo hello
|
|
echo {{`exit 111`}}
|
|
a = `exit 222`",
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: Backtick failed with exit code 222
|
|
|
|
|
4 | a = `exit 222`
|
|
| ^^^^^^^^^^
|
|
",
|
|
status: 222,
|
|
}
|
|
|
|
integration_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"),
|
|
stdout: "",
|
|
stderr: "error: Variables `baz` and `foo` overridden on the command line but not present \
|
|
in justfile\n",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: unknown_override_args,
|
|
justfile: "foo:
|
|
echo hello
|
|
echo {{`exit 111`}}
|
|
a = `exit 222`",
|
|
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,
|
|
}
|
|
|
|
integration_test! {
|
|
name: unknown_override_arg,
|
|
justfile: "foo:
|
|
echo hello
|
|
echo {{`exit 111`}}
|
|
a = `exit 222`",
|
|
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,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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`
|
|
",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: evaluate,
|
|
justfile: r#"
|
|
foo = "a\t"
|
|
hello = "c"
|
|
bar = "b\t"
|
|
ab = foo + bar + hello
|
|
|
|
wut:
|
|
touch /this/is/not/a/file
|
|
"#,
|
|
args: ("--evaluate"),
|
|
stdout: r#"ab = "a b c"
|
|
bar = "b "
|
|
foo = "a "
|
|
hello = "c"
|
|
"#,
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: export_success,
|
|
justfile: r#"
|
|
export FOO = "a"
|
|
baz = "c"
|
|
export BAR = "b"
|
|
export ABC = FOO + BAR + baz
|
|
|
|
wut:
|
|
echo $FOO $BAR $ABC
|
|
"#,
|
|
args: (),
|
|
stdout: "a b abc\n",
|
|
stderr: "echo $FOO $BAR $ABC\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: export_override,
|
|
justfile: r#"
|
|
export FOO = "a"
|
|
baz = "c"
|
|
export BAR = "b"
|
|
export ABC = FOO + "-" + BAR + "-" + baz
|
|
|
|
wut:
|
|
echo $FOO $BAR $ABC
|
|
"#,
|
|
args: ("--set", "BAR", "bye", "FOO=hello"),
|
|
stdout: "hello bye hello-bye-c\n",
|
|
stderr: "echo $FOO $BAR $ABC\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: outer_shebang,
|
|
justfile: r#"#!/lol/wut
|
|
export FOO = "a"
|
|
baz = "c"
|
|
export BAR = "b"
|
|
export ABC = FOO + BAR + baz
|
|
|
|
wut:
|
|
#!/bin/sh
|
|
echo $FOO $BAR $ABC
|
|
"#,
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: `#!` is reserved syntax outside of recipes
|
|
|
|
|
1 | #!/lol/wut
|
|
| ^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
|
|
integration_test! {
|
|
name: export_shebang,
|
|
justfile: r#"
|
|
export FOO = "a"
|
|
baz = "c"
|
|
export BAR = "b"
|
|
export ABC = FOO + BAR + baz
|
|
|
|
wut:
|
|
#!/bin/sh
|
|
echo $FOO $BAR $ABC
|
|
"#,
|
|
args: (),
|
|
stdout: "a b abc\n",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: export_recipe_backtick,
|
|
justfile: r#"
|
|
export EXPORTED_VARIABLE = "A-IS-A"
|
|
|
|
recipe:
|
|
echo {{`echo recipe $EXPORTED_VARIABLE`}}
|
|
"#,
|
|
args: (),
|
|
stdout: "recipe A-IS-A\n",
|
|
stderr: "echo recipe A-IS-A\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: raw_string,
|
|
justfile: r#"
|
|
export EXPORTED_VARIABLE = '\z'
|
|
|
|
recipe:
|
|
printf "$EXPORTED_VARIABLE"
|
|
"#,
|
|
args: (),
|
|
stdout: "\\z",
|
|
stderr: "printf \"$EXPORTED_VARIABLE\"\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: line_error_spacing,
|
|
justfile: r#"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
???
|
|
"#,
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: Unknown start of token:
|
|
|
|
|
10 | ???
|
|
| ^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: quiet_flag_no_stdout,
|
|
justfile: r#"
|
|
default:
|
|
@echo hello
|
|
"#,
|
|
args: ("--quiet"),
|
|
stdout: "",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: quiet_flag_no_stderr,
|
|
justfile: r#"
|
|
default:
|
|
@echo hello 1>&2
|
|
"#,
|
|
args: ("--quiet"),
|
|
stdout: "",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: quiet_flag_no_command_echoing,
|
|
justfile: r#"
|
|
default:
|
|
exit
|
|
"#,
|
|
args: ("--quiet"),
|
|
stdout: "",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: quiet_flag_no_error_messages,
|
|
justfile: r#"
|
|
default:
|
|
exit 100
|
|
"#,
|
|
args: ("--quiet"),
|
|
stdout: "",
|
|
stderr: "",
|
|
status: 100,
|
|
}
|
|
|
|
integration_test! {
|
|
name: quiet_flag_no_assignment_backtick_stderr,
|
|
justfile: r#"
|
|
a = `echo hello 1>&2`
|
|
default:
|
|
exit 100
|
|
"#,
|
|
args: ("--quiet"),
|
|
stdout: "",
|
|
stderr: "",
|
|
status: 100,
|
|
}
|
|
|
|
integration_test! {
|
|
name: quiet_flag_no_interpolation_backtick_stderr,
|
|
justfile: r#"
|
|
default:
|
|
echo `echo hello 1>&2`
|
|
exit 100
|
|
"#,
|
|
args: ("--quiet"),
|
|
stdout: "",
|
|
stderr: "",
|
|
status: 100,
|
|
}
|
|
|
|
integration_test! {
|
|
name: argument_single,
|
|
justfile: "
|
|
foo A:
|
|
echo {{A}}
|
|
",
|
|
args: ("foo", "ARGUMENT"),
|
|
stdout: "ARGUMENT\n",
|
|
stderr: "echo ARGUMENT\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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,
|
|
}
|
|
|
|
integration_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\n",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_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,
|
|
}
|
|
|
|
integration_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\n",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: unknown_recipe,
|
|
justfile: "hello:",
|
|
args: ("foo"),
|
|
stdout: "",
|
|
stderr: "error: Justfile does not contain recipe `foo`.\n",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
integration_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 3 with exit code 100\u{1b}[0m\n",
|
|
status: 100,
|
|
}
|
|
|
|
integration_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
|
|
",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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\t' c='C' # this does a thing
|
|
",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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\t' c='C' # this does a thing
|
|
",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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\t' c='C' # this does a thing
|
|
this-recipe-is-very-very-very-important Z='\t z' # something else
|
|
x a b='B\t' c='C' # this does another thing
|
|
",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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,
|
|
}
|
|
|
|
integration_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,
|
|
}
|
|
|
|
integration_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,
|
|
}
|
|
|
|
integration_test! {
|
|
name: line_continuation_with_space,
|
|
justfile: r#"
|
|
foo:
|
|
echo a\
|
|
b \
|
|
c
|
|
"#,
|
|
args: (),
|
|
stdout: "a b c\n",
|
|
stderr: "echo a b c\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: line_continuation_with_quoted_space,
|
|
justfile: r#"
|
|
foo:
|
|
echo 'a\
|
|
b \
|
|
c'
|
|
"#,
|
|
args: (),
|
|
stdout: "a b c\n",
|
|
stderr: "echo 'a b c'\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: line_continuation_no_space,
|
|
justfile: r#"
|
|
foo:
|
|
echo a\
|
|
b\
|
|
c
|
|
"#,
|
|
args: (),
|
|
stdout: "abc\n",
|
|
stderr: "echo abc\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: test_os_arch_functions_in_interpolation,
|
|
justfile: r#"
|
|
foo:
|
|
echo {{arch()}} {{os()}} {{os_family()}}
|
|
"#,
|
|
args: (),
|
|
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(),
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: test_os_arch_functions_in_expression,
|
|
justfile: r#"
|
|
a = arch()
|
|
o = os()
|
|
f = os_family()
|
|
|
|
foo:
|
|
echo {{a}} {{o}} {{f}}
|
|
"#,
|
|
args: (),
|
|
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(),
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
integration_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}}'
|
|
"#,
|
|
args: (),
|
|
stdout: format!("{} HTAP ABC\n", env::var("USER").unwrap()).as_str(),
|
|
stderr: format!("/bin/echo '{}' 'HTAP' 'ABC'\n", env::var("USER").unwrap()).as_str(),
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
integration_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}}'
|
|
"#,
|
|
args: (),
|
|
stdout: format!("{} HTAP ABC\n", env::var("USERNAME").unwrap()).as_str(),
|
|
stderr: format!("/bin/echo '{}' 'HTAP' 'ABC'\n", env::var("USERNAME").unwrap()).as_str(),
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
|
|
integration_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,
|
|
}
|
|
|
|
integration_test! {
|
|
name: quiet_recipe,
|
|
justfile: r#"
|
|
@quiet:
|
|
# a
|
|
# b
|
|
@echo c
|
|
"#,
|
|
args: (),
|
|
stdout: "c\n",
|
|
stderr: "echo c\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: quiet_shebang_recipe,
|
|
justfile: r#"
|
|
@quiet:
|
|
#!/bin/sh
|
|
echo hello
|
|
"#,
|
|
args: (),
|
|
stdout: "hello\n",
|
|
stderr: "#!/bin/sh\necho hello\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: shebang_line_numbers,
|
|
justfile: r#"
|
|
quiet:
|
|
#!/usr/bin/env cat
|
|
|
|
a
|
|
|
|
b
|
|
|
|
|
|
c
|
|
|
|
|
|
"#,
|
|
args: (),
|
|
stdout: "#!/usr/bin/env cat
|
|
|
|
|
|
|
|
a
|
|
|
|
b
|
|
|
|
|
|
c
|
|
|
|
|
|
",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: complex_dependencies,
|
|
justfile: r#"
|
|
a: b
|
|
b:
|
|
c: b a
|
|
"#,
|
|
args: ("b"),
|
|
stdout: "",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
integration_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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
integration_test! {
|
|
name: unexpected_token_in_dependency_position,
|
|
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,
|
|
}
|
|
|
|
integration_test! {
|
|
name: unexpected_token_after_name,
|
|
justfile: "foo 'bar'",
|
|
args: ("foo"),
|
|
stdout: "",
|
|
stderr: "error: Expected name, '+', ':', or '=', but found raw string
|
|
|
|
|
1 | foo 'bar'
|
|
| ^^^^^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: self_dependency,
|
|
justfile: "a: a",
|
|
args: ("a"),
|
|
stdout: "",
|
|
stderr: "error: Recipe `a` depends on itself
|
|
|
|
|
1 | a: a
|
|
| ^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
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,
|
|
}
|
|
|
|
integration_test! {
|
|
name: invalid_escape_sequence,
|
|
justfile: r#"x = "\q"
|
|
a:"#,
|
|
args: ("a"),
|
|
stdout: "",
|
|
stderr: "error: `\\q` is not a valid escape sequence
|
|
|
|
|
1 | x = \"\\q\"
|
|
| ^^^^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
|
|
integration_test! {
|
|
name: multiline_raw_string,
|
|
justfile: "
|
|
string = 'hello
|
|
whatever'
|
|
|
|
a:
|
|
echo '{{string}}'
|
|
",
|
|
args: ("a"),
|
|
stdout: "hello
|
|
whatever
|
|
",
|
|
stderr: "echo 'hello
|
|
whatever'
|
|
",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: error_line_after_multiline_raw_string,
|
|
justfile: "
|
|
string = 'hello
|
|
|
|
whatever' + 'yo'
|
|
|
|
a:
|
|
echo '{{foo}}'
|
|
",
|
|
args: ("a"),
|
|
stdout: "",
|
|
stderr: "error: Variable `foo` not defined
|
|
|
|
|
7 | echo '{{foo}}'
|
|
| ^^^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: error_column_after_multiline_raw_string,
|
|
justfile: "
|
|
string = 'hello
|
|
|
|
whatever' + bar
|
|
|
|
a:
|
|
echo '{{string}}'
|
|
",
|
|
args: ("a"),
|
|
stdout: "",
|
|
stderr: "error: Variable `bar` not defined
|
|
|
|
|
4 | whatever' + bar
|
|
| ^^^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: multiline_raw_string_in_interpolation,
|
|
justfile: r#"
|
|
a:
|
|
echo '{{"a" + '
|
|
' + "b"}}'
|
|
"#,
|
|
args: ("a"),
|
|
stdout: "a
|
|
b
|
|
",
|
|
stderr: "echo 'a
|
|
b'
|
|
",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: error_line_after_multiline_raw_string_in_interpolation,
|
|
justfile: r#"
|
|
a:
|
|
echo '{{"a" + '
|
|
' + "b"}}'
|
|
|
|
echo {{b}}
|
|
"#,
|
|
args: ("a"),
|
|
stdout: "",
|
|
stderr: "error: Variable `b` not defined
|
|
|
|
|
6 | echo {{b}}
|
|
| ^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: unterminated_raw_string,
|
|
justfile: "
|
|
a b=':
|
|
",
|
|
args: ("a"),
|
|
stdout: "",
|
|
stderr: "error: Unterminated string
|
|
|
|
|
2 | a b=':
|
|
| ^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: unterminated_string,
|
|
justfile: r#"
|
|
a b=":
|
|
"#,
|
|
args: ("a"),
|
|
stdout: "",
|
|
stderr: r#"error: Unterminated string
|
|
|
|
|
2 | a b=":
|
|
| ^
|
|
"#,
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: 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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: 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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: 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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: 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\n",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: missing_second_dependency,
|
|
justfile: "
|
|
x:
|
|
|
|
a: x y
|
|
",
|
|
args: (),
|
|
stdout: "",
|
|
stderr: "error: Recipe `a` has unknown dependency `y`
|
|
|
|
|
4 | a: x y
|
|
| ^
|
|
",
|
|
status: EXIT_FAILURE,
|
|
}
|
|
|
|
integration_test! {
|
|
name: list_colors,
|
|
justfile: "
|
|
# comment
|
|
a B C +D='hello':
|
|
echo {{B}} {{C}} {{D}}
|
|
",
|
|
args: ("--color", "always", "--list"),
|
|
stdout: "Available recipes:\n 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}[32mhello\u{1b}[0m\
|
|
\' \u{1b}[34m#\u{1b}[0m \u{1b}[34mcomment\u{1b}[0m\n",
|
|
stderr: "",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: comment_before_variable,
|
|
justfile: "
|
|
#
|
|
A='1'
|
|
echo:
|
|
echo {{A}}
|
|
",
|
|
args: ("echo"),
|
|
stdout: "1\n",
|
|
stderr: "echo 1\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: dotenv_variable_in_recipe,
|
|
justfile: "
|
|
#
|
|
echo:
|
|
echo $DOTENV_KEY
|
|
",
|
|
args: (),
|
|
stdout: "dotenv-value\n",
|
|
stderr: "echo $DOTENV_KEY\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: dotenv_variable_in_backtick,
|
|
justfile: "
|
|
#
|
|
X=`echo $DOTENV_KEY`
|
|
echo:
|
|
echo {{X}}
|
|
",
|
|
args: (),
|
|
stdout: "dotenv-value\n",
|
|
stderr: "echo dotenv-value\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
integration_test! {
|
|
name: dotenv_variable_in_function_in_recipe,
|
|
justfile: "
|
|
#
|
|
echo:
|
|
echo {{env_var_or_default('DOTENV_KEY', 'foo')}}
|
|
echo {{env_var('DOTENV_KEY')}}
|
|
",
|
|
args: (),
|
|
stdout: "dotenv-value\ndotenv-value\n",
|
|
stderr: "echo dotenv-value\necho dotenv-value\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_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}}
|
|
",
|
|
args: (),
|
|
stdout: "dotenv-value\ndotenv-value\n",
|
|
stderr: "echo dotenv-value\necho dotenv-value\n",
|
|
status: EXIT_SUCCESS,
|
|
}
|
|
|
|
integration_test! {
|
|
name: invalid_escape_sequence_message,
|
|
justfile: r#"
|
|
X = "\'"
|
|
"#,
|
|
args: (),
|
|
stdout: "",
|
|
stderr: r#"error: `\'` is not a valid escape sequence
|
|
|
|
|
2 | X = "\'"
|
|
| ^^^^
|
|
"#,
|
|
status: EXIT_FAILURE,
|
|
}
|