Skip to content

Commit

Permalink
runner: add start_command() and end_command() hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
erikgrinaker committed May 30, 2024
1 parent 9a824ad commit 4fad99e
Show file tree
Hide file tree
Showing 5 changed files with 121 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

* Fix spurious prefix emission with blank lines or empty output.

**Improvements**

* Add `Runner.start_command()` and `end_command()` hooks.

# 0.4.0 (2024-05-29)

**Breaking changes**
Expand Down
5 changes: 3 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,9 @@
//!
//! Runners have various hooks that will be called during script execution:
//! [`Runner::start_script`], [`Runner::end_script`], [`Runner::start_block`],
//! and [`Runner::end_block`]. These can be used e.g. for initial setup,
//! invariant assertions, or to output the current state.
//! [`Runner::end_block`], [`Runner::start_command`], and
//! [`Runner::end_command`]. These can be used e.g. for initial setup, invariant
//! assertions, or to output the current state.

#![warn(clippy::all)]

Expand Down
59 changes: 57 additions & 2 deletions src/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,22 @@ pub trait Runner {
fn end_block(&mut self) -> Result<String, Box<dyn Error>> {
Ok(String::new())
}

/// Called at the start of a command. Used e.g. for setup. Any output is
/// prepended to the command's output, and is affected e.g. by the prefix
/// and silencing of the command.
#[allow(unused_variables)]
fn start_command(&mut self, command: &Command) -> Result<String, Box<dyn Error>> {
Ok(String::new())
}

/// Called at the end of a command. Used e.g. for cleanup. Any output is
/// appended to the command's output, and is affected e.g. by the prefix and
/// silencing of the command.
#[allow(unused_variables)]
fn end_command(&mut self, command: &Command) -> Result<String, Box<dyn Error>> {
Ok(String::new())
}
}

/// Runs a goldenscript at the given path.
Expand Down Expand Up @@ -116,11 +132,24 @@ pub fn generate<R: Runner>(runner: &mut R, input: &str) -> std::io::Result<Strin
));

for command in &block.commands {
let mut command_output = String::new();

// Call the start_command() hook.
command_output.push_str(&ensure_eol(
runner.start_command(command).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("start_command failed at line {}: {e}", command.line_number),
)
})?,
eol,
));

// Execute the command. Handle panics and errors if requested. We
// assume the command is unwind-safe when handling panics, it is up
// to callers to manage this appropriately.
let run = std::panic::AssertUnwindSafe(|| runner.run(command));
let mut command_output = match std::panic::catch_unwind(run) {
command_output.push_str(&match std::panic::catch_unwind(run) {
// Unexpected success, error out.
Ok(Ok(output)) if command.fail => {
return Err(std::io::Error::new(
Expand Down Expand Up @@ -161,11 +190,22 @@ pub fn generate<R: Runner>(runner: &mut R, input: &str) -> std::io::Result<Strin

// Unexpected panic, throw it.
Err(panic) => std::panic::resume_unwind(panic),
};
});

// Make sure the command output has a trailing newline, unless empty.
command_output = ensure_eol(command_output, eol);

// Call the end_command() hook.
command_output.push_str(&ensure_eol(
runner.end_command(command).map_err(|e| {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("end_command failed at line {}: {e}", command.line_number),
)
})?,
eol,
));

// Silence the output if requested.
if command.silent {
command_output = "".to_string();
Expand Down Expand Up @@ -257,6 +297,8 @@ mod tests {
end_script_count: usize,
start_block_count: usize,
end_block_count: usize,
start_command_count: usize,
end_command_count: usize,
}

impl Runner for HookRunner {
Expand All @@ -283,6 +325,16 @@ mod tests {
self.end_block_count += 1;
Ok(String::new())
}

fn start_command(&mut self, _: &Command) -> Result<String, Box<dyn Error>> {
self.start_command_count += 1;
Ok(String::new())
}

fn end_command(&mut self, _: &Command) -> Result<String, Box<dyn Error>> {
self.end_command_count += 1;
Ok(String::new())
}
}

/// Tests that runner hooks are called as expected.
Expand All @@ -295,6 +347,7 @@ mod tests {
command
---
command
command
---
"#,
Expand All @@ -305,5 +358,7 @@ command
assert_eq!(runner.end_script_count, 1);
assert_eq!(runner.start_block_count, 2);
assert_eq!(runner.end_block_count, 2);
assert_eq!(runner.start_command_count, 3);
assert_eq!(runner.end_command_count, 3);
}
}
43 changes: 43 additions & 0 deletions tests/scripts/hooks
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,46 @@ command
>
> Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 38 }
> end

_set start_block="" end_block=""
---
> start
>

# Command hooks should be called before/after each command. It should be affected
# by command prefixes and silencing.
(_set start_command="start" end_command="end")
command
prefix: command
(command)
---
start
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 53 }
end
prefix: start
prefix: Command { name: "command", args: [], prefix: Some("prefix"), silent: false, fail: false, line_number: 54 }
prefix: end

# They should also be called after commands that were expected to fail.
! _panic foo
---
start
Panic: foo
end

# Newlines should be handled properly.
(_set start_command="start\n" end_command="end\n")
command
---
start
Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 70 }
end

(_set start_command="start\n\n" end_command="end\n\n")
command
---
> start
>
> Command { name: "command", args: [], prefix: None, silent: false, fail: false, line_number: 77 }
> end
>
14 changes: 14 additions & 0 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ fn test_error([in_path, out_path]: [&std::path::Path; 2]) {
/// - prefix=<string>: printed immediately before the command output
/// - suffix=<string>: printed immediately after the command output
/// - start_block=<string>: printed at the start of a block
/// - start_command=<string>: printed at the start of a command
/// - end_block=<string>: printed at the end of a block
/// - end_command=<string>: printed at the end of a command
///
/// If a command is expected to fail via !, the parsed command string is
/// returned as an error.
Expand All @@ -72,6 +74,8 @@ struct DebugRunner {
suffix: String,
start_block: String,
end_block: String,
start_command: String,
end_command: String,
}

impl DebugRunner {
Expand Down Expand Up @@ -110,6 +114,8 @@ impl goldenscript::Runner for DebugRunner {
Some("suffix") => self.suffix = arg.value.clone(),
Some("start_block") => self.start_block = arg.value.clone(),
Some("end_block") => self.end_block = arg.value.clone(),
Some("start_command") => self.start_command = arg.value.clone(),
Some("end_command") => self.end_command = arg.value.clone(),
Some(key) => return Err(format!("unknown argument key {key}").into()),
None => return Err("argument must have a key".into()),
}
Expand All @@ -132,6 +138,14 @@ impl goldenscript::Runner for DebugRunner {
fn end_block(&mut self) -> Result<String, Box<dyn Error>> {
Ok(self.end_block.clone())
}

fn start_command(&mut self, _: &goldenscript::Command) -> Result<String, Box<dyn Error>> {
Ok(self.start_command.clone())
}

fn end_command(&mut self, _: &goldenscript::Command) -> Result<String, Box<dyn Error>> {
Ok(self.end_command.clone())
}
}

/// A runner for dateparser tests. This is used to generate example
Expand Down

0 comments on commit 4fad99e

Please sign in to comment.