Stabilize fallback (#1471)

This commit is contained in:
Casey Rodarmor 2023-01-03 22:31:56 -08:00 committed by GitHub
parent bb5b962c3d
commit 10ad32430b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 123 additions and 113 deletions

View File

@ -92,20 +92,20 @@ impl Subcommand {
arguments: &[String], arguments: &[String],
overrides: &BTreeMap<String, String>, overrides: &BTreeMap<String, String>,
) -> Result<(), Error<'src>> { ) -> Result<(), Error<'src>> {
if config.unstable if matches!(
&& matches!( config.search_config,
config.search_config, SearchConfig::FromInvocationDirectory | SearchConfig::FromSearchDirectory { .. }
SearchConfig::FromInvocationDirectory | SearchConfig::FromSearchDirectory { .. } ) {
) let starting_path = match &config.search_config {
{
let mut path = match &config.search_config {
SearchConfig::FromInvocationDirectory => config.invocation_directory.clone(), SearchConfig::FromInvocationDirectory => config.invocation_directory.clone(),
SearchConfig::FromSearchDirectory { search_directory } => std::env::current_dir() SearchConfig::FromSearchDirectory { search_directory } => {
.unwrap() std::env::current_dir().unwrap().join(search_directory)
.join(search_directory.clone()), }
_ => unreachable!(), _ => unreachable!(),
}; };
let mut path = starting_path.clone();
let mut unknown_recipes_errors = None; let mut unknown_recipes_errors = None;
loop { loop {
@ -116,11 +116,10 @@ impl Subcommand {
}, },
Err(err) => return Err(err.into()), Err(err) => return Err(err.into()),
Ok(search) => { Ok(search) => {
if config.verbosity.loud() && path != config.invocation_directory { if config.verbosity.loud() && path != starting_path {
eprintln!( eprintln!(
"Trying {}", "Trying {}",
config starting_path
.invocation_directory
.strip_prefix(path) .strip_prefix(path)
.unwrap() .unwrap()
.components() .components()

View File

@ -31,7 +31,7 @@ fn allow_duplicate_recipes_with_args() {
set allow-duplicate-recipes set allow-duplicate-recipes
", ",
) )
.args(&["b", "one", "two"]) .args(["b", "one", "two"])
.stdout("bar one two\n") .stdout("bar one two\n")
.stderr("echo bar one two\n") .stderr("echo bar one two\n")
.run(); .run();

View File

@ -3,7 +3,7 @@ use super::*;
#[test] #[test]
fn print_changelog() { fn print_changelog() {
Test::new() Test::new()
.args(&["--changelog"]) .args(["--changelog"])
.stdout(fs::read_to_string("CHANGELOG.md").unwrap()) .stdout(fs::read_to_string("CHANGELOG.md").unwrap())
.run(); .run();
} }

View File

@ -128,7 +128,7 @@ fn invoke_error_function() {
.stderr_regex("error: Chooser `/ -cu fzf` invocation failed: .*") .stderr_regex("error: Chooser `/ -cu fzf` invocation failed: .*")
.status(EXIT_FAILURE) .status(EXIT_FAILURE)
.shell(false) .shell(false)
.args(&["--shell", "/", "--choose"]) .args(["--shell", "/", "--choose"])
.run(); .run();
} }

View File

@ -2,26 +2,14 @@ use super::*;
#[test] #[test]
fn dotenv() { fn dotenv() {
let tmp = temptree! { Test::new()
".env": "KEY=ROOT", .write(".env", "KEY=ROOT")
sub: { .write("sub/.env", "KEY=SUB")
".env": "KEY=SUB", .write("sub/justfile", "default:\n\techo KEY=${KEY:-unset}")
justfile: "default:\n\techo KEY=${KEY:-unset}", .args(["sub/default"])
}, .stdout("KEY=unset\n")
}; .stderr("echo KEY=${KEY:-unset}\n")
.run();
let binary = executable_path("just");
let output = Command::new(binary)
.current_dir(tmp.path())
.arg("sub/default")
.output()
.expect("just invocation failed");
assert_eq!(output.status.code().unwrap(), 0);
let stdout = str::from_utf8(&output.stdout).unwrap();
assert_eq!(stdout, "KEY=unset\n");
} }
test! { test! {
@ -83,7 +71,7 @@ fn path_not_found() {
echo $NAME echo $NAME
", ",
) )
.args(&["--dotenv-path", ".env.prod"]) .args(["--dotenv-path", ".env.prod"])
.stderr(if cfg!(windows) { .stderr(if cfg!(windows) {
"error: Failed to load environment file: The system cannot find the file specified. (os \ "error: Failed to load environment file: The system cannot find the file specified. (os \
error 2)\n" error 2)\n"
@ -108,7 +96,7 @@ fn path_resolves() {
".env": "NAME=bar" ".env": "NAME=bar"
} }
}) })
.args(&["--dotenv-path", "subdir/.env"]) .args(["--dotenv-path", "subdir/.env"])
.stdout("bar\n") .stdout("bar\n")
.status(EXIT_SUCCESS) .status(EXIT_SUCCESS)
.run(); .run();
@ -126,7 +114,7 @@ fn filename_resolves() {
.tree(tree! { .tree(tree! {
".env.special": "NAME=bar" ".env.special": "NAME=bar"
}) })
.args(&["--dotenv-filename", ".env.special"]) .args(["--dotenv-filename", ".env.special"])
.stdout("bar\n") .stdout("bar\n")
.status(EXIT_SUCCESS) .status(EXIT_SUCCESS)
.run(); .run();
@ -146,7 +134,7 @@ fn filename_flag_overwrites_no_load() {
.tree(tree! { .tree(tree! {
".env.special": "NAME=bar" ".env.special": "NAME=bar"
}) })
.args(&["--dotenv-filename", ".env.special"]) .args(["--dotenv-filename", ".env.special"])
.stdout("bar\n") .stdout("bar\n")
.status(EXIT_SUCCESS) .status(EXIT_SUCCESS)
.run(); .run();
@ -168,7 +156,7 @@ fn path_flag_overwrites_no_load() {
".env": "NAME=bar" ".env": "NAME=bar"
} }
}) })
.args(&["--dotenv-path", "subdir/.env"]) .args(["--dotenv-path", "subdir/.env"])
.stdout("bar\n") .stdout("bar\n")
.status(EXIT_SUCCESS) .status(EXIT_SUCCESS)
.run(); .run();

View File

@ -40,7 +40,7 @@ test! {
fn argument_count_mismatch() { fn argument_count_mismatch() {
Test::new() Test::new()
.justfile("foo a b:") .justfile("foo a b:")
.args(&["foo"]) .args(["foo"])
.stderr( .stderr(
" "
error: Recipe `foo` got 0 arguments but takes 2 error: Recipe `foo` got 0 arguments but takes 2

View File

@ -1,5 +1,45 @@
use super::*; use super::*;
#[test]
fn fallback_from_subdir_bugfix() {
Test::new()
.write(
"sub/justfile",
unindent(
"
set fallback
@default:
echo foo
",
),
)
.args(["sub/default"])
.stdout("foo\n")
.run();
}
#[test]
fn fallback_from_subdir_message() {
Test::new()
.justfile("bar:\n echo bar")
.write(
"sub/justfile",
unindent(
"
set fallback
@foo:
echo foo
",
),
)
.args(["sub/bar"])
.stderr(path("Trying ../justfile\necho bar\n"))
.stdout("bar\n")
.run();
}
#[test] #[test]
fn runs_recipe_in_parent_if_not_found_in_current() { fn runs_recipe_in_parent_if_not_found_in_current() {
Test::new() Test::new()
@ -19,7 +59,7 @@ fn runs_recipe_in_parent_if_not_found_in_current() {
echo root echo root
", ",
) )
.args(&["--unstable", "foo"]) .args(["foo"])
.current_dir("bar") .current_dir("bar")
.stderr(format!( .stderr(format!(
" "
@ -51,7 +91,7 @@ fn setting_accepts_value() {
echo root echo root
", ",
) )
.args(&["--unstable", "foo"]) .args(["foo"])
.current_dir("bar") .current_dir("bar")
.stderr(format!( .stderr(format!(
" "
@ -78,7 +118,7 @@ fn print_error_from_parent_if_recipe_not_found_in_current() {
} }
}) })
.justfile("foo:\n echo {{bar}}") .justfile("foo:\n echo {{bar}}")
.args(&["--unstable", "foo"]) .args(["foo"])
.current_dir("bar") .current_dir("bar")
.stderr(format!( .stderr(format!(
" "
@ -95,31 +135,6 @@ fn print_error_from_parent_if_recipe_not_found_in_current() {
} }
#[test] #[test]
fn requires_unstable() {
Test::new()
.tree(tree! {
bar: {
justfile: "
baz:
echo subdir
"
}
})
.justfile(
"
foo:
echo root
",
)
.args(&["foo"])
.current_dir("bar")
.status(EXIT_FAILURE)
.stderr("error: Justfile does not contain recipe `foo`.\n")
.run();
}
#[test]
#[ignore]
fn requires_setting() { fn requires_setting() {
Test::new() Test::new()
.tree(tree! { .tree(tree! {
@ -136,7 +151,7 @@ fn requires_setting() {
echo root echo root
", ",
) )
.args(&["--unstable", "foo"]) .args(["foo"])
.current_dir("bar") .current_dir("bar")
.status(EXIT_FAILURE) .status(EXIT_FAILURE)
.stderr("error: Justfile does not contain recipe `foo`.\n") .stderr("error: Justfile does not contain recipe `foo`.\n")
@ -162,7 +177,7 @@ fn works_with_provided_search_directory() {
echo root echo root
", ",
) )
.args(&["--unstable", "./foo"]) .args(["./foo"])
.stdout("root\n") .stdout("root\n")
.stderr(format!( .stderr(format!(
" "
@ -192,7 +207,7 @@ fn doesnt_work_with_justfile() {
echo root echo root
", ",
) )
.args(&["--unstable", "--justfile", "justfile", "foo"]) .args(["--justfile", "justfile", "foo"])
.current_dir("bar") .current_dir("bar")
.status(EXIT_FAILURE) .status(EXIT_FAILURE)
.stderr("error: Justfile does not contain recipe `foo`.\n") .stderr("error: Justfile does not contain recipe `foo`.\n")
@ -216,14 +231,7 @@ fn doesnt_work_with_justfile_and_working_directory() {
echo root echo root
", ",
) )
.args(&[ .args(["--justfile", "justfile", "--working-directory", ".", "foo"])
"--unstable",
"--justfile",
"justfile",
"--working-directory",
".",
"foo",
])
.current_dir("bar") .current_dir("bar")
.status(EXIT_FAILURE) .status(EXIT_FAILURE)
.stderr("error: Justfile does not contain recipe `foo`.\n") .stderr("error: Justfile does not contain recipe `foo`.\n")
@ -249,7 +257,7 @@ fn prints_correct_error_message_when_recipe_not_found() {
echo root echo root
", ",
) )
.args(&["--unstable", "foo"]) .args(["foo"])
.current_dir("bar") .current_dir("bar")
.status(EXIT_FAILURE) .status(EXIT_FAILURE)
.stderr(format!( .stderr(format!(
@ -289,7 +297,7 @@ fn multiple_levels_of_fallback_work() {
echo root echo root
", ",
) )
.args(&["--unstable", "baz"]) .args(["baz"])
.current_dir("a/b") .current_dir("a/b")
.stdout("root\n") .stdout("root\n")
.stderr(format!( .stderr(format!(
@ -328,7 +336,7 @@ fn stop_fallback_when_fallback_is_false() {
echo root echo root
", ",
) )
.args(&["--unstable", "baz"]) .args(["baz"])
.current_dir("a/b") .current_dir("a/b")
.stderr(format!( .stderr(format!(
" "

View File

@ -105,7 +105,7 @@ fn write_error() {
let test = Test::with_tempdir(tempdir) let test = Test::with_tempdir(tempdir)
.no_justfile() .no_justfile()
.args(&["--fmt", "--unstable"]) .args(["--fmt", "--unstable"])
.status(EXIT_FAILURE) .status(EXIT_FAILURE)
.stderr_regex(if cfg!(windows) { .stderr_regex(if cfg!(windows) {
r"error: Failed to write justfile to `.*`: Access is denied. \(os error 5\)\n" r"error: Failed to write justfile to `.*`: Access is denied. \(os error 5\)\n"
@ -1055,7 +1055,7 @@ test! {
fn exported_parameter() { fn exported_parameter() {
Test::new() Test::new()
.justfile("foo +$f:") .justfile("foo +$f:")
.args(&["--dump"]) .args(["--dump"])
.stdout("foo +$f:\n") .stdout("foo +$f:\n")
.run(); .run();
} }

View File

@ -414,7 +414,7 @@ test! {
fn assert_eval_eq(expression: &str, result: &str) { fn assert_eval_eq(expression: &str, result: &str) {
Test::new() Test::new()
.justfile(format!("x := {}", expression)) .justfile(format!("x := {}", expression))
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout(result) .stdout(result)
.unindent_stdout(false) .unindent_stdout(false)
.run(); .run();
@ -478,7 +478,7 @@ fn join() {
fn join_argument_count_error() { fn join_argument_count_error() {
Test::new() Test::new()
.justfile("x := join('a')") .justfile("x := join('a')")
.args(&["--evaluate"]) .args(["--evaluate"])
.stderr( .stderr(
" "
error: Function `join` called with 1 argument but takes 2 or more error: Function `join` called with 1 argument but takes 2 or more
@ -498,7 +498,7 @@ fn test_path_exists_filepath_exist() {
testfile: "" testfile: ""
}) })
.justfile("x := path_exists('testfile')") .justfile("x := path_exists('testfile')")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("true") .stdout("true")
.run(); .run();
} }
@ -507,7 +507,7 @@ fn test_path_exists_filepath_exist() {
fn test_path_exists_filepath_doesnt_exist() { fn test_path_exists_filepath_doesnt_exist() {
Test::new() Test::new()
.justfile("x := path_exists('testfile')") .justfile("x := path_exists('testfile')")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("false") .stdout("false")
.run(); .run();
} }
@ -516,7 +516,7 @@ fn test_path_exists_filepath_doesnt_exist() {
fn error_errors_with_message() { fn error_errors_with_message() {
Test::new() Test::new()
.justfile("x := error ('Thing Not Supported')") .justfile("x := error ('Thing Not Supported')")
.args(&["--evaluate"]) .args(["--evaluate"])
.status(1) .status(1)
.stderr("error: Call to function `error` failed: Thing Not Supported\n |\n1 | x := error ('Thing Not Supported')\n | ^^^^^\n") .stderr("error: Call to function `error` failed: Thing Not Supported\n |\n1 | x := error ('Thing Not Supported')\n | ^^^^^\n")
.run(); .run();
@ -526,7 +526,7 @@ fn error_errors_with_message() {
fn test_absolute_path_resolves() { fn test_absolute_path_resolves() {
let test_object = Test::new() let test_object = Test::new()
.justfile("path := absolute_path('./test_file')") .justfile("path := absolute_path('./test_file')")
.args(&["--evaluate", "path"]); .args(["--evaluate", "path"]);
let mut tempdir = test_object.tempdir.path().to_owned(); let mut tempdir = test_object.tempdir.path().to_owned();
@ -546,7 +546,7 @@ fn test_absolute_path_resolves() {
fn test_absolute_path_resolves_parent() { fn test_absolute_path_resolves_parent() {
let test_object = Test::new() let test_object = Test::new()
.justfile("path := absolute_path('../test_file')") .justfile("path := absolute_path('../test_file')")
.args(&["--evaluate", "path"]); .args(["--evaluate", "path"]);
let mut tempdir = test_object.tempdir.path().to_owned(); let mut tempdir = test_object.tempdir.path().to_owned();
@ -580,7 +580,7 @@ fn path_exists_subdir() {
}) })
.justfile("x := path_exists('foo')") .justfile("x := path_exists('foo')")
.current_dir("bar") .current_dir("bar")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("true") .stdout("true")
.run(); .run();
} }
@ -589,7 +589,7 @@ fn path_exists_subdir() {
fn uuid() { fn uuid() {
Test::new() Test::new()
.justfile("x := uuid()") .justfile("x := uuid()")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout_regex("........-....-....-....-............") .stdout_regex("........-....-....-....-............")
.run(); .run();
} }
@ -598,7 +598,7 @@ fn uuid() {
fn sha256() { fn sha256() {
Test::new() Test::new()
.justfile("x := sha256('5943ee37-0000-1000-8000-010203040506')") .justfile("x := sha256('5943ee37-0000-1000-8000-010203040506')")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("2330d7f5eb94a820b54fed59a8eced236f80b633a504289c030b6a65aef58871") .stdout("2330d7f5eb94a820b54fed59a8eced236f80b633a504289c030b6a65aef58871")
.run(); .run();
} }
@ -613,7 +613,7 @@ fn sha256_file() {
} }
}) })
.current_dir("sub") .current_dir("sub")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("177b3d79aaafb53a7a4d7aaba99a82f27c73370e8cb0295571aade1e4fea1cd2") .stdout("177b3d79aaafb53a7a4d7aaba99a82f27c73370e8cb0295571aade1e4fea1cd2")
.run(); .run();
} }

View File

@ -46,7 +46,7 @@ fn write_error() {
test test
.no_justfile() .no_justfile()
.args(&["--init"]) .args(["--init"])
.status(EXIT_FAILURE) .status(EXIT_FAILURE)
.stderr_regex(if cfg!(windows) { .stderr_regex(if cfg!(windows) {
r"error: Failed to write justfile to `.*`: Access is denied. \(os error 5\)\n" r"error: Failed to write justfile to `.*`: Access is denied. \(os error 5\)\n"

View File

@ -3,7 +3,7 @@ use super::*;
fn test(justfile: &str, value: Value) { fn test(justfile: &str, value: Value) {
Test::new() Test::new()
.justfile(justfile) .justfile(justfile)
.args(&["--dump", "--dump-format", "json", "--unstable"]) .args(["--dump", "--dump-format", "json", "--unstable"])
.stdout(format!("{}\n", serde_json::to_string(&value).unwrap())) .stdout(format!("{}\n", serde_json::to_string(&value).unwrap()))
.run(); .run();
} }
@ -709,7 +709,7 @@ fn quiet() {
fn requires_unstable() { fn requires_unstable() {
Test::new() Test::new()
.justfile("foo:") .justfile("foo:")
.args(&["--dump", "--dump-format", "json"]) .args(["--dump", "--dump-format", "json"])
.stderr("error: The JSON dump format is currently unstable. Invoke `just` with the `--unstable` flag to enable unstable features.\n") .stderr("error: The JSON dump format is currently unstable. Invoke `just` with the `--unstable` flag to enable unstable features.\n")
.status(EXIT_FAILURE) .status(EXIT_FAILURE)
.run(); .run();

View File

@ -47,7 +47,7 @@ mod error_messages;
mod evaluate; mod evaluate;
mod examples; mod examples;
mod export; mod export;
mod fall_back_to_parent; mod fallback;
mod fmt; mod fmt;
mod functions; mod functions;
mod ignore_comments; mod ignore_comments;
@ -83,3 +83,11 @@ mod undefined_variables;
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]
mod windows_shell; mod windows_shell;
mod working_directory; mod working_directory;
fn path(s: &str) -> String {
if cfg!(windows) {
s.replace('/', "\\")
} else {
s.into()
}
}

View File

@ -9,7 +9,7 @@ fn private_attribute_for_recipe() {
foo: foo:
", ",
) )
.args(&["--list"]) .args(["--list"])
.stdout( .stdout(
" "
Available recipes: Available recipes:
@ -29,7 +29,7 @@ fn private_attribute_for_alias() {
foo: foo:
", ",
) )
.args(&["--list"]) .args(["--list"])
.stdout( .stdout(
" "
Available recipes: Available recipes:

View File

@ -8,7 +8,7 @@ fn single_quotes_are_prepended_and_appended() {
x := quote('abc') x := quote('abc')
", ",
) )
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("'abc'") .stdout("'abc'")
.run(); .run();
} }
@ -21,7 +21,7 @@ fn quotes_are_escaped() {
x := quote("'") x := quote("'")
"#, "#,
) )
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout(r"''\'''") .stdout(r"''\'''")
.run(); .run();
} }

View File

@ -9,7 +9,7 @@ fn dont_run_duplicate_recipes() {
# foo # foo
", ",
) )
.args(&["foo", "foo"]) .args(["foo", "foo"])
.stderr( .stderr(
" "
# foo # foo

View File

@ -4,7 +4,7 @@ use super::*;
fn once() { fn once() {
Test::new() Test::new()
.justfile("x := 'a' / 'b'") .justfile("x := 'a' / 'b'")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("a/b") .stdout("a/b")
.run(); .run();
} }
@ -13,7 +13,7 @@ fn once() {
fn twice() { fn twice() {
Test::new() Test::new()
.justfile("x := 'a' / 'b' / 'c'") .justfile("x := 'a' / 'b' / 'c'")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("a/b/c") .stdout("a/b/c")
.run(); .run();
} }
@ -22,7 +22,7 @@ fn twice() {
fn no_lhs_once() { fn no_lhs_once() {
Test::new() Test::new()
.justfile("x := / 'a'") .justfile("x := / 'a'")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("/a") .stdout("/a")
.run(); .run();
} }
@ -31,12 +31,12 @@ fn no_lhs_once() {
fn no_lhs_twice() { fn no_lhs_twice() {
Test::new() Test::new()
.justfile("x := / 'a' / 'b'") .justfile("x := / 'a' / 'b'")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("/a/b") .stdout("/a/b")
.run(); .run();
Test::new() Test::new()
.justfile("x := // 'a'") .justfile("x := // 'a'")
.args(&["--evaluate", "x"]) .args(["--evaluate", "x"])
.stdout("//a") .stdout("//a")
.run(); .run();
} }

View File

@ -78,8 +78,8 @@ impl Test {
self self
} }
pub(crate) fn args(mut self, args: &[&str]) -> Self { pub(crate) fn args<'a>(mut self, args: impl AsRef<[&'a str]>) -> Self {
for arg in args { for arg in args.as_ref() {
self = self.arg(arg); self = self.arg(arg);
} }
self self
@ -154,6 +154,13 @@ impl Test {
self.unindent_stdout = unindent_stdout; self.unindent_stdout = unindent_stdout;
self self
} }
pub(crate) fn write(self, path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> Self {
let path = self.tempdir.path().join(path);
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
std::fs::write(path, content).unwrap();
self
}
} }
impl Test { impl Test {