Files
hyperfine/tests/execution_order_tests.rs
T
David Peter d9418e0731 Use raw executor by default, skip intermediate shell
This adds a new "raw executor" (next to the "shell executor") that
allows hyperfine to execute commands directly without any intermediate
shell.

The command line is split into tokens (using the `shell-words` crate),
and according to POSIX rules. The first token is taken as the executable,
and the rest as arguments.

The new executor is enabled by default. In order to select the shell
executor, users will have to pass `--shell=default`.

This allows us to reduce measurement noise and to benchmark very quick
commands. It also decreases the time to run benchmarks, as we don't need
the calibration phase.

Also, it allows one to make sure that the executed command is not
implemented as a shell builtin. For example `hyperfine true`
and `hyperfine --shell=default true` return different times due
to the fact that `bash` executes `true` as a NOP.

Co-authored: Ciprian Dorin Craciun <ciprian@volution.ro>
2022-02-22 22:51:09 +01:00

279 lines
7.1 KiB
Rust

use std::{fs::File, io::Read, path::PathBuf};
use tempfile::{tempdir, TempDir};
mod common;
use common::hyperfine_shell;
struct ExecutionOrderTest {
cmd: assert_cmd::Command,
expected_content: String,
logfile_path: PathBuf,
#[allow(dead_code)]
tempdir: TempDir,
}
impl ExecutionOrderTest {
fn new() -> Self {
let tempdir = tempdir().unwrap();
let logfile_path = tempdir.path().join("output.log");
ExecutionOrderTest {
cmd: hyperfine_shell(),
expected_content: String::new(),
logfile_path,
tempdir,
}
}
fn arg<S: AsRef<str>>(&mut self, arg: S) -> &mut Self {
self.cmd.arg(arg.as_ref());
self
}
fn get_command(&self, output: &str) -> String {
format!(
"echo {output} >> {path}",
output = output,
path = self.logfile_path.to_string_lossy()
)
}
fn command(&mut self, output: &str) -> &mut Self {
self.arg(self.get_command(output));
self
}
fn setup(&mut self, output: &str) -> &mut Self {
self.arg("--setup");
self.command(output)
}
fn prepare(&mut self, output: &str) -> &mut Self {
self.arg("--prepare");
self.command(output)
}
fn cleanup(&mut self, output: &str) -> &mut Self {
self.arg("--cleanup");
self.command(output)
}
fn expect_output(&mut self, output: &str) -> &mut Self {
self.expected_content.push_str(output);
#[cfg(windows)]
{
self.expected_content.push_str(" \r");
}
self.expected_content.push('\n');
self
}
fn run(&mut self) {
self.cmd.assert().success();
let mut f = File::open(&self.logfile_path).unwrap();
let mut content = String::new();
f.read_to_string(&mut content).unwrap();
assert_eq!(content, self.expected_content);
}
}
impl Default for ExecutionOrderTest {
fn default() -> Self {
Self::new()
}
}
#[test]
fn benchmarks_are_executed_sequentially_one() {
ExecutionOrderTest::new()
.arg("--runs=1")
.command("command 1")
.command("command 2")
.expect_output("command 1")
.expect_output("command 2")
.run();
}
#[test]
fn benchmarks_are_executed_sequentially() {
ExecutionOrderTest::new()
.arg("--runs=2")
.command("command 1")
.command("command 2")
.expect_output("command 1")
.expect_output("command 1")
.expect_output("command 2")
.expect_output("command 2")
.run();
}
#[test]
fn warmup_runs_are_executed_before_benchmarking_runs() {
ExecutionOrderTest::new()
.arg("--runs=2")
.arg("--warmup=3")
.command("command 1")
.expect_output("command 1")
.expect_output("command 1")
.expect_output("command 1")
.expect_output("command 1")
.expect_output("command 1")
.run();
}
#[test]
fn setup_commands_are_executed_before_each_series_of_timing_runs() {
ExecutionOrderTest::new()
.arg("--runs=2")
.setup("setup")
.command("command 1")
.command("command 2")
.expect_output("setup")
.expect_output("command 1")
.expect_output("command 1")
.expect_output("setup")
.expect_output("command 2")
.expect_output("command 2")
.run();
}
#[test]
fn prepare_commands_are_executed_before_each_timing_run() {
ExecutionOrderTest::new()
.arg("--runs=2")
.prepare("prepare")
.command("command 1")
.command("command 2")
.expect_output("prepare")
.expect_output("command 1")
.expect_output("prepare")
.expect_output("command 1")
.expect_output("prepare")
.expect_output("command 2")
.expect_output("prepare")
.expect_output("command 2")
.run();
}
#[test]
fn prepare_commands_are_executed_before_each_warmup() {
ExecutionOrderTest::new()
.arg("--warmup=2")
.arg("--runs=1")
.prepare("prepare")
.command("command 1")
.command("command 2")
// warmup 1
.expect_output("prepare")
.expect_output("command 1")
.expect_output("prepare")
.expect_output("command 1")
// benchmark 1
.expect_output("prepare")
.expect_output("command 1")
// warmup 2
.expect_output("prepare")
.expect_output("command 2")
.expect_output("prepare")
.expect_output("command 2")
// benchmark 2
.expect_output("prepare")
.expect_output("command 2")
.run();
}
#[test]
fn cleanup_commands_are_executed_once_after_each_benchmark() {
ExecutionOrderTest::new()
.arg("--runs=2")
.cleanup("cleanup")
.command("command 1")
.command("command 2")
.expect_output("command 1")
.expect_output("command 1")
.expect_output("cleanup")
.expect_output("command 2")
.expect_output("command 2")
.expect_output("cleanup")
.run();
}
#[test]
fn setup_prepare_cleanup_combined() {
ExecutionOrderTest::new()
.arg("--warmup=1")
.arg("--runs=2")
.setup("setup")
.prepare("prepare")
.command("command1")
.command("command2")
.cleanup("cleanup")
// 1
.expect_output("setup")
.expect_output("prepare")
.expect_output("command1")
.expect_output("prepare")
.expect_output("command1")
.expect_output("prepare")
.expect_output("command1")
.expect_output("cleanup")
// 2
.expect_output("setup")
.expect_output("prepare")
.expect_output("command2")
.expect_output("prepare")
.expect_output("command2")
.expect_output("prepare")
.expect_output("command2")
.expect_output("cleanup")
.run();
}
#[test]
fn single_parameter_value() {
ExecutionOrderTest::new()
.arg("--runs=2")
.arg("--parameter-list")
.arg("number")
.arg("1,2,3")
.command("command {number}")
.expect_output("command 1")
.expect_output("command 1")
.expect_output("command 2")
.expect_output("command 2")
.expect_output("command 3")
.expect_output("command 3")
.run();
}
#[test]
fn multiple_parameter_values() {
ExecutionOrderTest::new()
.arg("--runs=2")
.arg("--parameter-list")
.arg("number")
.arg("1,2,3")
.arg("--parameter-list")
.arg("letter")
.arg("a,b")
.command("command {number} {letter}")
.expect_output("command 1 a")
.expect_output("command 1 a")
.expect_output("command 2 a")
.expect_output("command 2 a")
.expect_output("command 3 a")
.expect_output("command 3 a")
.expect_output("command 1 b")
.expect_output("command 1 b")
.expect_output("command 2 b")
.expect_output("command 2 b")
.expect_output("command 3 b")
.expect_output("command 3 b")
.run();
}