Arguments working but still ugly
This commit is contained in:
parent
b956ce2397
commit
ac5433248e
5
justfile
5
justfile
@ -1,12 +1,9 @@
|
||||
test:
|
||||
test: build
|
||||
cargo test --lib
|
||||
|
||||
test-quine:
|
||||
cargo run -- quine clean
|
||||
|
||||
test-integ:
|
||||
cargo run -- --justfile integration-tests/justfile --working-directory integration-tests
|
||||
|
||||
backtrace:
|
||||
RUST_BACKTRACE=1 cargo test --lib
|
||||
|
||||
|
28
notes
28
notes
@ -1,19 +1,18 @@
|
||||
notes
|
||||
-----
|
||||
|
||||
- figure out argument passing:
|
||||
. flag: j build --set a=hello
|
||||
. by export: A=HELLO j build
|
||||
. by export 2: BUILD.A=HELLO j build
|
||||
. by name: j build a=hello
|
||||
. by position: j build hello
|
||||
. with marker: j build hello : clean hello :
|
||||
. after -- : j build -- foo baz
|
||||
. fast errors when arguments are missing
|
||||
. could also allow this to override variables
|
||||
although maybe only after a '--': j build -- a=hello
|
||||
- arguments:
|
||||
. change evaluate_expression to evaluate_lines
|
||||
. fast errors when arguments are not passed
|
||||
. don't assume that argument count is correct
|
||||
. don't unwrap errors in evaluate line
|
||||
. sub arguments into recipes
|
||||
|
||||
- save result of commands in variables: `hello`
|
||||
|
||||
- set variables from the command line: j --set build linux
|
||||
|
||||
|
||||
- before release:
|
||||
|
||||
- rewrite grammar.txt
|
||||
@ -56,15 +55,12 @@ notes
|
||||
enhancements:
|
||||
|
||||
- colored error messages
|
||||
- save result of commands in variables
|
||||
- multi line strings (maybe not in recipe interpolations)
|
||||
- raw strings
|
||||
- raw strings with ''
|
||||
- iteration: {{x for x in y}}
|
||||
- allow calling recipes in a justfile in a different directory:
|
||||
. just ../foo # ../justfile:foo
|
||||
. just xyz/foo # xyz/justfile:foo
|
||||
. just xyz/ # xyz/justfile:DEFAULT
|
||||
- allow setting and exporting environment variables
|
||||
- indentation or slash for line continuation
|
||||
- figure out some way to allow changing directories in
|
||||
plain recipes
|
||||
- indentation or slash for line continuation in plain recipes
|
||||
|
12
src/app.rs
12
src/app.rs
@ -45,7 +45,7 @@ pub fn app() {
|
||||
.long("justfile")
|
||||
.takes_value(true)
|
||||
.help("Use <justfile> as justfile. --working-directory must also be set"))
|
||||
.arg(Arg::with_name("recipe")
|
||||
.arg(Arg::with_name("arguments")
|
||||
.multiple(true)
|
||||
.help("recipe(s) to run, defaults to the first recipe in the justfile"))
|
||||
.get_matches();
|
||||
@ -123,15 +123,15 @@ pub fn app() {
|
||||
}
|
||||
}
|
||||
|
||||
let names = if let Some(names) = matches.values_of("recipe") {
|
||||
names.collect::<Vec<_>>()
|
||||
} else if let Some(name) = justfile.first() {
|
||||
vec![name]
|
||||
let arguments = if let Some(arguments) = matches.values_of("arguments") {
|
||||
arguments.collect::<Vec<_>>()
|
||||
} else if let Some(recipe) = justfile.first() {
|
||||
vec![recipe]
|
||||
} else {
|
||||
die!("Justfile contains no recipes");
|
||||
};
|
||||
|
||||
if let Err(run_error) = justfile.run(&names) {
|
||||
if let Err(run_error) = justfile.run(&arguments) {
|
||||
warn!("{}", run_error);
|
||||
process::exit(if let super::RunError::Code{code, ..} = run_error { code } else { -1 });
|
||||
}
|
||||
|
230
src/lib.rs
230
src/lib.rs
@ -134,7 +134,34 @@ fn error_from_signal(recipe: &str, exit_status: process::ExitStatus) -> RunError
|
||||
}
|
||||
|
||||
impl<'a> Recipe<'a> {
|
||||
fn run(&self) -> Result<(), RunError<'a>> {
|
||||
fn run(&self, arguments: &[&'a str], scope: &BTreeMap<&'a str, String>) -> Result<(), RunError<'a>> {
|
||||
let mut evaluated_lines = vec![];
|
||||
for fragments in &self.lines {
|
||||
let mut line = String::new();
|
||||
for fragment in fragments.iter() {
|
||||
match *fragment {
|
||||
Fragment::Text{ref text} => line += text.lexeme,
|
||||
Fragment::Expression{value: Some(ref value), ..} => {
|
||||
line += &value;
|
||||
}
|
||||
Fragment::Expression{ref expression, value: None} => {
|
||||
let mut arg_map = BTreeMap::new();
|
||||
for (i, argument) in arguments.iter().enumerate() {
|
||||
arg_map.insert(*self.arguments.get(i).unwrap(), *argument);
|
||||
}
|
||||
line += &evaluate_expression(
|
||||
expression,
|
||||
&scope,
|
||||
&BTreeMap::new(),
|
||||
&BTreeMap::new(),
|
||||
&arg_map,
|
||||
).unwrap().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
evaluated_lines.push(line);
|
||||
}
|
||||
|
||||
if self.shebang {
|
||||
let tmp = try!(
|
||||
tempdir::TempDir::new("j")
|
||||
@ -149,7 +176,7 @@ impl<'a> Recipe<'a> {
|
||||
);
|
||||
let mut text = String::new();
|
||||
// add the shebang
|
||||
text += &self.evaluated_lines[0];
|
||||
text += &evaluated_lines[0];
|
||||
text += "\n";
|
||||
// add blank lines so that lines in the generated script
|
||||
// have the same line number as the corresponding lines
|
||||
@ -157,7 +184,7 @@ impl<'a> Recipe<'a> {
|
||||
for _ in 1..(self.line_number + 2) {
|
||||
text += "\n"
|
||||
}
|
||||
for line in &self.evaluated_lines[1..] {
|
||||
for line in &evaluated_lines[1..] {
|
||||
text += line;
|
||||
text += "\n";
|
||||
}
|
||||
@ -193,7 +220,7 @@ impl<'a> Recipe<'a> {
|
||||
Err(io_error) => Err(RunError::TmpdirIoError{recipe: self.name, io_error: io_error})
|
||||
});
|
||||
} else {
|
||||
for command in &self.evaluated_lines {
|
||||
for command in &evaluated_lines {
|
||||
let mut command = &command[0..];
|
||||
if !command.starts_with('@') {
|
||||
warn!("{}", command);
|
||||
@ -323,41 +350,60 @@ fn evaluate<'a>(
|
||||
assignment_tokens: &BTreeMap<&'a str, Token<'a>>,
|
||||
recipes: &mut BTreeMap<&'a str, Recipe<'a>>,
|
||||
) -> Result<BTreeMap<&'a str, String>, Error<'a>> {
|
||||
let mut evaluator = Evaluator{
|
||||
seen: HashSet::new(),
|
||||
stack: vec![],
|
||||
evaluated: BTreeMap::new(),
|
||||
assignments: assignments,
|
||||
assignment_tokens: assignment_tokens,
|
||||
};
|
||||
for name in assignments.keys() {
|
||||
try!(evaluator.evaluate_assignment(name));
|
||||
}
|
||||
let mut evaluated = BTreeMap::new();
|
||||
|
||||
for recipe in recipes.values_mut() {
|
||||
for fragments in &mut recipe.lines {
|
||||
let mut line = String::new();
|
||||
for mut fragment in fragments.iter_mut() {
|
||||
match *fragment {
|
||||
Fragment::Text{ref text} => line += text.lexeme,
|
||||
Fragment::Expression{ref expression, ref mut value} => {
|
||||
let evaluated = &try!(evaluator.evaluate_expression(&expression));
|
||||
*value = Some(evaluated.clone());
|
||||
line += evaluated;
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut evaluator = Evaluator{
|
||||
seen: HashSet::new(),
|
||||
stack: vec![],
|
||||
evaluated: &mut evaluated,
|
||||
scope: &BTreeMap::new(),
|
||||
assignments: assignments,
|
||||
assignment_tokens: assignment_tokens,
|
||||
};
|
||||
for name in assignments.keys() {
|
||||
try!(evaluator.evaluate_assignment(name));
|
||||
}
|
||||
|
||||
for recipe in recipes.values_mut() {
|
||||
let mut arguments = BTreeMap::new();
|
||||
for argument in &recipe.arguments {
|
||||
arguments.insert(*argument, None);
|
||||
}
|
||||
recipe.evaluated_lines.push(line);
|
||||
try!(evaluator.evaluate_recipe(recipe, &arguments));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(evaluator.evaluated)
|
||||
Ok(evaluated)
|
||||
}
|
||||
|
||||
fn evaluate_expression<'a: 'b, 'b> (
|
||||
expression: &Expression<'a>,
|
||||
scope: &BTreeMap<&'a str, String>,
|
||||
assignments: &'b BTreeMap<&'a str, Expression<'a>>,
|
||||
assignment_tokens: &'b BTreeMap<&'a str, Token<'a>>,
|
||||
arguments: &BTreeMap<&str, &str>,
|
||||
) -> Result<Option<String>, Error<'a>> {
|
||||
let mut evaluator = Evaluator{
|
||||
seen: HashSet::new(),
|
||||
stack: vec![],
|
||||
evaluated: &mut BTreeMap::new(),
|
||||
scope: scope,
|
||||
assignments: assignments,
|
||||
assignment_tokens: assignment_tokens,
|
||||
};
|
||||
let mut argument_options = BTreeMap::new();
|
||||
for (name, value) in arguments.iter() {
|
||||
argument_options.insert(*name, Some(*value));
|
||||
}
|
||||
evaluator.evaluate_expression(expression, &argument_options)
|
||||
}
|
||||
|
||||
struct Evaluator<'a: 'b, 'b> {
|
||||
stack: Vec<&'a str>,
|
||||
seen: HashSet<&'a str>,
|
||||
evaluated: BTreeMap<&'a str, String>,
|
||||
evaluated: &'b mut BTreeMap<&'a str, String>,
|
||||
scope: &'b BTreeMap<&'a str, String>,
|
||||
assignments: &'b BTreeMap<&'a str, Expression<'a>>,
|
||||
assignment_tokens: &'b BTreeMap<&'a str, Token<'a>>,
|
||||
}
|
||||
@ -372,7 +418,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
||||
self.seen.insert(name);
|
||||
|
||||
if let Some(expression) = self.assignments.get(name) {
|
||||
let value = try!(self.evaluate_expression(expression));
|
||||
let value = try!(self.evaluate_expression(expression, &BTreeMap::new())).unwrap();
|
||||
self.evaluated.insert(name, value);
|
||||
} else {
|
||||
let token = self.assignment_tokens.get(name).unwrap();
|
||||
@ -383,11 +429,35 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn evaluate_expression(&mut self, expression: &Expression<'a>) -> Result<String, Error<'a>> {
|
||||
fn evaluate_recipe(
|
||||
&mut self,
|
||||
recipe: &mut Recipe<'a>,
|
||||
arguments: &BTreeMap<&str, Option<&str>>,
|
||||
) -> Result<(), Error<'a>> {
|
||||
for fragments in &mut recipe.lines {
|
||||
for mut fragment in fragments.iter_mut() {
|
||||
match *fragment {
|
||||
Fragment::Text{..} => {},
|
||||
Fragment::Expression{ref expression, ref mut value} => {
|
||||
*value = try!(self.evaluate_expression(&expression, arguments));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn evaluate_expression(
|
||||
&mut self,
|
||||
expression: &Expression<'a>,
|
||||
arguments: &BTreeMap<&str, Option<&str>>
|
||||
) -> Result<Option<String>, Error<'a>> {
|
||||
Ok(match *expression {
|
||||
Expression::Variable{name, ref token} => {
|
||||
if self.evaluated.contains_key(name) {
|
||||
self.evaluated.get(name).unwrap().clone()
|
||||
Some(self.evaluated.get(name).unwrap().clone())
|
||||
} else if self.scope.contains_key(name) {
|
||||
Some(self.scope.get(name).unwrap().clone())
|
||||
} else if self.seen.contains(name) {
|
||||
let token = self.assignment_tokens.get(name).unwrap();
|
||||
self.stack.push(name);
|
||||
@ -395,20 +465,26 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
||||
variable: name,
|
||||
circle: self.stack.clone(),
|
||||
}));
|
||||
} else if !self.assignments.contains_key(name) {
|
||||
return Err(token.error(ErrorKind::UnknownVariable{variable: name}));
|
||||
} else {
|
||||
} else if self.assignments.contains_key(name) {
|
||||
try!(self.evaluate_assignment(name));
|
||||
self.evaluated.get(name).unwrap().clone()
|
||||
Some(self.evaluated.get(name).unwrap().clone())
|
||||
} else if arguments.contains_key(name) {
|
||||
arguments.get(name).unwrap().map(|s| s.to_string())
|
||||
} else {
|
||||
return Err(token.error(ErrorKind::UnknownVariable{variable: name}));
|
||||
}
|
||||
}
|
||||
Expression::String{ref cooked, ..} => {
|
||||
cooked.clone()
|
||||
Some(cooked.clone())
|
||||
}
|
||||
Expression::Concatination{ref lhs, ref rhs} => {
|
||||
try!(self.evaluate_expression(lhs))
|
||||
+
|
||||
&try!(self.evaluate_expression(rhs))
|
||||
let lhs = try!(self.evaluate_expression(lhs, arguments));
|
||||
let rhs = try!(self.evaluate_expression(rhs, arguments));
|
||||
if let (Some(lhs), Some(rhs)) = (lhs, rhs) {
|
||||
Some(lhs + &rhs)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -434,6 +510,7 @@ enum ErrorKind<'a> {
|
||||
DuplicateDependency{recipe: &'a str, dependency: &'a str},
|
||||
DuplicateRecipe{recipe: &'a str, first: usize},
|
||||
DuplicateVariable{variable: &'a str},
|
||||
DependencyHasArguments{recipe: &'a str, dependency: &'a str},
|
||||
ExtraLeadingWhitespace,
|
||||
InconsistentLeadingWhitespace{expected: &'a str, found: &'a str},
|
||||
InternalError{message: String},
|
||||
@ -517,6 +594,9 @@ impl<'a> Display for Error<'a> {
|
||||
recipe, first, self.line));
|
||||
return Ok(());
|
||||
}
|
||||
ErrorKind::DependencyHasArguments{recipe, dependency} => {
|
||||
try!(writeln!(f, "recipe `{}` depends on `{}` which takes arguments. dependencies may not take arguments", recipe, dependency));
|
||||
}
|
||||
ErrorKind::ArgumentShadowsVariable{argument} => {
|
||||
try!(writeln!(f, "argument `{}` shadows variable of the same name", argument));
|
||||
}
|
||||
@ -578,7 +658,7 @@ struct Justfile<'a> {
|
||||
values: BTreeMap<&'a str, String>,
|
||||
}
|
||||
|
||||
impl<'a> Justfile<'a> {
|
||||
impl<'a, 'b> Justfile<'a> where 'a: 'b {
|
||||
fn first(&self) -> Option<&'a str> {
|
||||
let mut first: Option<&Recipe<'a>> = None;
|
||||
for recipe in self.recipes.values() {
|
||||
@ -601,22 +681,43 @@ impl<'a> Justfile<'a> {
|
||||
self.recipes.keys().cloned().collect()
|
||||
}
|
||||
|
||||
fn run_recipe(&self, recipe: &Recipe<'a>, ran: &mut HashSet<&'a str>) -> Result<(), RunError> {
|
||||
fn run_recipe(&self, recipe: &Recipe<'a>, arguments: &[&'a str], ran: &mut HashSet<&'a str>) -> Result<(), RunError> {
|
||||
for dependency_name in &recipe.dependencies {
|
||||
if !ran.contains(dependency_name) {
|
||||
try!(self.run_recipe(&self.recipes[dependency_name], ran));
|
||||
try!(self.run_recipe(&self.recipes[dependency_name], &[], ran));
|
||||
}
|
||||
}
|
||||
try!(recipe.run());
|
||||
try!(recipe.run(arguments, &self.values));
|
||||
ran.insert(recipe.name);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run<'b>(&'a self, names: &[&'b str]) -> Result<(), RunError<'b>>
|
||||
where 'a: 'b
|
||||
{
|
||||
fn run(&'a self, arguments: &[&'b str]) -> Result<(), RunError<'b>> {
|
||||
for (i, argument) in arguments.iter().enumerate() {
|
||||
if let Some(recipe) = self.recipes.get(argument) {
|
||||
if !recipe.arguments.is_empty() {
|
||||
if i != 0 {
|
||||
return Err(RunError::NonLeadingRecipeWithArguments{recipe: recipe.name});
|
||||
}
|
||||
let rest = &arguments[1..];
|
||||
if recipe.arguments.len() != rest.len() {
|
||||
return Err(RunError::ArgumentCountMismatch {
|
||||
recipe: recipe.name,
|
||||
found: rest.len(),
|
||||
expected: recipe.arguments.len(),
|
||||
});
|
||||
}
|
||||
let mut ran = HashSet::new();
|
||||
try!(self.run_recipe(recipe, rest, &mut ran));
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut missing = vec![];
|
||||
for recipe in names {
|
||||
for recipe in arguments {
|
||||
if !self.recipes.contains_key(recipe) {
|
||||
missing.push(*recipe);
|
||||
}
|
||||
@ -624,15 +725,10 @@ impl<'a> Justfile<'a> {
|
||||
if !missing.is_empty() {
|
||||
return Err(RunError::UnknownRecipes{recipes: missing});
|
||||
}
|
||||
let recipes = names.iter().map(|name| self.recipes.get(name).unwrap()).collect::<Vec<_>>();
|
||||
let recipes: Vec<_> = arguments.iter().map(|name| self.recipes.get(name).unwrap()).collect();
|
||||
let mut ran = HashSet::new();
|
||||
for recipe in &recipes {
|
||||
if !recipe.arguments.is_empty() {
|
||||
return Err(RunError::MissingArguments);
|
||||
}
|
||||
}
|
||||
for recipe in recipes {
|
||||
try!(self.run_recipe(recipe, &mut ran));
|
||||
try!(self.run_recipe(recipe, &[], &mut ran));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@ -674,7 +770,8 @@ impl<'a> Display for Justfile<'a> {
|
||||
#[derive(Debug)]
|
||||
enum RunError<'a> {
|
||||
UnknownRecipes{recipes: Vec<&'a str>},
|
||||
MissingArguments,
|
||||
NonLeadingRecipeWithArguments{recipe: &'a str},
|
||||
ArgumentCountMismatch{recipe: &'a str, found: usize, expected: usize},
|
||||
Signal{recipe: &'a str, signal: i32},
|
||||
Code{recipe: &'a str, code: i32},
|
||||
UnknownFailure{recipe: &'a str},
|
||||
@ -692,8 +789,13 @@ impl<'a> Display for RunError<'a> {
|
||||
try!(write!(f, "Justfile does not contain recipes: {}", recipes.join(" ")));
|
||||
};
|
||||
},
|
||||
RunError::MissingArguments => {
|
||||
try!(write!(f, "Running recipes with arguments is not yet supported"));
|
||||
RunError::NonLeadingRecipeWithArguments{recipe} => {
|
||||
try!(write!(f, "Recipe `{}` takes arguments and so must be the first and only recipe specified on the command line", recipe));
|
||||
},
|
||||
RunError::ArgumentCountMismatch{recipe, found, expected} => {
|
||||
try!(write!(f, "Recipe `{}` takes {} argument{}, but {}{} were found",
|
||||
recipe, expected, if expected == 1 { "" } else { "s" },
|
||||
if found < expected { "only " } else { "" }, found));
|
||||
},
|
||||
RunError::Code{recipe, code} => {
|
||||
try!(write!(f, "Recipe \"{}\" failed with exit code {}", recipe, code));
|
||||
@ -1305,6 +1407,15 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
for dependency in &recipe.dependency_tokens {
|
||||
if !recipes.get(dependency.lexeme).unwrap().arguments.is_empty() {
|
||||
return Err(dependency.error(ErrorKind::DependencyHasArguments {
|
||||
recipe: recipe.name,
|
||||
dependency: dependency.lexeme,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
for line in &recipe.lines {
|
||||
for piece in line {
|
||||
if let Fragment::Expression{ref expression, ..} = *piece {
|
||||
@ -1338,7 +1449,8 @@ impl<'a> Parser<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let values = try!(evaluate(&assignments, &assignment_tokens, &mut recipes));
|
||||
let values =
|
||||
try!(evaluate(&assignments, &assignment_tokens, &mut recipes));
|
||||
|
||||
Ok(Justfile {
|
||||
recipes: recipes,
|
||||
|
50
src/unit.rs
50
src/unit.rs
@ -338,6 +338,19 @@ fn argument_shadows_varible() {
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dependency_with_arguments() {
|
||||
let text = "foo arg:\nb: foo";
|
||||
parse_error(text, Error {
|
||||
text: text,
|
||||
index: 12,
|
||||
line: 1,
|
||||
column: 3,
|
||||
width: Some(3),
|
||||
kind: ErrorKind::DependencyHasArguments{recipe: "b", dependency: "foo"}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_dependency() {
|
||||
let text = "a b c: b c z z";
|
||||
@ -445,6 +458,16 @@ fn string_escapes() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arguments() {
|
||||
parse_summary(
|
||||
"a b c:
|
||||
{{b}} {{c}}",
|
||||
"a b c:
|
||||
{{b # ? }} {{c # ? }}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn self_recipe_dependency() {
|
||||
let text = "a: a";
|
||||
@ -640,7 +663,7 @@ fn unknown_second_interpolation_variable() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_order() {
|
||||
fn tokenize_order() {
|
||||
let text = r"
|
||||
b: a
|
||||
@mv a b
|
||||
@ -657,15 +680,6 @@ c: b
|
||||
tokenize_success(text, "$N:N$>^_$$<N:$>^_$^_$$<N:N$>^_$$<N:N$>^_<.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_arguments_not_supported() {
|
||||
let text = "a foo:";
|
||||
match parse_success(text).run(&["a"]) {
|
||||
Err(super::RunError::MissingArguments) => {}
|
||||
result => panic!("Expecting MissingArguments from run() but got {:?}", result),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_shebang() {
|
||||
// this test exists to make sure that shebang recipes
|
||||
@ -703,3 +717,19 @@ fn code_error() {
|
||||
other @ _ => panic!("expected a code run error, but got: {}", other),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn run_args() {
|
||||
let text = r#"
|
||||
a return code:
|
||||
@function x { {{return}} {{code + "0"}}; }; x"#;
|
||||
|
||||
match parse_success(text).run(&["a", "return", "15"]).unwrap_err() {
|
||||
super::RunError::Code{recipe, code} => {
|
||||
assert_eq!(recipe, "a");
|
||||
assert_eq!(code, 150);
|
||||
},
|
||||
other => panic!("expected an code run error, but got: {}", other),
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user