diff --git a/Cargo.lock b/Cargo.lock index 9ffafa9..eb81506 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,16 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -24,6 +35,20 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "assert_cmd" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atty" version = "0.2.14" @@ -148,12 +173,39 @@ dependencies = [ "memchr", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "encode_unicode" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -185,6 +237,7 @@ name = "hyperfine" version = "1.11.0" dependencies = [ "approx", + "assert_cmd", "atty", "cfg-if", "clap", @@ -192,11 +245,13 @@ dependencies = [ "csv", "indicatif", "libc", + "predicates", "rand 0.8.4", "rust_decimal", "serde", "serde_json", "statistical", + "tempfile", "version_check", "winapi", ] @@ -213,6 +268,15 @@ dependencies = [ "regex", ] +[[package]] +name = "itertools" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "0.4.7" @@ -237,6 +301,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc" +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num" version = "0.2.1" @@ -326,6 +396,36 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "predicates" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c143348f141cc87aab5b950021bac6145d0e5ae754b0591de23244cee42c9308" +dependencies = [ + "difflib", + "float-cmp", + "itertools", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" + +[[package]] +name = "predicates-tree" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7dd0fd014130206c9352efbdc92be592751b2b9274dff685348341082c6ea3d" +dependencies = [ + "predicates-core", + "treeline", +] + [[package]] name = "proc-macro2" version = "1.0.26" @@ -499,12 +599,23 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "redox_syscall" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -523,6 +634,15 @@ version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rust_decimal" version = "1.15.0" @@ -598,6 +718,20 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tempfile" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +dependencies = [ + "cfg-if", + "libc", + "rand 0.8.4", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "term_size" version = "0.3.2" @@ -628,6 +762,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + [[package]] name = "unicode-width" version = "0.1.8" @@ -652,6 +792,15 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 605c20a..7c1ff70 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,9 @@ features = ["suggestions", "color", "wrap_help"] [dev-dependencies] approx = "0.5" +assert_cmd = "1.0.8" +predicates = "2.0.1" +tempfile = "3.2.0" [build-dependencies] clap = "2" diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs new file mode 100644 index 0000000..1f93721 --- /dev/null +++ b/tests/integration_tests.rs @@ -0,0 +1,208 @@ +use std::{fs::File, io::Read, path::PathBuf, process::Command}; + +use assert_cmd::cargo::CommandCargoExt; +use tempfile::{tempdir, TempDir}; + +fn hyperfine_raw_command() -> Command { + let mut cmd = Command::cargo_bin("hyperfine").unwrap(); + cmd.current_dir("tests/"); + cmd +} + +fn hyperfine() -> assert_cmd::Command { + assert_cmd::Command::from_std(hyperfine_raw_command()) +} + +#[test] +fn hyperfine_runs_successfully() { + hyperfine() + .arg("--runs=2") + .arg("echo 'dummy benchmark'") + .assert() + .success(); +} + +#[test] +fn at_least_two_runs_are_required() { + hyperfine() + .arg("--runs=1") + .arg("echo 'dummy benchmark'") + .assert() + .failure(); +} + +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(), + expected_content: String::new(), + logfile_path, + tempdir, + } + } + + fn arg>(&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 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); + 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() { + 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 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 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 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(); +}