Skip to content

Commit

Permalink
Add tests for require() (#2594)
Browse files Browse the repository at this point in the history
  • Loading branch information
casey authored Jan 22, 2025
1 parent 398eb29 commit a548f6d
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 52 deletions.
55 changes: 4 additions & 51 deletions src/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -513,13 +513,8 @@ fn replace(_context: Context, s: &str, from: &str, to: &str) -> FunctionResult {
Ok(s.replace(from, to))
}

fn require(context: Context, s: &str) -> FunctionResult {
let p = which(context, s)?;
if p.is_empty() {
Err(format!("could not find required executable: `{s}`"))
} else {
Ok(p)
}
fn require(context: Context, name: &str) -> FunctionResult {
crate::which(context, name)?.ok_or_else(|| format!("could not find executable `{name}`"))
}

fn replace_regex(_context: Context, s: &str, regex: &str, replacement: &str) -> FunctionResult {
Expand Down Expand Up @@ -672,50 +667,8 @@ fn uuid(_context: Context) -> FunctionResult {
Ok(uuid::Uuid::new_v4().to_string())
}

fn which(context: Context, s: &str) -> FunctionResult {
let cmd = Path::new(s);

let candidates = match cmd.components().count() {
0 => return Err("empty command".into()),
1 => {
// cmd is a regular command
let path_var = env::var_os("PATH").ok_or("Environment variable `PATH` is not set")?;
env::split_paths(&path_var)
.map(|path| path.join(cmd))
.collect()
}
_ => {
// cmd contains a path separator, treat it as a path
vec![cmd.into()]
}
};

for mut candidate in candidates {
if candidate.is_relative() {
// This candidate is a relative path, either because the user invoked `which("rel/path")`,
// or because there was a relative path in `PATH`. Resolve it to an absolute path,
// relative to the working directory of the just invocation.
candidate = context
.evaluator
.context
.working_directory()
.join(candidate);
}

candidate = candidate.lexiclean();

if is_executable::is_executable(&candidate) {
return candidate.to_str().map(str::to_string).ok_or_else(|| {
format!(
"Executable path is not valid unicode: {}",
candidate.display()
)
});
}
}

// No viable candidates; return an empty string
Ok(String::new())
fn which(context: Context, name: &str) -> FunctionResult {
Ok(crate::which(context, name)?.unwrap_or_default())
}

fn without_extension(_context: Context, path: &str) -> FunctionResult {
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ pub(crate) use {
variables::Variables,
verbosity::Verbosity,
warning::Warning,
which::which,
},
camino::Utf8Path,
clap::ValueEnum,
Expand Down Expand Up @@ -273,3 +274,4 @@ mod use_color;
mod variables;
mod verbosity;
mod warning;
mod which;
48 changes: 48 additions & 0 deletions src/which.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use super::*;

pub(crate) fn which(context: function::Context, name: &str) -> Result<Option<String>, String> {
let name = Path::new(name);

let candidates = match name.components().count() {
0 => return Err("empty command".into()),
1 => {
// cmd is a regular command
env::split_paths(&env::var_os("PATH").ok_or("`PATH` environment variable not set")?)
.map(|path| path.join(name))
.collect()
}
_ => {
// cmd contains a path separator, treat it as a path
vec![name.into()]
}
};

for mut candidate in candidates {
if candidate.is_relative() {
// This candidate is a relative path, either because the user invoked `which("rel/path")`,
// or because there was a relative path in `PATH`. Resolve it to an absolute path,
// relative to the working directory of the just invocation.
candidate = context
.evaluator
.context
.working_directory()
.join(candidate);
}

candidate = candidate.lexiclean();

if is_executable::is_executable(&candidate) {
return candidate
.to_str()
.map(|candidate| Some(candidate.into()))
.ok_or_else(|| {
format!(
"Executable path is not valid unicode: {}",
candidate.display()
)
});
}
}

Ok(None)
}
35 changes: 34 additions & 1 deletion tests/which_function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,39 @@ fn is_unstable() {
.make_executable("hello.exe")
.env("PATH", path.to_str().unwrap())
.stderr_regex(r".*The `which\(\)` function is currently unstable\..*")
.status(1)
.status(EXIT_FAILURE)
.run();
}

#[test]
fn require_error() {
Test::new()
.justfile("p := require('asdfasdf')")
.args(["--evaluate", "p"])
.stderr(
"
error: Call to function `require` failed: could not find executable `asdfasdf`
——▶ justfile:1:6
1 │ p := require('asdfasdf')
│ ^^^^^^^
",
)
.status(EXIT_FAILURE)
.run();
}

#[test]
fn require_success() {
let tmp = tempdir();
let path = PathBuf::from(tmp.path());

Test::with_tempdir(tmp)
.justfile("p := require('hello.exe')")
.args(["--evaluate", "p"])
.write("hello.exe", HELLO_SCRIPT)
.make_executable("hello.exe")
.env("PATH", path.to_str().unwrap())
.stdout(path.join("hello.exe").display().to_string())
.run();
}

0 comments on commit a548f6d

Please sign in to comment.