Repository: casey/just
Pull request #1471 (2023-01-04 06:18:11)

Stabilize fallback setting


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


Test patch:
diff --git a/tests/allow_duplicate_recipes.rs b/tests/allow_duplicate_recipes.rs
index 9e873e4d..f4fc65df 100644
--- a/tests/allow_duplicate_recipes.rs
+++ b/tests/allow_duplicate_recipes.rs
@@ -31,7 +31,7 @@ fn allow_duplicate_recipes_with_args() {
       set allow-duplicate-recipes
     ",
     )
-    .args(&["b", "one", "two"])
+    .args(["b", "one", "two"])
     .stdout("bar one two\n")
     .stderr("echo bar one two\n")
     .run();
diff --git a/tests/changelog.rs b/tests/changelog.rs
index 73fc0ff9..a1a88a0b 100644
--- a/tests/changelog.rs
+++ b/tests/changelog.rs
@@ -3,7 +3,7 @@ use super::*;
 #[test]
 fn print_changelog() {
   Test::new()
-    .args(&["--changelog"])
+    .args(["--changelog"])
     .stdout(fs::read_to_string("CHANGELOG.md").unwrap())
     .run();
 }
diff --git a/tests/choose.rs b/tests/choose.rs
index 6444f2a7..d89c31bc 100644
--- a/tests/choose.rs
+++ b/tests/choose.rs
@@ -128,7 +128,7 @@ fn invoke_error_function() {
     .stderr_regex("error: Chooser `/ -cu fzf` invocation failed: .*")
     .status(EXIT_FAILURE)
     .shell(false)
-    .args(&["--shell", "/", "--choose"])
+    .args(["--shell", "/", "--choose"])
     .run();
 }
 
diff --git a/tests/dotenv.rs b/tests/dotenv.rs
index 257a2d6a..a1182f60 100644
--- a/tests/dotenv.rs
+++ b/tests/dotenv.rs
@@ -2,26 +2,14 @@ use super::*;
 
 #[test]
 fn dotenv() {
-  let tmp = temptree! {
-    ".env": "KEY=ROOT",
-    sub: {
-      ".env": "KEY=SUB",
-      justfile: "default:\n\techo KEY=${KEY:-unset}",
-    },
-  };
-
-  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::new()
+    .write(".env", "KEY=ROOT")
+    .write("sub/.env", "KEY=SUB")
+    .write("sub/justfile", "default:\n\techo KEY=${KEY:-unset}")
+    .args(["sub/default"])
+    .stdout("KEY=unset\n")
+    .stderr("echo KEY=${KEY:-unset}\n")
+    .run();
 }
 
 test! {
@@ -83,7 +71,7 @@ fn path_not_found() {
         echo $NAME
     ",
     )
-    .args(&["--dotenv-path", ".env.prod"])
+    .args(["--dotenv-path", ".env.prod"])
     .stderr(if cfg!(windows) {
       "error: Failed to load environment file: The system cannot find the file specified. (os \
        error 2)\n"
@@ -108,7 +96,7 @@ fn path_resolves() {
         ".env": "NAME=bar"
       }
     })
-    .args(&["--dotenv-path", "subdir/.env"])
+    .args(["--dotenv-path", "subdir/.env"])
     .stdout("bar\n")
     .status(EXIT_SUCCESS)
     .run();
@@ -126,7 +114,7 @@ fn filename_resolves() {
     .tree(tree! {
       ".env.special": "NAME=bar"
     })
-    .args(&["--dotenv-filename", ".env.special"])
+    .args(["--dotenv-filename", ".env.special"])
     .stdout("bar\n")
     .status(EXIT_SUCCESS)
     .run();
@@ -146,7 +134,7 @@ fn filename_flag_overwrites_no_load() {
     .tree(tree! {
       ".env.special": "NAME=bar"
     })
-    .args(&["--dotenv-filename", ".env.special"])
+    .args(["--dotenv-filename", ".env.special"])
     .stdout("bar\n")
     .status(EXIT_SUCCESS)
     .run();
@@ -168,7 +156,7 @@ fn path_flag_overwrites_no_load() {
         ".env": "NAME=bar"
       }
     })
-    .args(&["--dotenv-path", "subdir/.env"])
+    .args(["--dotenv-path", "subdir/.env"])
     .stdout("bar\n")
     .status(EXIT_SUCCESS)
     .run();
diff --git a/tests/error_messages.rs b/tests/error_messages.rs
index d5c2300c..8e5fb36a 100644
--- a/tests/error_messages.rs
+++ b/tests/error_messages.rs
@@ -40,7 +40,7 @@ test! {
 fn argument_count_mismatch() {
   Test::new()
     .justfile("foo a b:")
-    .args(&["foo"])
+    .args(["foo"])
     .stderr(
       "
       error: Recipe `foo` got 0 arguments but takes 2
diff --git a/tests/fall_back_to_parent.rs b/tests/fallback.rs
similarity index 85%
rename from tests/fall_back_to_parent.rs
rename to tests/fallback.rs
index ea4771a4..8a1e2117 100644
--- a/tests/fall_back_to_parent.rs
+++ b/tests/fallback.rs
@@ -1,5 +1,45 @@
 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]
 fn runs_recipe_in_parent_if_not_found_in_current() {
   Test::new()
@@ -19,7 +59,7 @@ fn runs_recipe_in_parent_if_not_found_in_current() {
         echo root
     ",
     )
-    .args(&["--unstable", "foo"])
+    .args(["foo"])
     .current_dir("bar")
     .stderr(format!(
       "
@@ -51,7 +91,7 @@ fn setting_accepts_value() {
         echo root
     ",
     )
-    .args(&["--unstable", "foo"])
+    .args(["foo"])
     .current_dir("bar")
     .stderr(format!(
       "
@@ -78,7 +118,7 @@ fn print_error_from_parent_if_recipe_not_found_in_current() {
       }
     })
     .justfile("foo:\n echo {{bar}}")
-    .args(&["--unstable", "foo"])
+    .args(["foo"])
     .current_dir("bar")
     .stderr(format!(
       "
@@ -95,31 +135,6 @@ fn print_error_from_parent_if_recipe_not_found_in_current() {
 }
 
 #[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() {
   Test::new()
     .tree(tree! {
@@ -136,7 +151,7 @@ fn requires_setting() {
         echo root
     ",
     )
-    .args(&["--unstable", "foo"])
+    .args(["foo"])
     .current_dir("bar")
     .status(EXIT_FAILURE)
     .stderr("error: Justfile does not contain recipe `foo`.\n")
@@ -162,7 +177,7 @@ fn works_with_provided_search_directory() {
         echo root
     ",
     )
-    .args(&["--unstable", "./foo"])
+    .args(["./foo"])
     .stdout("root\n")
     .stderr(format!(
       "
@@ -192,7 +207,7 @@ fn doesnt_work_with_justfile() {
         echo root
     ",
     )
-    .args(&["--unstable", "--justfile", "justfile", "foo"])
+    .args(["--justfile", "justfile", "foo"])
     .current_dir("bar")
     .status(EXIT_FAILURE)
     .stderr("error: Justfile does not contain recipe `foo`.\n")
@@ -216,14 +231,7 @@ fn doesnt_work_with_justfile_and_working_directory() {
         echo root
     ",
     )
-    .args(&[
-      "--unstable",
-      "--justfile",
-      "justfile",
-      "--working-directory",
-      ".",
-      "foo",
-    ])
+    .args(["--justfile", "justfile", "--working-directory", ".", "foo"])
     .current_dir("bar")
     .status(EXIT_FAILURE)
     .stderr("error: Justfile does not contain recipe `foo`.\n")
@@ -249,7 +257,7 @@ fn prints_correct_error_message_when_recipe_not_found() {
         echo root
     ",
     )
-    .args(&["--unstable", "foo"])
+    .args(["foo"])
     .current_dir("bar")
     .status(EXIT_FAILURE)
     .stderr(format!(
@@ -289,7 +297,7 @@ fn multiple_levels_of_fallback_work() {
         echo root
     ",
     )
-    .args(&["--unstable", "baz"])
+    .args(["baz"])
     .current_dir("a/b")
     .stdout("root\n")
     .stderr(format!(
@@ -328,7 +336,7 @@ fn stop_fallback_when_fallback_is_false() {
         echo root
     ",
     )
-    .args(&["--unstable", "baz"])
+    .args(["baz"])
     .current_dir("a/b")
     .stderr(format!(
       "
diff --git a/tests/fmt.rs b/tests/fmt.rs
index 37862424..1b46dcd7 100644
--- a/tests/fmt.rs
+++ b/tests/fmt.rs
@@ -105,7 +105,7 @@ fn write_error() {
 
   let test = Test::with_tempdir(tempdir)
     .no_justfile()
-    .args(&["--fmt", "--unstable"])
+    .args(["--fmt", "--unstable"])
     .status(EXIT_FAILURE)
     .stderr_regex(if cfg!(windows) {
       r"error: Failed to write justfile to `.*`: Access is denied. \(os error 5\)\n"
@@ -1055,7 +1055,7 @@ test! {
 fn exported_parameter() {
   Test::new()
     .justfile("foo +$f:")
-    .args(&["--dump"])
+    .args(["--dump"])
     .stdout("foo +$f:\n")
     .run();
 }
diff --git a/tests/functions.rs b/tests/functions.rs
index 97472b34..14ec4e2f 100644
--- a/tests/functions.rs
+++ b/tests/functions.rs
@@ -414,7 +414,7 @@ test! {
 fn assert_eval_eq(expression: &str, result: &str) {
   Test::new()
     .justfile(format!("x := {}", expression))
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout(result)
     .unindent_stdout(false)
     .run();
@@ -478,7 +478,7 @@ fn join() {
 fn join_argument_count_error() {
   Test::new()
     .justfile("x := join('a')")
-    .args(&["--evaluate"])
+    .args(["--evaluate"])
     .stderr(
       "
       error: Function `join` called with 1 argument but takes 2 or more
@@ -498,7 +498,7 @@ fn test_path_exists_filepath_exist() {
       testfile: ""
     })
     .justfile("x := path_exists('testfile')")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("true")
     .run();
 }
@@ -507,7 +507,7 @@ fn test_path_exists_filepath_exist() {
 fn test_path_exists_filepath_doesnt_exist() {
   Test::new()
     .justfile("x := path_exists('testfile')")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("false")
     .run();
 }
@@ -516,7 +516,7 @@ fn test_path_exists_filepath_doesnt_exist() {
 fn error_errors_with_message() {
   Test::new()
     .justfile("x := error ('Thing Not Supported')")
-    .args(&["--evaluate"])
+    .args(["--evaluate"])
     .status(1)
     .stderr("error: Call to function `error` failed: Thing Not Supported\n  |\n1 | x := error ('Thing Not Supported')\n  |      ^^^^^\n")
     .run();
@@ -526,7 +526,7 @@ fn error_errors_with_message() {
 fn test_absolute_path_resolves() {
   let test_object = Test::new()
     .justfile("path := absolute_path('./test_file')")
-    .args(&["--evaluate", "path"]);
+    .args(["--evaluate", "path"]);
 
   let mut tempdir = test_object.tempdir.path().to_owned();
 
@@ -546,7 +546,7 @@ fn test_absolute_path_resolves() {
 fn test_absolute_path_resolves_parent() {
   let test_object = Test::new()
     .justfile("path := absolute_path('../test_file')")
-    .args(&["--evaluate", "path"]);
+    .args(["--evaluate", "path"]);
 
   let mut tempdir = test_object.tempdir.path().to_owned();
 
@@ -580,7 +580,7 @@ fn path_exists_subdir() {
     })
     .justfile("x := path_exists('foo')")
     .current_dir("bar")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("true")
     .run();
 }
@@ -589,7 +589,7 @@ fn path_exists_subdir() {
 fn uuid() {
   Test::new()
     .justfile("x := uuid()")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout_regex("........-....-....-....-............")
     .run();
 }
@@ -598,7 +598,7 @@ fn uuid() {
 fn sha256() {
   Test::new()
     .justfile("x := sha256('5943ee37-0000-1000-8000-010203040506')")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("2330d7f5eb94a820b54fed59a8eced236f80b633a504289c030b6a65aef58871")
     .run();
 }
@@ -613,7 +613,7 @@ fn sha256_file() {
       }
     })
     .current_dir("sub")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("177b3d79aaafb53a7a4d7aaba99a82f27c73370e8cb0295571aade1e4fea1cd2")
     .run();
 }
diff --git a/tests/init.rs b/tests/init.rs
index 7ef27666..deab5bda 100644
--- a/tests/init.rs
+++ b/tests/init.rs
@@ -46,7 +46,7 @@ fn write_error() {
 
   test
     .no_justfile()
-    .args(&["--init"])
+    .args(["--init"])
     .status(EXIT_FAILURE)
     .stderr_regex(if cfg!(windows) {
       r"error: Failed to write justfile to `.*`: Access is denied. \(os error 5\)\n"
diff --git a/tests/json.rs b/tests/json.rs
index a00b2b9f..599bdc01 100644
--- a/tests/json.rs
+++ b/tests/json.rs
@@ -3,7 +3,7 @@ use super::*;
 fn test(justfile: &str, value: Value) {
   Test::new()
     .justfile(justfile)
-    .args(&["--dump", "--dump-format", "json", "--unstable"])
+    .args(["--dump", "--dump-format", "json", "--unstable"])
     .stdout(format!("{}\n", serde_json::to_string(&value).unwrap()))
     .run();
 }
@@ -709,7 +709,7 @@ fn quiet() {
 fn requires_unstable() {
   Test::new()
     .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")
     .status(EXIT_FAILURE)
     .run();
diff --git a/tests/lib.rs b/tests/lib.rs
index 506942d3..ce246688 100644
--- a/tests/lib.rs
+++ b/tests/lib.rs
@@ -47,7 +47,7 @@ mod error_messages;
 mod evaluate;
 mod examples;
 mod export;
-mod fall_back_to_parent;
+mod fallback;
 mod fmt;
 mod functions;
 mod ignore_comments;
@@ -83,3 +83,11 @@ mod undefined_variables;
 #[cfg(target_family = "windows")]
 mod windows_shell;
 mod working_directory;
+
+fn path(s: &str) -> String {
+  if cfg!(windows) {
+    s.replace('/', "\\")
+  } else {
+    s.into()
+  }
+}
diff --git a/tests/private.rs b/tests/private.rs
index 13dcacf5..9ab0cd82 100644
--- a/tests/private.rs
+++ b/tests/private.rs
@@ -9,7 +9,7 @@ fn private_attribute_for_recipe() {
       foo:
       ",
     )
-    .args(&["--list"])
+    .args(["--list"])
     .stdout(
       "
       Available recipes:
@@ -29,7 +29,7 @@ fn private_attribute_for_alias() {
       foo:
       ",
     )
-    .args(&["--list"])
+    .args(["--list"])
     .stdout(
       "
       Available recipes:
diff --git a/tests/quote.rs b/tests/quote.rs
index bc6afc40..080a9967 100644
--- a/tests/quote.rs
+++ b/tests/quote.rs
@@ -8,7 +8,7 @@ fn single_quotes_are_prepended_and_appended() {
       x := quote('abc')
     ",
     )
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("'abc'")
     .run();
 }
@@ -21,7 +21,7 @@ fn quotes_are_escaped() {
       x := quote("'")
     "#,
     )
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout(r"''\'''")
     .run();
 }
diff --git a/tests/run.rs b/tests/run.rs
index bad51f8a..7f10cc99 100644
--- a/tests/run.rs
+++ b/tests/run.rs
@@ -9,7 +9,7 @@ fn dont_run_duplicate_recipes() {
         # foo
     ",
     )
-    .args(&["foo", "foo"])
+    .args(["foo", "foo"])
     .stderr(
       "
       # foo
diff --git a/tests/slash_operator.rs b/tests/slash_operator.rs
index 31859e3b..ab91559f 100644
--- a/tests/slash_operator.rs
+++ b/tests/slash_operator.rs
@@ -4,7 +4,7 @@ use super::*;
 fn once() {
   Test::new()
     .justfile("x := 'a' / 'b'")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("a/b")
     .run();
 }
@@ -13,7 +13,7 @@ fn once() {
 fn twice() {
   Test::new()
     .justfile("x := 'a' / 'b' / 'c'")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("a/b/c")
     .run();
 }
@@ -22,7 +22,7 @@ fn twice() {
 fn no_lhs_once() {
   Test::new()
     .justfile("x := / 'a'")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("/a")
     .run();
 }
@@ -31,12 +31,12 @@ fn no_lhs_once() {
 fn no_lhs_twice() {
   Test::new()
     .justfile("x := / 'a' / 'b'")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("/a/b")
     .run();
   Test::new()
     .justfile("x := // 'a'")
-    .args(&["--evaluate", "x"])
+    .args(["--evaluate", "x"])
     .stdout("//a")
     .run();
 }
diff --git a/tests/test.rs b/tests/test.rs
index 58c54221..62bc0895 100644
--- a/tests/test.rs
+++ b/tests/test.rs
@@ -78,8 +78,8 @@ impl Test {
     self
   }
 
-  pub(crate) fn args(mut self, args: &[&str]) -> Self {
-    for arg in args {
+  pub(crate) fn args<'a>(mut self, args: impl AsRef<[&'a str]>) -> Self {
+    for arg in args.as_ref() {
       self = self.arg(arg);
     }
     self
@@ -154,6 +154,13 @@ impl Test {
     self.unindent_stdout = unindent_stdout;
     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 {