Add subsequent dependencies (#820)
Subsequents are dependencies which run after a recipe instead of prior. Subsequents to a recipe only run if the recipe succeeds. Subsequents will run even if a matching invocation already ran as a prior dependencies.
This commit is contained in:
parent
7bbc38a261
commit
77bba3ee0e
@ -302,7 +302,7 @@ impl<'src> Justfile<'src> {
|
|||||||
let mut evaluator =
|
let mut evaluator =
|
||||||
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
|
Evaluator::recipe_evaluator(context.config, dotenv, &scope, context.settings, search);
|
||||||
|
|
||||||
for Dependency { recipe, arguments } in &recipe.dependencies {
|
for Dependency { recipe, arguments } in recipe.dependencies.iter().take(recipe.priors) {
|
||||||
let mut invocation = vec![recipe.name().to_owned()];
|
let mut invocation = vec![recipe.name().to_owned()];
|
||||||
|
|
||||||
for argument in arguments {
|
for argument in arguments {
|
||||||
@ -321,6 +321,27 @@ impl<'src> Justfile<'src> {
|
|||||||
|
|
||||||
recipe.run(context, dotenv, scope.child(), search, &positional)?;
|
recipe.run(context, dotenv, scope.child(), search, &positional)?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut ran = BTreeSet::new();
|
||||||
|
|
||||||
|
for Dependency { recipe, arguments } in recipe.dependencies.iter().skip(recipe.priors) {
|
||||||
|
let mut evaluated = Vec::new();
|
||||||
|
|
||||||
|
for argument in arguments {
|
||||||
|
evaluated.push(evaluator.evaluate_expression(argument)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.run_recipe(
|
||||||
|
context,
|
||||||
|
recipe,
|
||||||
|
&evaluated.iter().map(String::as_ref).collect::<Vec<&str>>(),
|
||||||
|
dotenv,
|
||||||
|
search,
|
||||||
|
&mut ran,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut invocation = vec![recipe.name().to_owned()];
|
let mut invocation = vec![recipe.name().to_owned()];
|
||||||
for argument in arguments.iter().cloned() {
|
for argument in arguments.iter().cloned() {
|
||||||
invocation.push(argument.to_owned());
|
invocation.push(argument.to_owned());
|
||||||
|
62
src/lexer.rs
62
src/lexer.rs
@ -479,7 +479,8 @@ impl<'src> Lexer<'src> {
|
|||||||
/// Lex token beginning with `start` outside of a recipe body
|
/// Lex token beginning with `start` outside of a recipe body
|
||||||
fn lex_normal(&mut self, start: char) -> CompilationResult<'src, ()> {
|
fn lex_normal(&mut self, start: char) -> CompilationResult<'src, ()> {
|
||||||
match start {
|
match start {
|
||||||
'!' => self.lex_bang(),
|
'&' => self.lex_digraph('&', '&', AmpersandAmpersand),
|
||||||
|
'!' => self.lex_digraph('!', '=', BangEquals),
|
||||||
'*' => self.lex_single(Asterisk),
|
'*' => self.lex_single(Asterisk),
|
||||||
'$' => self.lex_single(Dollar),
|
'$' => self.lex_single(Dollar),
|
||||||
'@' => self.lex_single(At),
|
'@' => self.lex_single(At),
|
||||||
@ -679,25 +680,30 @@ impl<'src> Lexer<'src> {
|
|||||||
!self.open_delimiters.is_empty()
|
!self.open_delimiters.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lex a token starting with '!'
|
/// Lex a two-character digraph
|
||||||
fn lex_bang(&mut self) -> CompilationResult<'src, ()> {
|
fn lex_digraph(
|
||||||
self.presume('!')?;
|
&mut self,
|
||||||
|
left: char,
|
||||||
|
right: char,
|
||||||
|
token: TokenKind,
|
||||||
|
) -> CompilationResult<'src, ()> {
|
||||||
|
self.presume(left)?;
|
||||||
|
|
||||||
if self.accepted('=')? {
|
if self.accepted(right)? {
|
||||||
self.token(BangEquals);
|
self.token(token);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
// Emit an unspecified token to consume the current character,
|
// Emit an unspecified token to consume the current character,
|
||||||
self.token(Unspecified);
|
self.token(Unspecified);
|
||||||
|
|
||||||
if self.at_eof() {
|
if self.at_eof() {
|
||||||
return Err(self.error(UnexpectedEndOfToken { expected: '=' }));
|
return Err(self.error(UnexpectedEndOfToken { expected: right }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// …and advance past another character,
|
// …and advance past another character,
|
||||||
self.advance()?;
|
self.advance()?;
|
||||||
// …so that the error we produce highlights the unexpected character.
|
// …so that the error we produce highlights the unexpected character.
|
||||||
Err(self.error(UnexpectedCharacter { expected: '=' }))
|
Err(self.error(UnexpectedCharacter { expected: right }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -919,6 +925,7 @@ mod tests {
|
|||||||
fn default_lexeme(kind: TokenKind) -> &'static str {
|
fn default_lexeme(kind: TokenKind) -> &'static str {
|
||||||
match kind {
|
match kind {
|
||||||
// Fixed lexemes
|
// Fixed lexemes
|
||||||
|
AmpersandAmpersand => "&&",
|
||||||
Asterisk => "*",
|
Asterisk => "*",
|
||||||
At => "@",
|
At => "@",
|
||||||
BangEquals => "!=",
|
BangEquals => "!=",
|
||||||
@ -1048,6 +1055,12 @@ mod tests {
|
|||||||
tokens: (StringToken:"\"\"\"hello\ngoodbye\"\"\""),
|
tokens: (StringToken:"\"\"\"hello\ngoodbye\"\"\""),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: ampersand_ampersand,
|
||||||
|
text: "&&",
|
||||||
|
tokens: (AmpersandAmpersand),
|
||||||
|
}
|
||||||
|
|
||||||
test! {
|
test! {
|
||||||
name: equals,
|
name: equals,
|
||||||
text: "=",
|
text: "=",
|
||||||
@ -2109,16 +2122,6 @@ mod tests {
|
|||||||
kind: UnpairedCarriageReturn,
|
kind: UnpairedCarriageReturn,
|
||||||
}
|
}
|
||||||
|
|
||||||
error! {
|
|
||||||
name: unknown_start_of_token_ampersand,
|
|
||||||
input: " \r\n&",
|
|
||||||
offset: 3,
|
|
||||||
line: 1,
|
|
||||||
column: 0,
|
|
||||||
width: 1,
|
|
||||||
kind: UnknownStartOfToken,
|
|
||||||
}
|
|
||||||
|
|
||||||
error! {
|
error! {
|
||||||
name: unknown_start_of_token_tilde,
|
name: unknown_start_of_token_tilde,
|
||||||
input: "~",
|
input: "~",
|
||||||
@ -2257,6 +2260,29 @@ mod tests {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error! {
|
||||||
|
name: ampersand_eof,
|
||||||
|
input: "&",
|
||||||
|
offset: 1,
|
||||||
|
line: 0,
|
||||||
|
column: 1,
|
||||||
|
width: 0,
|
||||||
|
kind: UnexpectedEndOfToken {
|
||||||
|
expected: '&',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
error! {
|
||||||
|
name: ampersand_unexpected,
|
||||||
|
input: "&%",
|
||||||
|
offset: 1,
|
||||||
|
line: 0,
|
||||||
|
column: 1,
|
||||||
|
width: 1,
|
||||||
|
kind: UnexpectedCharacter {
|
||||||
|
expected: '&',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn presume_error() {
|
fn presume_error() {
|
||||||
assert_matches!(
|
assert_matches!(
|
||||||
|
17
src/node.rs
17
src/node.rs
@ -145,18 +145,29 @@ impl<'src> Node<'src> for UnresolvedRecipe<'src> {
|
|||||||
|
|
||||||
if !self.dependencies.is_empty() {
|
if !self.dependencies.is_empty() {
|
||||||
let mut dependencies = Tree::atom("deps");
|
let mut dependencies = Tree::atom("deps");
|
||||||
|
let mut subsequents = Tree::atom("sups");
|
||||||
|
|
||||||
for dependency in &self.dependencies {
|
for (i, dependency) in self.dependencies.iter().enumerate() {
|
||||||
let mut d = Tree::atom(dependency.recipe.lexeme());
|
let mut d = Tree::atom(dependency.recipe.lexeme());
|
||||||
|
|
||||||
for argument in &dependency.arguments {
|
for argument in &dependency.arguments {
|
||||||
d.push_mut(argument.tree());
|
d.push_mut(argument.tree());
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies.push_mut(d);
|
if i < self.priors {
|
||||||
|
dependencies.push_mut(d);
|
||||||
|
} else {
|
||||||
|
subsequents.push_mut(d);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.push_mut(dependencies);
|
if let Tree::List(_) = dependencies {
|
||||||
|
t.push_mut(dependencies);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Tree::List(_) = subsequents {
|
||||||
|
t.push_mut(subsequents);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.body.is_empty() {
|
if !self.body.is_empty() {
|
||||||
|
@ -627,19 +627,36 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
|
|||||||
dependencies.push(dependency);
|
dependencies.push(dependency);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let priors = dependencies.len();
|
||||||
|
|
||||||
|
if self.accepted(AmpersandAmpersand)? {
|
||||||
|
let mut subsequents = Vec::new();
|
||||||
|
|
||||||
|
while let Some(subsequent) = self.accept_dependency()? {
|
||||||
|
subsequents.push(subsequent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if subsequents.is_empty() {
|
||||||
|
return Err(self.unexpected_token()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies.append(&mut subsequents);
|
||||||
|
}
|
||||||
|
|
||||||
self.expect_eol()?;
|
self.expect_eol()?;
|
||||||
|
|
||||||
let body = self.parse_body()?;
|
let body = self.parse_body()?;
|
||||||
|
|
||||||
Ok(Recipe {
|
Ok(Recipe {
|
||||||
|
parameters: positional.into_iter().chain(variadic).collect(),
|
||||||
private: name.lexeme().starts_with('_'),
|
private: name.lexeme().starts_with('_'),
|
||||||
shebang: body.first().map(Line::is_shebang).unwrap_or(false),
|
shebang: body.first().map(Line::is_shebang).unwrap_or(false),
|
||||||
parameters: positional.into_iter().chain(variadic).collect(),
|
priors,
|
||||||
|
body,
|
||||||
|
dependencies,
|
||||||
doc,
|
doc,
|
||||||
name,
|
name,
|
||||||
quiet,
|
quiet,
|
||||||
dependencies,
|
|
||||||
body,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1102,6 +1119,12 @@ mod tests {
|
|||||||
tree: (justfile (recipe foo (deps (bar (+ 'a' 'b') (+ 'c' 'd'))))),
|
tree: (justfile (recipe foo (deps (bar (+ 'a' 'b') (+ 'c' 'd'))))),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: recipe_subsequent,
|
||||||
|
text: "foo: && bar",
|
||||||
|
tree: (justfile (recipe foo (sups bar))),
|
||||||
|
}
|
||||||
|
|
||||||
test! {
|
test! {
|
||||||
name: recipe_line_single,
|
name: recipe_line_single,
|
||||||
text: "foo:\n bar",
|
text: "foo:\n bar",
|
||||||
@ -1885,10 +1908,13 @@ mod tests {
|
|||||||
name: missing_eol,
|
name: missing_eol,
|
||||||
input: "a b c: z =",
|
input: "a b c: z =",
|
||||||
offset: 9,
|
offset: 9,
|
||||||
line: 0,
|
line: 0,
|
||||||
column: 9,
|
column: 9,
|
||||||
width: 1,
|
width: 1,
|
||||||
kind: UnexpectedToken{expected: vec![Comment, Eof, Eol, Identifier, ParenL], found: Equals},
|
kind: UnexpectedToken{
|
||||||
|
expected: vec![AmpersandAmpersand, Comment, Eof, Eol, Identifier, ParenL],
|
||||||
|
found: Equals
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
error! {
|
error! {
|
||||||
|
@ -25,14 +25,15 @@ fn error_from_signal(
|
|||||||
/// A recipe, e.g. `foo: bar baz`
|
/// A recipe, e.g. `foo: bar baz`
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub(crate) struct Recipe<'src, D = Dependency<'src>> {
|
pub(crate) struct Recipe<'src, D = Dependency<'src>> {
|
||||||
|
pub(crate) body: Vec<Line<'src>>,
|
||||||
pub(crate) dependencies: Vec<D>,
|
pub(crate) dependencies: Vec<D>,
|
||||||
pub(crate) doc: Option<&'src str>,
|
pub(crate) doc: Option<&'src str>,
|
||||||
pub(crate) body: Vec<Line<'src>>,
|
|
||||||
pub(crate) name: Name<'src>,
|
pub(crate) name: Name<'src>,
|
||||||
pub(crate) parameters: Vec<Parameter<'src>>,
|
pub(crate) parameters: Vec<Parameter<'src>>,
|
||||||
pub(crate) private: bool,
|
pub(crate) private: bool,
|
||||||
pub(crate) quiet: bool,
|
pub(crate) quiet: bool,
|
||||||
pub(crate) shebang: bool,
|
pub(crate) shebang: bool,
|
||||||
|
pub(crate) priors: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'src, D> Recipe<'src, D> {
|
impl<'src, D> Recipe<'src, D> {
|
||||||
@ -330,7 +331,12 @@ impl<'src, D: Display> Display for Recipe<'src, D> {
|
|||||||
write!(f, " {}", parameter)?;
|
write!(f, " {}", parameter)?;
|
||||||
}
|
}
|
||||||
write!(f, ":")?;
|
write!(f, ":")?;
|
||||||
for dependency in &self.dependencies {
|
|
||||||
|
for (i, dependency) in self.dependencies.iter().enumerate() {
|
||||||
|
if i == self.priors {
|
||||||
|
write!(f, " &&")?;
|
||||||
|
}
|
||||||
|
|
||||||
write!(f, " {}", dependency)?;
|
write!(f, " {}", dependency)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,9 +113,10 @@ impl<'src: 'run, 'run> RecipeResolver<'src, 'run> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stack.pop();
|
||||||
|
|
||||||
let resolved = Rc::new(recipe.resolve(dependencies)?);
|
let resolved = Rc::new(recipe.resolve(dependencies)?);
|
||||||
self.resolved_recipes.insert(Rc::clone(&resolved));
|
self.resolved_recipes.insert(Rc::clone(&resolved));
|
||||||
stack.pop();
|
|
||||||
Ok(resolved)
|
Ok(resolved)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ use crate::common::*;
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, Ord, PartialOrd, Eq)]
|
#[derive(Debug, PartialEq, Clone, Copy, Ord, PartialOrd, Eq)]
|
||||||
pub(crate) enum TokenKind {
|
pub(crate) enum TokenKind {
|
||||||
|
AmpersandAmpersand,
|
||||||
Asterisk,
|
Asterisk,
|
||||||
At,
|
At,
|
||||||
Backtick,
|
Backtick,
|
||||||
@ -37,6 +38,7 @@ impl Display for TokenKind {
|
|||||||
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
|
||||||
use TokenKind::*;
|
use TokenKind::*;
|
||||||
write!(f, "{}", match *self {
|
write!(f, "{}", match *self {
|
||||||
|
AmpersandAmpersand => "'&&'",
|
||||||
Asterisk => "'*'",
|
Asterisk => "'*'",
|
||||||
At => "'@'",
|
At => "'@'",
|
||||||
Backtick => "backtick",
|
Backtick => "backtick",
|
||||||
|
@ -42,6 +42,12 @@ macro_rules! tree {
|
|||||||
$crate::tree::Tree::atom("*")
|
$crate::tree::Tree::atom("*")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
&&
|
||||||
|
} => {
|
||||||
|
$crate::tree::Tree::atom("&&")
|
||||||
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
==
|
==
|
||||||
} => {
|
} => {
|
||||||
|
@ -7,7 +7,14 @@ impl<'src> UnresolvedRecipe<'src> {
|
|||||||
self,
|
self,
|
||||||
resolved: Vec<Rc<Recipe<'src>>>,
|
resolved: Vec<Rc<Recipe<'src>>>,
|
||||||
) -> CompilationResult<'src, Recipe<'src>> {
|
) -> CompilationResult<'src, Recipe<'src>> {
|
||||||
assert_eq!(self.dependencies.len(), resolved.len());
|
assert_eq!(
|
||||||
|
self.dependencies.len(),
|
||||||
|
resolved.len(),
|
||||||
|
"UnresolvedRecipe::resolve: dependency count not equal to resolved count: {} != {}",
|
||||||
|
self.dependencies.len(),
|
||||||
|
resolved.len()
|
||||||
|
);
|
||||||
|
|
||||||
for (unresolved, resolved) in self.dependencies.iter().zip(&resolved) {
|
for (unresolved, resolved) in self.dependencies.iter().zip(&resolved) {
|
||||||
assert_eq!(unresolved.recipe.lexeme(), resolved.name.lexeme());
|
assert_eq!(unresolved.recipe.lexeme(), resolved.name.lexeme());
|
||||||
if !resolved
|
if !resolved
|
||||||
@ -36,13 +43,14 @@ impl<'src> UnresolvedRecipe<'src> {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Recipe {
|
Ok(Recipe {
|
||||||
doc: self.doc,
|
|
||||||
body: self.body,
|
body: self.body,
|
||||||
|
doc: self.doc,
|
||||||
name: self.name,
|
name: self.name,
|
||||||
parameters: self.parameters,
|
parameters: self.parameters,
|
||||||
private: self.private,
|
private: self.private,
|
||||||
quiet: self.quiet,
|
quiet: self.quiet,
|
||||||
shebang: self.shebang,
|
shebang: self.shebang,
|
||||||
|
priors: self.priors,
|
||||||
dependencies,
|
dependencies,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
15
tests/fmt.rs
15
tests/fmt.rs
@ -944,3 +944,18 @@ test! {
|
|||||||
echo foo
|
echo foo
|
||||||
",
|
",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: subsequent,
|
||||||
|
justfile: "
|
||||||
|
bar:
|
||||||
|
foo: && bar
|
||||||
|
echo foo",
|
||||||
|
args: ("--dump"),
|
||||||
|
stdout: "
|
||||||
|
bar:
|
||||||
|
|
||||||
|
foo: && bar
|
||||||
|
echo foo
|
||||||
|
",
|
||||||
|
}
|
||||||
|
@ -3,11 +3,9 @@ mod test;
|
|||||||
|
|
||||||
mod assert_stdout;
|
mod assert_stdout;
|
||||||
mod assert_success;
|
mod assert_success;
|
||||||
mod common;
|
|
||||||
mod tempdir;
|
|
||||||
|
|
||||||
mod choose;
|
mod choose;
|
||||||
mod command;
|
mod command;
|
||||||
|
mod common;
|
||||||
mod completions;
|
mod completions;
|
||||||
mod conditional;
|
mod conditional;
|
||||||
mod delimiters;
|
mod delimiters;
|
||||||
@ -31,4 +29,6 @@ mod shebang;
|
|||||||
mod shell;
|
mod shell;
|
||||||
mod string;
|
mod string;
|
||||||
mod sublime_syntax;
|
mod sublime_syntax;
|
||||||
|
mod subsequents;
|
||||||
|
mod tempdir;
|
||||||
mod working_directory;
|
mod working_directory;
|
||||||
|
@ -1336,7 +1336,7 @@ test! {
|
|||||||
justfile: "foo: 'bar'",
|
justfile: "foo: 'bar'",
|
||||||
args: ("foo"),
|
args: ("foo"),
|
||||||
stdout: "",
|
stdout: "",
|
||||||
stderr: "error: Expected comment, end of file, end of line, \
|
stderr: "error: Expected '&&', comment, end of file, end of line, \
|
||||||
identifier, or '(', but found string
|
identifier, or '(', but found string
|
||||||
|
|
|
|
||||||
1 | foo: 'bar'
|
1 | foo: 'bar'
|
||||||
|
151
tests/subsequents.rs
Normal file
151
tests/subsequents.rs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
use crate::common::*;
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: success,
|
||||||
|
justfile: "
|
||||||
|
foo: && bar
|
||||||
|
echo foo
|
||||||
|
|
||||||
|
bar:
|
||||||
|
echo bar
|
||||||
|
",
|
||||||
|
stdout: "
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
",
|
||||||
|
stderr: "
|
||||||
|
echo foo
|
||||||
|
echo bar
|
||||||
|
",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: failure,
|
||||||
|
justfile: "
|
||||||
|
foo: && bar
|
||||||
|
echo foo
|
||||||
|
false
|
||||||
|
|
||||||
|
bar:
|
||||||
|
echo bar
|
||||||
|
",
|
||||||
|
stdout: "
|
||||||
|
foo
|
||||||
|
",
|
||||||
|
stderr: "
|
||||||
|
echo foo
|
||||||
|
false
|
||||||
|
error: Recipe `foo` failed on line 3 with exit code 1
|
||||||
|
",
|
||||||
|
status: EXIT_FAILURE,
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: circular_dependency,
|
||||||
|
justfile: "
|
||||||
|
foo: && foo
|
||||||
|
",
|
||||||
|
stderr: "
|
||||||
|
error: Recipe `foo` depends on itself
|
||||||
|
|
|
||||||
|
1 | foo: && foo
|
||||||
|
| ^^^
|
||||||
|
",
|
||||||
|
status: EXIT_FAILURE,
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: unknown,
|
||||||
|
justfile: "
|
||||||
|
foo: && bar
|
||||||
|
",
|
||||||
|
stderr: "
|
||||||
|
error: Recipe `foo` has unknown dependency `bar`
|
||||||
|
|
|
||||||
|
1 | foo: && bar
|
||||||
|
| ^^^
|
||||||
|
",
|
||||||
|
status: EXIT_FAILURE,
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: unknown_argument,
|
||||||
|
justfile: "
|
||||||
|
bar x:
|
||||||
|
|
||||||
|
foo: && (bar y)
|
||||||
|
",
|
||||||
|
stderr: "
|
||||||
|
error: Variable `y` not defined
|
||||||
|
|
|
||||||
|
3 | foo: && (bar y)
|
||||||
|
| ^
|
||||||
|
",
|
||||||
|
status: EXIT_FAILURE,
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: argument,
|
||||||
|
justfile: "
|
||||||
|
foo: && (bar 'hello')
|
||||||
|
|
||||||
|
bar x:
|
||||||
|
echo {{ x }}
|
||||||
|
",
|
||||||
|
stdout: "
|
||||||
|
hello
|
||||||
|
",
|
||||||
|
stderr: "
|
||||||
|
echo hello
|
||||||
|
",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: duplicate_subsequents_dont_run,
|
||||||
|
justfile: "
|
||||||
|
a: && b c
|
||||||
|
echo a
|
||||||
|
|
||||||
|
b: d
|
||||||
|
echo b
|
||||||
|
|
||||||
|
c: d
|
||||||
|
echo c
|
||||||
|
|
||||||
|
d:
|
||||||
|
echo d
|
||||||
|
",
|
||||||
|
stdout: "
|
||||||
|
a
|
||||||
|
d
|
||||||
|
b
|
||||||
|
c
|
||||||
|
",
|
||||||
|
stderr: "
|
||||||
|
echo a
|
||||||
|
echo d
|
||||||
|
echo b
|
||||||
|
echo c
|
||||||
|
",
|
||||||
|
}
|
||||||
|
|
||||||
|
test! {
|
||||||
|
name: subsequents_run_even_if_already_ran_as_prior,
|
||||||
|
justfile: "
|
||||||
|
a: b && b
|
||||||
|
echo a
|
||||||
|
|
||||||
|
b:
|
||||||
|
echo b
|
||||||
|
",
|
||||||
|
stdout: "
|
||||||
|
b
|
||||||
|
a
|
||||||
|
b
|
||||||
|
",
|
||||||
|
stderr: "
|
||||||
|
echo b
|
||||||
|
echo a
|
||||||
|
echo b
|
||||||
|
",
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user