diff --git a/notes b/notes index f18e108..e9f6178 100644 --- a/notes +++ b/notes @@ -1,15 +1,6 @@ notes ----- -- unit tests for as many backtick error types as possible -- integration tests for as many error types as possible, errors should underline backtick token -- test shebang recipe `` evaluation order - make sure that nothing happens if a `` fails on a later line -- test command recipe `` evaluation order - do some stuff, then have a line with a `` that fails - make sure that stuff before that actually happened - make sure that result error code is correct - - --debug tests that: - should consider renaming it to --evaluate if it actually evaluates stuff - test values of assignments diff --git a/src/app.rs b/src/app.rs index ee69846..b9d1580 100644 --- a/src/app.rs +++ b/src/app.rs @@ -133,6 +133,10 @@ pub fn app() { if let Err(run_error) = justfile.run(&arguments) { warn!("{}", run_error); - process::exit(if let super::RunError::Code{code, ..} = run_error { code } else { -1 }); + match run_error { + super::RunError::Code{code, .. } => process::exit(code), + super::RunError::BacktickCode{code, ..} => process::exit(code), + _ => process::exit(-1), + } } } diff --git a/src/integration.rs b/src/integration.rs index aed0f38..efa0ee5 100644 --- a/src/integration.rs +++ b/src/integration.rs @@ -261,3 +261,91 @@ fn backtick_trimming() { "echo 'Hello, world!'\n", ); } + +#[test] +fn backtick_code_assignment() { + integration_test( + "backtick_code_assignment", + &[], + "b = a\na = `function f { return 100; }; f`\nbar:\n echo '{{`function f { return 200; }; f`}}'", + 100, + "", + "backtick failed with exit code 100 + | +2 | a = `function f { return 100; }; f` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +", + ); +} + +#[test] +fn backtick_code_interpolation() { + integration_test( + "backtick_code_interpolation", + &[], + "b = a\na = `echo hello`\nbar:\n echo '{{`function f { return 200; }; f`}}'", + 200, + "", + "backtick failed with exit code 200 + | +4 | echo '{{`function f { return 200; }; f`}}' + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +", + ); +} + +#[test] +fn shebang_backtick_failure() { + integration_test( + "shebang_backtick_failure", + &[], + "foo: + #!/bin/sh + echo hello + echo {{`exit 123`}}", + 123, + "", + "backtick failed with exit code 123 + | +4 | echo {{`exit 123`}} + | ^^^^^^^^^^ +", + ); +} + +#[test] +fn command_backtick_failure() { + integration_test( + "command_backtick_failure", + &[], + "foo: + echo hello + echo {{`exit 123`}}", + 123, + "hello\n", + "echo hello\nbacktick failed with exit code 123 + | +3 | echo {{`exit 123`}} + | ^^^^^^^^^^ +", + ); +} + +#[test] +fn assignment_backtick_failure() { + integration_test( + "assignment_backtick_failure", + &[], + "foo: + echo hello + echo {{`exit 111`}} +a = `exit 222`", + 222, + "", + "backtick failed with exit code 222 + | +4 | a = `exit 222` + | ^^^^^^^^^^ +", + ); +} diff --git a/src/lib.rs b/src/lib.rs index bcfb4b5..781fb7f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,7 +148,7 @@ fn backtick_error_from_signal(exit_status: process::ExitStatus) -> RunError<'sta RunError::BacktickUnknownFailure } -fn run_backtick<'a>(raw: &str, _token: &Token<'a>) -> Result> { +fn run_backtick<'a>(raw: &str, token: &Token<'a>) -> Result> { let output = process::Command::new("sh") .arg("-cu") .arg(raw) @@ -160,6 +160,7 @@ fn run_backtick<'a>(raw: &str, _token: &Token<'a>) -> Result { TmpdirIoError{recipe: &'a str, io_error: io::Error}, UnknownFailure{recipe: &'a str}, UnknownRecipes{recipes: Vec<&'a str>}, - BacktickCode{code: i32}, + BacktickCode{code: i32, token: Token<'a>}, BacktickIoError{io_error: io::Error}, BacktickSignal{signal: i32}, BacktickUtf8Error{utf8_error: std::str::Utf8Error}, @@ -920,14 +921,27 @@ impl<'a> Display for RunError<'a> { }, RunError::TmpdirIoError{recipe, ref io_error} => try!(write!(f, "Recipe \"{}\" could not be run because of an IO error while trying to create a temporary directory or write a file to that directory`:\n{}", recipe, io_error)), - RunError::BacktickCode{code} => { - try!(writeln!(f, "backtick failed with exit code {}", code)); + RunError::BacktickCode{code, ref token} => { + try!(write!(f, "backtick failed with exit code {}\n", code)); + match token.text.lines().nth(token.line) { + Some(line) => { + let line_number_width = token.line.to_string().len(); + try!(write!(f, "{0:1$} |\n", "", line_number_width)); + try!(write!(f, "{} | {}\n", token.line + 1, line)); + try!(write!(f, "{0:1$} |", "", line_number_width)); + try!(write!(f, " {0:1$}{2:^<3$}", "", + token.column + token.prefix.len(), "", token.lexeme.len())); + }, + None => if token.index != token.text.len() { + try!(write!(f, "internal error: Error has invalid line number: {}", token.line + 1)) + }, + } } RunError::BacktickSignal{signal} => { - try!(writeln!(f, "backtick was terminated by signal {}", signal)); + try!(write!(f, "backtick was terminated by signal {}", signal)); } RunError::BacktickUnknownFailure => { - try!(writeln!(f, "backtick failed for an uknown reason")); + try!(write!(f, "backtick failed for an uknown reason")); } RunError::BacktickIoError{ref io_error} => { try!(match io_error.kind() { @@ -940,14 +954,15 @@ impl<'a> Display for RunError<'a> { try!(write!(f, "backtick succeeded but stdout was not utf8: {}", utf8_error)); } RunError::InternalError{ref message} => { - try!(writeln!(f, "internal error, this may indicate a bug in j: {}\n consider filing an issue: https://github.com/casey/j/issues/new", message)); + try!(write!(f, "internal error, this may indicate a bug in j: {}\n consider filing an issue: https://github.com/casey/j/issues/new", message)); } } + Ok(()) } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] struct Token<'a> { index: usize, line: usize, diff --git a/src/unit.rs b/src/unit.rs index 59ca67a..dbaf8a5 100644 --- a/src/unit.rs +++ b/src/unit.rs @@ -797,3 +797,14 @@ fn missing_default() { other => panic!("expected an code run error, but got: {}", other), } } + +#[test] +fn backtick_code() { + match parse_success("a:\n echo {{`function f { return 100; }; f`}}").run(&["a"]).unwrap_err() { + super::RunError::BacktickCode{code, token} => { + assert_eq!(code, 100); + assert_eq!(token.lexeme, "`function f { return 100; }; f`"); + }, + other => panic!("expected an code run error, but got: {}", other), + } +}