diff --git a/CHANGELOG.md b/CHANGELOG.md index 110b512..94fe7bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ **Improvements** +* Add `!` syntax to expect command failures (panics or errors). * Add `Command.consume_args()` for convenient argument handling. * Allow `@` in unquoted strings. diff --git a/src/command.rs b/src/command.rs index bdbdd64..cdf2b35 100644 --- a/src/command.rs +++ b/src/command.rs @@ -26,6 +26,9 @@ pub struct Command { /// Silences the output of this command. This is handled automatically, the /// [`Runner`](crate::Runner) does not have to take this into account. pub silent: bool, + /// If true, the command is expected to fail with a panic or error. If the + /// command does not fail, the test fails. + pub fail: bool, /// The command's line number position in the script. pub line_number: u32, } diff --git a/src/lib.rs b/src/lib.rs index 1ea10a5..b665a16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -187,9 +187,9 @@ //! ``` //! //! * [**Prefix:**](Command::prefix) an optional :-terminated string prefix -//! before the command name. The command's output will be given the same -//! prefix. The prefix can be used by the test runner, e.g. to signify two -//! different clients. +//! before the command. The command's output will be given the same prefix. +//! The prefix can be used by the test runner, e.g. to signify two different +//! clients. //! //! ```text //! client1: put key=value @@ -210,6 +210,21 @@ //! foo //! ``` //! +//! * [**Failure:**](Command::fail) if `!` precedes the command, it is expected +//! to fail with an error or panic, and the failure message is used as output. +//! If the command unexpectedly succeeds, the test fails. If the line contains +//! other symbols before the command name (e.g. a prefix or silencing), the +//! `!` must be used immediately before the command name. +//! +//! ```text +//! ! command error=foo +//! prefix: ! command panic=bar +//! (!command error=foo) +//! --- +//! Error: foo +//! prefix: Panic: bar +//! ``` +//! //! ## Output //! //! The command output following a `---` separator can contain any arbitrary diff --git a/src/parser.rs b/src/parser.rs index 586a1e7..d9c69b3 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -98,8 +98,12 @@ fn command(input: Span) -> IResult { let (input, maybe_silent) = opt(terminated(char('('), space0))(input)?; let silent = maybe_silent.is_some(); - // The command itself. + // The prefix and fail marker. let (input, prefix) = opt(terminated(identifier, pair(tag(":"), space0)))(input)?; + let (input, maybe_fail) = opt(terminated(char('!'), space0))(input)?; + let fail = maybe_fail.is_some(); + + // The command itself. let line_number = input.location_line(); let (input, name) = identifier(input)?; let (mut input, args) = many0(preceded(space1, argument))(input)?; @@ -114,7 +118,7 @@ fn command(input: Span) -> IResult { let (input, _) = opt(comment)(input)?; let (input, _) = line_ending(input)?; - Ok((input, Command { name, args, prefix, silent, line_number })) + Ok((input, Command { name, args, prefix, silent, fail, line_number })) } /// Parses a single command argument, consisting of an argument value and diff --git a/src/runner.rs b/src/runner.rs index 2e65267..250de5c 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -116,16 +116,52 @@ pub fn generate(runner: &mut R, input: &str) -> std::io::Result { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "expected command '{}' to fail at line {}, succeeded with: {output}", + command.name, command.line_number + ), + )) + } + + // Expected success, output the result. + Ok(Ok(output)) => output, + + // Expected error, output it. + Ok(Err(e)) if command.fail => format!("Error: {e}"), + + // Unexpected error, return it. + Ok(Err(e)) => { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "command '{}' failed at line {}: {e}", + command.name, command.line_number + ), + )) + } + + // Expected panic, output it. + Err(panic) if command.fail => { + let message = panic + .downcast_ref::<&str>() + .map(|s| s.to_string()) + .or_else(|| panic.downcast_ref::().cloned()) + .unwrap_or_else(|| std::panic::resume_unwind(panic)); + format!("Panic: {message}") + } + + // Unexpected panic, throw it. + Err(panic) => std::panic::resume_unwind(panic), + }; // Silence the output if requested. if command.silent { diff --git a/tests/errors/fail_before_prefix.error b/tests/errors/fail_before_prefix.error new file mode 100644 index 0000000..2a8c695 --- /dev/null +++ b/tests/errors/fail_before_prefix.error @@ -0,0 +1,3 @@ +parse error at line 1 column 8 for CrLf: +!prefix: command + ^ \ No newline at end of file diff --git a/tests/errors/fail_before_prefix.in b/tests/errors/fail_before_prefix.in new file mode 100644 index 0000000..dd00047 --- /dev/null +++ b/tests/errors/fail_before_prefix.in @@ -0,0 +1,2 @@ +!prefix: command +--- \ No newline at end of file diff --git a/tests/errors/fail_before_silence.error b/tests/errors/fail_before_silence.error new file mode 100644 index 0000000..edf2a30 --- /dev/null +++ b/tests/errors/fail_before_silence.error @@ -0,0 +1,3 @@ +parse error at line 1 column 2 for Tag: +!(command) + ^ \ No newline at end of file diff --git a/tests/errors/fail_before_silence.in b/tests/errors/fail_before_silence.in new file mode 100644 index 0000000..b404a16 --- /dev/null +++ b/tests/errors/fail_before_silence.in @@ -0,0 +1,2 @@ +!(command) +--- \ No newline at end of file diff --git a/tests/errors/fail_error.error b/tests/errors/fail_error.error new file mode 100644 index 0000000..0f3db04 --- /dev/null +++ b/tests/errors/fail_error.error @@ -0,0 +1 @@ +command '_error' failed at line 1: message \ No newline at end of file diff --git a/tests/errors/fail_error.in b/tests/errors/fail_error.in new file mode 100644 index 0000000..711aafa --- /dev/null +++ b/tests/errors/fail_error.in @@ -0,0 +1,2 @@ +_error message +--- \ No newline at end of file diff --git a/tests/errors/fail_ok_unexpected.error b/tests/errors/fail_ok_unexpected.error new file mode 100644 index 0000000..ddf6233 --- /dev/null +++ b/tests/errors/fail_ok_unexpected.error @@ -0,0 +1 @@ +expected command '_echo' to fail at line 1, succeeded with: message \ No newline at end of file diff --git a/tests/errors/fail_ok_unexpected.in b/tests/errors/fail_ok_unexpected.in new file mode 100644 index 0000000..695abec --- /dev/null +++ b/tests/errors/fail_ok_unexpected.in @@ -0,0 +1,2 @@ +! _echo message +--- \ No newline at end of file diff --git a/tests/errors/fail_panic.error b/tests/errors/fail_panic.error new file mode 100644 index 0000000..36ebb02 --- /dev/null +++ b/tests/errors/fail_panic.error @@ -0,0 +1 @@ +message \ No newline at end of file diff --git a/tests/errors/fail_panic.in b/tests/errors/fail_panic.in new file mode 100644 index 0000000..0f2f0cf --- /dev/null +++ b/tests/errors/fail_panic.in @@ -0,0 +1,2 @@ +_panic message +--- \ No newline at end of file diff --git a/tests/generate/empty_output.out b/tests/generate/empty_output.out index f8f9c85..1fdc180 100644 --- a/tests/generate/empty_output.out +++ b/tests/generate/empty_output.out @@ -2,12 +2,12 @@ # an empty block at the end of the file. command --- -Command { name: "command", args: [], prefix: None, silent: false, line_number: 3 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 3 } command --- -Command { name: "command", args: [], prefix: None, silent: false, line_number: 6 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 6 } command --- -Command { name: "command", args: [], prefix: None, silent: false, line_number: 9 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 9 } diff --git a/tests/scripts/commands b/tests/scripts/commands index 9d7ef68..7df3a4f 100644 --- a/tests/scripts/commands +++ b/tests/scripts/commands @@ -1,35 +1,40 @@ # A bare command. command --- -Command { name: "command", args: [], prefix: None, silent: false, line_number: 2 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 2 } # Commands with arguments. foo arg bar key=value baz arg key=value --- -Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, line_number: 7 } -Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, line_number: 8 } -Command { name: "baz", args: [Argument { key: None, value: "arg" }, Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, line_number: 9 } +Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: false, line_number: 7 } +Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, fail: false, line_number: 8 } +Command { name: "baz", args: [Argument { key: None, value: "arg" }, Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, fail: false, line_number: 9 } # Commands with prefixes. a: foo arg b: bar key=value --- -a: Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: Some("a"), silent: false, line_number: 16 } -b: Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: Some("b"), silent: false, line_number: 17 } +a: Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: Some("a"), silent: false, fail: false, line_number: 16 } +b: Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: Some("b"), silent: false, fail: false, line_number: 17 } + +# Failing commands. +! foo bar +--- +Error: Command { name: "foo", args: [Argument { key: None, value: "bar" }], prefix: None, silent: false, fail: true, line_number: 23 } # Both prefixes, commands, and argument identifiers can be whitespace, but not # empty (tested under errors/). " ": " " " "=" " --- - : Command { name: " ", args: [Argument { key: Some(" "), value: " " }], prefix: Some(" "), silent: false, line_number: 24 } + : Command { name: " ", args: [Argument { key: Some(" "), value: " " }], prefix: Some(" "), silent: false, fail: false, line_number: 29 } # Empty argument values are fine. command "" command arg="" command arg= --- -Command { name: "command", args: [Argument { key: None, value: "" }], prefix: None, silent: false, line_number: 29 } -Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, line_number: 30 } -Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, line_number: 31 } +Command { name: "command", args: [Argument { key: None, value: "" }], prefix: None, silent: false, fail: false, line_number: 34 } +Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, fail: false, line_number: 35 } +Command { name: "command", args: [Argument { key: Some("arg"), value: "" }], prefix: None, silent: false, fail: false, line_number: 36 } diff --git a/tests/scripts/comments b/tests/scripts/comments index 5828deb..302911c 100644 --- a/tests/scripts/comments +++ b/tests/scripts/comments @@ -5,25 +5,25 @@ # Comment before command. command id=1 --- -Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, line_number: 6 } +Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, fail: false, line_number: 6 } command id=2 # Comment beside command. --- -Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, line_number: 10 } +Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, fail: false, line_number: 10 } command id=3 # Comment after command. --- -Command { name: "command", args: [Argument { key: Some("id"), value: "3" }], prefix: None, silent: false, line_number: 14 } +Command { name: "command", args: [Argument { key: Some("id"), value: "3" }], prefix: None, silent: false, fail: false, line_number: 14 } # Comment between blocks. command id=4 --- -Command { name: "command", args: [Argument { key: Some("id"), value: "4" }], prefix: None, silent: false, line_number: 21 } +Command { name: "command", args: [Argument { key: Some("id"), value: "4" }], prefix: None, silent: false, fail: false, line_number: 21 } command id=5 // Comment using //. --- -Command { name: "command", args: [Argument { key: Some("id"), value: "5" }], prefix: None, silent: false, line_number: 25 } +Command { name: "command", args: [Argument { key: Some("id"), value: "5" }], prefix: None, silent: false, fail: false, line_number: 25 } # Comment at end. \ No newline at end of file diff --git a/tests/scripts/dos_line_endings b/tests/scripts/dos_line_endings index e293b59..a9cb7fb 100644 --- a/tests/scripts/dos_line_endings +++ b/tests/scripts/dos_line_endings @@ -2,7 +2,7 @@ command --- -Command { name: "command", args: [], prefix: None, silent: false, line_number: 3 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 3 } // Comment. @@ -10,9 +10,9 @@ foo arg bar key=value baz arg key=value --- -Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, line_number: 9 } -Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, line_number: 10 } -Command { name: "baz", args: [Argument { key: None, value: "arg" }, Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, line_number: 11 } +Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: false, line_number: 9 } +Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, fail: false, line_number: 10 } +Command { name: "baz", args: [Argument { key: None, value: "arg" }, Argument { key: Some("key"), value: "value" }], prefix: None, silent: false, fail: false, line_number: 11 } # Comment. a: foo arg @@ -20,7 +20,7 @@ a: foo arg b: bar key=value # Comment. --- -a: Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: Some("a"), silent: false, line_number: 18 } -b: Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: Some("b"), silent: false, line_number: 20 } +a: Command { name: "foo", args: [Argument { key: None, value: "arg" }], prefix: Some("a"), silent: false, fail: false, line_number: 18 } +b: Command { name: "bar", args: [Argument { key: Some("key"), value: "value" }], prefix: Some("b"), silent: false, fail: false, line_number: 20 } # Comment. \ No newline at end of file diff --git a/tests/scripts/fail b/tests/scripts/fail new file mode 100644 index 0000000..fe8809b --- /dev/null +++ b/tests/scripts/fail @@ -0,0 +1,27 @@ +# ! is parsed correctly into the fail attribute. It is output as an error, since +# the runner expects an error. +! command arg +--- +Error: Command { name: "command", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: true, line_number: 3 } + +# Errors and panics are handled when ! is given. +! _error foo +--- +Error: foo + +! _panic foo +--- +Panic: foo + +# () can be used to silence the output. +(!_error foo) +(!_panic foo) +--- +ok + +# Prefixes can be used too. +a: ! _error foo +b:!_panic foo +--- +a: Error: foo +b: Panic: foo diff --git a/tests/scripts/hooks b/tests/scripts/hooks index e3a2dce..8445c6c 100644 --- a/tests/scripts/hooks +++ b/tests/scripts/hooks @@ -2,13 +2,13 @@ _set start_block="start" end_block="end" command --- -Command { name: "command", args: [], prefix: None, silent: false, line_number: 3 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 3 } end command --- start -Command { name: "command", args: [], prefix: None, silent: false, line_number: 8 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 8 } end # Newlines in block hooks should be handled appropriately. @@ -16,13 +16,13 @@ _set start_block="start\n" end_block="end\n" command --- start -Command { name: "command", args: [], prefix: None, silent: false, line_number: 16 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 16 } end command --- start -Command { name: "command", args: [], prefix: None, silent: false, line_number: 22 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 22 } end # As should empty lines. @@ -30,7 +30,7 @@ _set start_block="start\n\n" end_block="end\n\n" command --- > start -> Command { name: "command", args: [], prefix: None, silent: false, line_number: 30 } +> Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 30 } > end > @@ -39,5 +39,5 @@ command --- > start > -> Command { name: "command", args: [], prefix: None, silent: false, line_number: 38 } +> Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 38 } > end diff --git a/tests/scripts/output b/tests/scripts/output index 94bd1e8..8397f1b 100644 --- a/tests/scripts/output +++ b/tests/scripts/output @@ -2,8 +2,8 @@ command command arg --- -Command { name: "command", args: [], prefix: None, silent: false, line_number: 2 } -Command { name: "command", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, line_number: 3 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 2 } +Command { name: "command", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: false, line_number: 3 } # Output that contains empty lines should automatically be prefixed with >. # This should be the case for empty lines at the start and end of the output, @@ -13,17 +13,17 @@ command id=1 command id=2 --- > -> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, line_number: 12 } +> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, fail: false, line_number: 12 } > -> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, line_number: 13 } +> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, fail: false, line_number: 13 } _set prefix="" suffix="\n\n" command id=1 command id=2 --- -> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, line_number: 21 } +> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, fail: false, line_number: 21 } > -> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, line_number: 22 } +> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, fail: false, line_number: 22 } > _set prefix="\n" suffix="\n\n" @@ -31,10 +31,10 @@ command id=1 command id=2 --- > -> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, line_number: 30 } +> Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, fail: false, line_number: 30 } > > -> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, line_number: 31 } +> Command { name: "command", args: [Argument { key: Some("id"), value: "2" }], prefix: None, silent: false, fail: false, line_number: 31 } > # Empty output blocks should default to "ok", but only once, and only if none of @@ -54,7 +54,7 @@ ok command (command) --- -Command { name: "command", args: [], prefix: None, silent: false, line_number: 54 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 54 } _set start_block="" end_block="end" --- diff --git a/tests/scripts/silent b/tests/scripts/silent index 8254bdc..5146c8f 100644 --- a/tests/scripts/silent +++ b/tests/scripts/silent @@ -8,8 +8,8 @@ command id=1 (command id=2) command id=3 --- -Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, line_number: 7 } -Command { name: "command", args: [Argument { key: Some("id"), value: "3" }], prefix: None, silent: false, line_number: 9 } +Command { name: "command", args: [Argument { key: Some("id"), value: "1" }], prefix: None, silent: false, fail: false, line_number: 7 } +Command { name: "command", args: [Argument { key: Some("id"), value: "3" }], prefix: None, silent: false, fail: false, line_number: 9 } # Whitespace is allowed around the parentheses, except for the first. ( command ) # eol diff --git a/tests/scripts/strings b/tests/scripts/strings index 7a75c73..a8816b5 100644 --- a/tests/scripts/strings +++ b/tests/scripts/strings @@ -2,18 +2,18 @@ 0: 1 2 3=4 01: 23 45 67=89 --- -0: Command { name: "1", args: [Argument { key: None, value: "2" }, Argument { key: Some("3"), value: "4" }], prefix: Some("0"), silent: false, line_number: 2 } -01: Command { name: "23", args: [Argument { key: None, value: "45" }, Argument { key: Some("67"), value: "89" }], prefix: Some("01"), silent: false, line_number: 3 } +0: Command { name: "1", args: [Argument { key: None, value: "2" }, Argument { key: Some("3"), value: "4" }], prefix: Some("0"), silent: false, fail: false, line_number: 2 } +01: Command { name: "23", args: [Argument { key: None, value: "45" }, Argument { key: Some("67"), value: "89" }], prefix: Some("01"), silent: false, fail: false, line_number: 3 } # Unquoted strings can start with _. _prefix: _command _arg _key=_value --- -_prefix: Command { name: "_command", args: [Argument { key: None, value: "_arg" }, Argument { key: Some("_key"), value: "_value" }], prefix: Some("_prefix"), silent: false, line_number: 9 } +_prefix: Command { name: "_command", args: [Argument { key: None, value: "_arg" }, Argument { key: Some("_key"), value: "_value" }], prefix: Some("_prefix"), silent: false, fail: false, line_number: 9 } # Unquoted strings can contain -_./@ prefix-_.: command-_./@ arg-_./@ key-_./@=value-_./@ --- -prefix-_.: Command { name: "command-_./@", args: [Argument { key: None, value: "arg-_./@" }, Argument { key: Some("key-_./@"), value: "value-_./@" }], prefix: Some("prefix-_."), silent: false, line_number: 14 } +prefix-_.: Command { name: "command-_./@", args: [Argument { key: None, value: "arg-_./@" }, Argument { key: Some("key-_./@"), value: "value-_./@" }], prefix: Some("prefix-_."), silent: false, fail: false, line_number: 14 } # Single-quoted strings can contain any character, including newlines. '➡️': '😀' '"👋"' '\t'='\0' ' @@ -22,7 +22,7 @@ prefix-_.: Command { name: "command-_./@", args: [Argument { key: None, value: " ' --- -➡️: Command { name: "😀", args: [Argument { key: None, value: "\"👋\"" }, Argument { key: Some("\t"), value: "\0" }, Argument { key: None, value: "\n\n 🚀\n\n" }], prefix: Some("➡\u{fe0f}"), silent: false, line_number: 19 } +➡️: Command { name: "😀", args: [Argument { key: None, value: "\"👋\"" }, Argument { key: Some("\t"), value: "\0" }, Argument { key: None, value: "\n\n 🚀\n\n" }], prefix: Some("➡\u{fe0f}"), silent: false, fail: false, line_number: 19 } # Double-quoted strings can too. "➡️": "😀" "'👋'" "\t"="\0" " @@ -31,37 +31,37 @@ prefix-_.: Command { name: "command-_./@", args: [Argument { key: None, value: " " --- -➡️: Command { name: "😀", args: [Argument { key: None, value: "'👋'" }, Argument { key: Some("\t"), value: "\0" }, Argument { key: None, value: "\n\n 🚀\n\n" }], prefix: Some("➡\u{fe0f}"), silent: false, line_number: 28 } +➡️: Command { name: "😀", args: [Argument { key: None, value: "'👋'" }, Argument { key: Some("\t"), value: "\0" }, Argument { key: None, value: "\n\n 🚀\n\n" }], prefix: Some("➡\u{fe0f}"), silent: false, fail: false, line_number: 28 } # Single- and double-quoted strings can also be empty, but is not allowed in # identifiers (prefixes, commands, and argument names). It is allowed as # argument values. command foo="" bar='' --- -Command { name: "command", args: [Argument { key: Some("foo"), value: "" }, Argument { key: Some("bar"), value: "" }], prefix: None, silent: false, line_number: 39 } +Command { name: "command", args: [Argument { key: Some("foo"), value: "" }, Argument { key: Some("bar"), value: "" }], prefix: None, silent: false, fail: false, line_number: 39 } # Single- and double-quoted strings respect all of the same escape sequences, # including both quote types. '\\ \' \" \0 \n \r \t \\' "\\ \' \" \0 \n \r \t \\" --- -Command { name: "\\ ' \" \0 \n \r \t \\", args: [], prefix: None, silent: false, line_number: 45 } -Command { name: "\\ ' \" \0 \n \r \t \\", args: [], prefix: None, silent: false, line_number: 46 } +Command { name: "\\ ' \" \0 \n \r \t \\", args: [], prefix: None, silent: false, fail: false, line_number: 45 } +Command { name: "\\ ' \" \0 \n \r \t \\", args: [], prefix: None, silent: false, fail: false, line_number: 46 } # Quoted strings can contain the other, unescaped quote kind. '"' "'" --- -Command { name: "\"", args: [], prefix: None, silent: false, line_number: 52 } -Command { name: "'", args: [], prefix: None, silent: false, line_number: 53 } +Command { name: "\"", args: [], prefix: None, silent: false, fail: false, line_number: 52 } +Command { name: "'", args: [], prefix: None, silent: false, fail: false, line_number: 53 } # Quoted strings can also contain special characters like silencing ( and prefix # : without them being interpreted as such. '(command:' arg --- -Command { name: "(command:", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, line_number: 60 } +Command { name: "(command:", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: false, line_number: 60 } # They can also contain comments. 'command # with comment' --- -Command { name: "command # with comment", args: [], prefix: None, silent: false, line_number: 65 } +Command { name: "command # with comment", args: [], prefix: None, silent: false, fail: false, line_number: 65 } diff --git a/tests/scripts/whitespace b/tests/scripts/whitespace index 8094008..1bdcd07 100644 --- a/tests/scripts/whitespace +++ b/tests/scripts/whitespace @@ -8,7 +8,7 @@ command --- -Command { name: "command", args: [], prefix: None, silent: false, line_number: 9 } +Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 9 } # Comment. @@ -18,7 +18,7 @@ Command { name: "command", args: [], prefix: None, silent: false, line_number: 9 command arg --- -Command { name: "command", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, line_number: 19 } +Command { name: "command", args: [Argument { key: None, value: "arg" }], prefix: None, silent: false, fail: false, line_number: 19 } diff --git a/tests/tests.rs b/tests/tests.rs index 4267e4c..bfa8a37 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -27,25 +27,36 @@ fn test_generate([in_path, out_path]: [&std::path::Path; 2]) { } // Generate error tests for each pair of *.in and *.error files in tests/errors/. -// The input scripts are expected to error with the stored output. +// The input scripts are expected to error or panic with the stored output. test_each_path! { for ["in", "error"] in "tests/errors" as errors => test_error } fn test_error([in_path, out_path]: [&std::path::Path; 2]) { let input = std::fs::read_to_string(in_path).expect("failed to read file"); - let error = - goldenscript::generate(&mut DebugRunner::new(), &input).expect_err("script succeeded"); + let run = + std::panic::AssertUnwindSafe(|| goldenscript::generate(&mut DebugRunner::new(), &input)); + let message = match std::panic::catch_unwind(run) { + Ok(Ok(_)) => panic!("script succeeded"), + Ok(Err(e)) => e.to_string(), + Err(panic) => panic + .downcast_ref::<&str>() + .map(|s| s.to_string()) + .or_else(|| panic.downcast_ref::().cloned()) + .unwrap_or_else(|| std::panic::resume_unwind(panic)), + }; let dir = out_path.parent().expect("invalid path"); let filename = out_path.file_name().expect("invalid path"); let mut mint = goldenfile::Mint::new(dir); let mut f = mint.new_goldenfile(filename).expect("failed to create goldenfile"); - f.write_all(error.to_string().as_bytes()).expect("failed to write goldenfile"); + f.write_all(message.as_bytes()).expect("failed to write goldenfile"); } /// A goldenscript runner that debug-prints the parsed command. It /// understands the following special commands: /// /// _echo: prints back the arguments, one per line +/// _error: errors with the given string +/// _panic: panics with the given string /// _set: sets various options /// /// - prefix=: printed immediately before the command output @@ -53,6 +64,8 @@ fn test_error([in_path, out_path]: [&std::path::Path; 2]) { /// - start_block=: printed at the start of a block /// - end_block=: printed at the end of a block /// +/// If a command is expected to fail via !, the parsed command string is +/// returned as an error. #[derive(Default)] struct DebugRunner { prefix: String, @@ -80,6 +93,16 @@ impl goldenscript::Runner for DebugRunner { command.args.iter().map(|a| a.value.clone()).collect::>().join(" ") } + "_error" => { + let message = command.args.first().map(|a| a.value.as_str()).unwrap_or("error"); + return Err(message.to_string().into()); + } + + "_panic" => { + let message = command.args.first().map(|a| a.value.as_str()).unwrap_or("panic"); + panic!("{message}"); + } + "_set" => { for arg in &command.args { match arg.key.as_deref() { @@ -94,6 +117,8 @@ impl goldenscript::Runner for DebugRunner { return Ok(String::new()); } + _ if command.fail => return Err(format!("{command:?}").into()), + _ => format!("{command:?}"), };