diff --git a/Cargo.lock b/Cargo.lock index ee1faed0..d4ab32c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,12 +42,53 @@ dependencies = [ ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "anstream" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ - "winapi 0.3.9", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] @@ -96,7 +137,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -154,7 +195,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.109", + "syn", ] [[package]] @@ -394,19 +435,44 @@ dependencies = [ [[package]] name = "clap" -version = "2.34.0" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ - "ansi_term", - "atty", - "bitflags 1.3.2", - "strsim 0.8.0", - "textwrap", - "unicode-width 0.1.14", - "vec_map", + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + [[package]] name = "cobs" version = "0.3.0" @@ -416,6 +482,12 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -544,7 +616,7 @@ dependencies = [ "cranelift-assembler-x64-meta", "cranelift-codegen-shared", "cranelift-srcgen", - "heck 0.5.0", + "heck", "pulley-interpreter", ] @@ -684,7 +756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -734,8 +806,8 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim 0.11.1", - "syn 2.0.109", + "strsim", + "syn", ] [[package]] @@ -746,7 +818,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -796,7 +868,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -806,7 +878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.109", + "syn", ] [[package]] @@ -826,7 +898,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -909,7 +981,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -1245,7 +1317,7 @@ version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df" dependencies = [ - "unicode-width 0.2.2", + "unicode-width", ] [[package]] @@ -1351,15 +1423,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.5.0" @@ -1690,6 +1753,12 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -1928,7 +1997,7 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -2150,6 +2219,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "opener" version = "0.7.2" @@ -2185,7 +2260,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -2316,7 +2391,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -2478,31 +2553,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.109", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "syn", ] [[package]] @@ -2534,7 +2585,7 @@ checksum = "36f7d5ef31ebf1b46cd7e722ffef934e670d7e462f49aa01cde07b9b76dca580" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -2979,7 +3030,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -3104,6 +3155,7 @@ version = "0.1.0" dependencies = [ "anyhow", "assert_cmd", + "clap", "csv", "env_logger 0.8.4", "libloading 0.9.0", @@ -3122,7 +3174,6 @@ dependencies = [ "sightglass-fingerprint", "sightglass-recorder", "sightglass-upload", - "structopt", "tempfile", "thiserror 1.0.69", "vega_lite_4", @@ -3286,42 +3337,12 @@ dependencies = [ "quote", ] -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck 0.3.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "strum" version = "0.28.0" @@ -3337,10 +3358,10 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -3349,17 +3370,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.109" @@ -3385,7 +3395,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -3496,15 +3506,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" -[[package]] -name = "textwrap" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width 0.1.14", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -3531,7 +3532,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -3542,7 +3543,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -3687,7 +3688,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -3717,18 +3718,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-width" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" - [[package]] name = "unicode-width" version = "0.2.2" @@ -3771,6 +3760,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid" version = "1.23.2" @@ -3802,12 +3797,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "vega_lite_4" version = "0.8.1" @@ -3922,7 +3911,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.109", + "syn", "wasm-bindgen-shared", ] @@ -3942,7 +3931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd23d12cc95c451c1306db5bc63075fbebb612bb70c53b4237b1ce5bc178343" dependencies = [ "anyhow", - "heck 0.5.0", + "heck", "im-rc", "indexmap", "log", @@ -4181,7 +4170,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.109", + "syn", "wasmtime-internal-component-util", "wasmtime-internal-wit-bindgen", "wit-parser", @@ -4292,7 +4281,7 @@ checksum = "737c4d956fc3a848541a064afb683dd2771132a6b125be5baaf95c4379aa47df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -4320,7 +4309,7 @@ checksum = "2192a77a00b9a67800c2b4e1c70fb6abca79d6b529e53a2ef9dcdcc36090330d" dependencies = [ "anyhow", "bitflags 2.10.0", - "heck 0.5.0", + "heck", "indexmap", "wit-parser", ] @@ -4386,7 +4375,7 @@ dependencies = [ "bumpalo", "leb128fmt", "memchr", - "unicode-width 0.2.2", + "unicode-width", "wasm-encoder 0.251.0", ] @@ -4441,10 +4430,10 @@ version = "43.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bd7a197903e5b4ff5e13aef9c891960d71e92073600ecf4c86c7e795ac1c803" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.109", + "syn", "wasmtime-environ", "witx", ] @@ -4457,7 +4446,7 @@ checksum = "6410b86fcec207070d9372b215d3470bad67215e6bbac46981a16999c4abbc28" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", "wiggle-generate", ] @@ -4544,7 +4533,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -4555,7 +4544,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -4917,7 +4906,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", "synstructure", ] @@ -4938,7 +4927,7 @@ checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] @@ -4958,7 +4947,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", "synstructure", ] @@ -4992,7 +4981,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.109", + "syn", ] [[package]] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 69e95cea..a30d5859 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -21,7 +21,7 @@ sightglass-data = { path = "../data" } sightglass-fingerprint = { path = "../fingerprint" } sightglass-recorder = { path = "../recorder" } sightglass-upload = { path = "../upload" } -structopt = { version = "0.3", features = ["color", "suggestions"] } +clap = { version = "4", features = ["derive"] } thiserror = "1.0" rand = { version = "0.7.3", features = ["small_rng"] } csv = "1.1.6" diff --git a/crates/cli/src/benchmark.rs b/crates/cli/src/benchmark.rs index 92d5fcf7..fd6022d3 100644 --- a/crates/cli/src/benchmark.rs +++ b/crates/cli/src/benchmark.rs @@ -1,5 +1,6 @@ use crate::suite::BenchmarkOrSuite; use anyhow::{anyhow, Context, Result}; +use clap::Parser; use rand::{rngs::SmallRng, Rng, SeedableRng}; use sightglass_data::{Format, Measurement, Phase}; use sightglass_recorder::bench_api::Engine; @@ -13,7 +14,6 @@ use std::{ path::{Path, PathBuf}, process::{Command, Stdio}, }; -use structopt::StructOpt; const DEFAULT_PROCESSES: usize = 10; const DEFAULT_ITERATIONS_PER_PROCESS: usize = 10; @@ -204,7 +204,7 @@ mod callgrind { /// /// The total number of samples taken for each Wasm benchmark is `PROCESSES * /// NUMBER_OF_ITERATIONS_PER_PROCESS`. -#[derive(StructOpt, Debug)] +#[derive(Parser, Debug)] pub struct BenchmarkCommand { /// The path to the file(s) to benchmark. This accepts one or more: /// @@ -217,11 +217,7 @@ pub struct BenchmarkCommand { /// the `*.suite` file. /// /// By default, this will use `benchmarks/default.suite`. - #[structopt( - index = 1, - default_value = "benchmarks/default.suite", - value_name = "FILE" - )] + #[arg(default_value = "benchmarks/default.suite", value_name = "FILE")] benchmarks: Vec, /// The benchmark engine(s) with which to run the benchmark. @@ -229,13 +225,13 @@ pub struct BenchmarkCommand { /// This is one or more paths to a shared library implementing the /// benchmarking engine specification. See `engines/wasmtime` for an example /// script to build an engine. - #[structopt(long("engine"), short("e"), value_name = "PATH", empty_values = false)] + #[arg(long = "engine", short = 'e', value_name = "PATH")] engines: Vec, /// Configure an engine using engine-specific flags. (For the Wasmtime /// engine, these can be a subset of flags from `wasmtime run --help`). - #[structopt( - long("engine-flags"), + #[arg( + long = "engine-flags", value_name = "ENGINE_FLAGS", allow_hyphen_values = true )] @@ -245,7 +241,7 @@ pub struct BenchmarkCommand { /// /// Defaults to `10`, unless using the `callgrind` measure, in which case the /// default is `3`. - #[structopt(long = "processes", value_name = "PROCESSES")] + #[arg(long = "processes", value_name = "PROCESSES")] processes: Option, /// Override the "engine" name; this is useful if running experiments that might @@ -253,14 +249,14 @@ pub struct BenchmarkCommand { /// /// If multiple engines are provided, the order of names provided here should /// match the order of the engines specified. - #[structopt(long = "name", short = "n")] + #[arg(long = "name", short = 'n')] names: Option>, /// How many times should we run a benchmark in a single process? /// /// Defaults to `10`, unless using the `callgrind` measure, in which case the /// default is `1`. - #[structopt( + #[arg( long = "iterations-per-process", value_name = "NUMBER_OF_ITERATIONS_PER_PROCESS" )] @@ -268,17 +264,17 @@ pub struct BenchmarkCommand { /// Output raw data, rather than the summarized, human-readable analysis /// results. - #[structopt(long)] + #[arg(long)] raw: bool, /// The format of the raw output data when `--raw` is used. Either 'json' or /// 'csv'. - #[structopt(short = "f", long = "output-format", default_value = "json")] + #[arg(short = 'f', long = "output-format", default_value = "json")] output_format: Format, /// Path to a file which will contain the output data, or nothing to print /// to stdout (default). - #[structopt(short = "o", long = "output-file")] + #[arg(short = 'o', long = "output-file")] output_file: Option, /// The type of measurement to use (cycles, insts-retired, perf-counters, @@ -292,7 +288,7 @@ pub struct BenchmarkCommand { /// `callgrind` defaults to fewer processes and iterations per process /// because it runs the benchmarking processes under Valgrind, which is /// slower but also more deterministic and less noisy. - #[structopt(long = "measure", short = "m", multiple = true)] + #[arg(long = "measure", short = 'm', action = clap::ArgAction::Append)] measures: Vec, /// Pass this flag to only run benchmarks over "small" workloads (rather @@ -307,36 +303,36 @@ pub struct BenchmarkCommand { /// of truth, and any cases where results differ between the small and /// default workloads, the results from the small workloads should be /// ignored. - #[structopt(long, alias = "small-workload")] + #[arg(long, alias = "small-workload")] small_workloads: bool, /// The directory to preopen as the benchmark working directory. If the /// benchmark accesses files using WASI, it will see this directory as its /// current working directory (i.e. `.`). If the working directory is not /// specified, the Wasm file's parent directory is used instead. - #[structopt(short("d"), long("working-dir"), parse(from_os_str))] + #[arg(short = 'd', long = "working-dir")] working_dir: Option, /// Benchmark only the given phase (compilation, instantiation, or /// execution). Benchmarks all phases if omitted. - #[structopt(long("benchmark-phase"))] + #[arg(long = "benchmark-phase")] benchmark_phase: Option, /// The significance level for confidence intervals. Typical values are 0.01 /// and 0.05, which correspond to 99% and 95% confidence respectively. This /// is ignored when using `--raw` or when there aren't exactly two engines /// supplied. - #[structopt(short, long, default_value = "0.01")] + #[arg(short, long, default_value = "0.01")] significance_level: f64, /// Pin all benchmark iterations in a process to a single core. See /// `cpu_affinity` in the `sightglass-recorder` crate for more information. - #[structopt(long)] + #[arg(long)] pin: bool, /// Keep log files after successful benchmark runs. By default, logs are /// only kept on failures. - #[structopt(short = "k", long = "keep-logs")] + #[arg(short = 'k', long = "keep-logs")] keep_logs: bool, } diff --git a/crates/cli/src/clean.rs b/crates/cli/src/clean.rs index fd0ca6d8..6dd2765b 100644 --- a/crates/cli/src/clean.rs +++ b/crates/cli/src/clean.rs @@ -1,10 +1,10 @@ use anyhow::{Context, Result}; +use clap::Parser; use regex::Regex; -use structopt::StructOpt; /// Remove the log files emitted in Sightglass runs. -#[derive(StructOpt, Debug)] -#[structopt(name = "clean")] +#[derive(Parser, Debug)] +#[command(name = "clean")] pub struct CleanCommand {} impl CleanCommand { @@ -51,9 +51,16 @@ impl CleanCommand { // Okay! It's one of our log files! log::info!("Removing log file: {}", path.display()); - std::fs::remove_file(&path) - .with_context(|| format!("failed to remove {}", path.display()))?; - removed_count += 1; + match std::fs::remove_file(&path) { + Ok(()) => removed_count += 1, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + // File was already removed (e.g. by a parallel process); that's fine. + } + Err(e) => { + return Err(e) + .with_context(|| format!("failed to remove {}", path.display())); + } + } } } diff --git a/crates/cli/src/effect_size.rs b/crates/cli/src/effect_size.rs index d806f74c..fe965565 100644 --- a/crates/cli/src/effect_size.rs +++ b/crates/cli/src/effect_size.rs @@ -1,33 +1,33 @@ use anyhow::Result; +use clap::Parser; use sightglass_analysis::{effect_size, summarize}; use sightglass_data::Format; use std::{ fs::File, io::{self, BufReader}, }; -use structopt::StructOpt; /// Calculate the effect size (and associated confidence interval) between the /// results for two different engines. -#[derive(Debug, StructOpt)] -#[structopt(name = "effect-size")] +#[derive(Debug, Parser)] +#[command(name = "effect-size")] pub struct EffectSizeCommand { /// Path to the file(s) that will be read from, or none to indicate stdin (default). - #[structopt(short = "f")] + #[arg(short = 'f')] input_file: Option>, /// The format of the input data. Either 'json' or 'csv'. - #[structopt(short = "i", long = "input-format", default_value = "json")] + #[arg(short = 'i', long = "input-format", default_value = "json")] input_format: Format, /// The format of the output data. Either 'json' or 'csv'; if unspecified, print the output in /// human-readable form. - #[structopt(short = "o", long = "output-format")] + #[arg(short = 'o', long = "output-format")] output_format: Option, /// The significance level for the confidence interval. Typical values are /// 0.01 and 0.05, which correspond to 99% and 95% confidence respectively. - #[structopt(short, long, default_value = "0.01")] + #[arg(short, long, default_value = "0.01")] significance_level: f64, } diff --git a/crates/cli/src/fingerprint.rs b/crates/cli/src/fingerprint.rs index 23a5561f..898583d8 100644 --- a/crates/cli/src/fingerprint.rs +++ b/crates/cli/src/fingerprint.rs @@ -1,25 +1,25 @@ use anyhow::Result; +use clap::Parser; use sightglass_data::Format; use sightglass_fingerprint::{Benchmark, Engine, Machine}; use std::{io, path::PathBuf}; -use structopt::StructOpt; /// Gather information about the current machine, a Wasm benchmark, or a Wasm /// engine and print the results to `stdout`. -#[derive(Debug, StructOpt)] -#[structopt(name = "fingerprint")] +#[derive(Debug, Parser)] +#[command(name = "fingerprint")] pub struct FingerprintCommand { /// The kind of item to fingerprint. One of: 'benchmark', 'engine', 'machine'. - #[structopt(short = "k", long = "kind")] + #[arg(short = 'k', long = "kind")] kind: Kind, /// The format of the output data. Either 'json' or 'csv'. - #[structopt(short = "o", long = "output-format", default_value = "json")] + #[arg(short = 'o', long = "output-format", default_value = "json")] output_format: Format, /// The optional path to the file to fingerprint; not all kinds /// fingerprinting require a file (e.g., `--kind machine`). - #[structopt(index = 1, value_name = "FILE", parse(from_os_str))] + #[arg(value_name = "FILE")] file: Option, } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index bb13bb58..8d789413 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -11,13 +11,13 @@ mod validate; use anyhow::Result; use benchmark::BenchmarkCommand; +use clap::Parser; use clean::CleanCommand; use effect_size::EffectSizeCommand; use fingerprint::FingerprintCommand; use log::trace; use pca_metrics::PcaMetricsCommand; use report::ReportCommand; -use structopt::{clap::AppSettings, StructOpt}; use summarize::SummarizeCommand; use upload::UploadCommand; use validate::ValidateCommand; @@ -25,20 +25,14 @@ use validate::ValidateCommand; /// Main entry point for CLI. fn main() -> Result<()> { pretty_env_logger::init(); - let command = SightglassCommand::from_args(); + let command = SightglassCommand::parse(); command.execute()?; Ok(()) } /// The sightglass benchmark runner. -#[derive(StructOpt, Debug)] -#[structopt( - version = env!("CARGO_PKG_VERSION"), - global_settings = &[ - AppSettings::VersionlessSubcommands, - AppSettings::ColoredHelp - ], -)] +#[derive(Parser, Debug)] +#[command(version, propagate_version = true)] enum SightglassCommand { Benchmark(BenchmarkCommand), Clean(CleanCommand), diff --git a/crates/cli/src/pca_metrics.rs b/crates/cli/src/pca_metrics.rs index 42fd81fc..c27f7cca 100644 --- a/crates/cli/src/pca_metrics.rs +++ b/crates/cli/src/pca_metrics.rs @@ -7,20 +7,20 @@ mod static_metrics; use crate::suite::BenchmarkOrSuite; use anyhow::{Context, Result}; use category::{Category, NUM_CATEGORIES}; +use clap::Parser; use dynamic_metrics::{dynamic_metrics, make_engine}; use serde::Serialize; use sightglass_build::get_engine_filename; use static_metrics::static_metrics; use std::path::{Path, PathBuf}; -use structopt::StructOpt; /// Capture benchmark metrics for principal component analysis (PCA). -#[derive(Debug, StructOpt)] -#[structopt(name = "pca-metrics")] +#[derive(Debug, Parser)] +#[command(name = "pca-metrics")] pub struct PcaMetricsCommand { /// The optional file path to write output to. Writes output to stdout if /// omitted. - #[structopt(long, short, parse(from_os_str))] + #[arg(long, short)] output: Option, /// Optionally bound each benchmark's execution to this many units of fuel. @@ -31,13 +31,13 @@ pub struct PcaMetricsCommand { /// /// This primarily exists to make testing this command easier, and shouldn't /// be used when doing full PCA. - #[structopt(long)] + #[arg(long)] fuel: Option, /// The benchmark engine with which to run Callgrind measurements. /// /// Defaults to the Wasmtime engine library in this repository. - #[structopt(long, short, parse(from_os_str))] + #[arg(long, short)] engine: Option, /// The Wasm benchmarks whose PCA metrics should be taken. diff --git a/crates/cli/src/report.rs b/crates/cli/src/report.rs index 620a92d0..1978e1c8 100644 --- a/crates/cli/src/report.rs +++ b/crates/cli/src/report.rs @@ -1,5 +1,5 @@ +use clap::Parser; use std::{ - cell::Cell, collections::HashMap, fs::File, hash::{Hash, Hasher}, @@ -10,7 +10,6 @@ use std::{ use serde::Serialize; use sightglass_analysis::report_stats::{calculate_benchmark_stats, BenchmarkStats, ReportConfig}; use sightglass_data::{extract_benchmark_name, Engine, Format, Measurement, Phase}; -use structopt::StructOpt; use vega_lite_4::{ AxisBuilder, ColorClassBuilder, EdEncodingBuilder, LegendBuilder, Mark, NormalizedSpecBuilder, XClassBuilder, YClassBuilder, @@ -19,37 +18,37 @@ use vega_lite_4::{ const TEMPLATE: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/report.jinja")); /// Generate an HTML report for a given set of raw inputs -#[derive(Debug, StructOpt)] -#[structopt(name = "report")] +#[derive(Debug, Parser)] +#[command(name = "report")] pub struct ReportCommand { /// The format of the input data. Either 'json' or 'csv'; if not provided /// then we will attempt to infer it from provided filenames, falling back to json. - #[structopt(short = "i", long = "input-format")] + #[arg(short = 'i', long = "input-format")] input_format: Option, /// Output HTML file path - #[structopt(short = "o", long = "output-file", default_value = "report.html")] + #[arg(short = 'o', long = "output-file", default_value = "report.html")] output_path: PathBuf, /// Name of the baseline to use; if not provided, the first engine encountered /// in the ordered input files will be used. - #[structopt(short = "b", long = "baseline-engine")] + #[arg(short = 'b', long = "baseline-engine")] baseline_engine: Option, /// Significance level for statistical tests (default: 0.05 for 95% confidence) - #[structopt(long = "significance-level", default_value = "0.05")] + #[arg(long = "significance-level", default_value = "0.05")] significance_level: f64, /// Primary event to analyze (default: cycles) - #[structopt(long = "event", default_value = "cycles")] + #[arg(long = "event", default_value = "cycles")] primary_event: String, /// Target phase to analyze (default: execution) - #[structopt(long = "phase", default_value = "execution")] + #[arg(long = "phase", default_value = "execution")] target_phase: Phase, /// Path to the file(s) that will be read from, or none to indicate stdin (default). - #[structopt(min_values = 1)] + #[arg(num_args = 1..)] input_files: Vec, } @@ -119,9 +118,7 @@ fn parse_input( let format = format .or_else(|| match path.as_ref().extension()?.to_str()? { "json" => Some(Format::Json), - "csv" => Some(Format::Csv { - headers: Cell::new(true), - }), + "csv" => Some(Format::Csv { headers: true }), _ => None, }) .unwrap_or(Format::Json); @@ -360,7 +357,7 @@ impl ReportCommand { let mut all_measurements = Vec::new(); for input_file in &self.input_files { - let measurements = parse_input(self.input_format.clone(), input_file)?; + let measurements = parse_input(self.input_format, input_file)?; all_measurements.extend(measurements); } diff --git a/crates/cli/src/suite.rs b/crates/cli/src/suite.rs index 1d8f1560..83af1f5c 100644 --- a/crates/cli/src/suite.rs +++ b/crates/cli/src/suite.rs @@ -14,7 +14,7 @@ use std::{ }; /// Decide between a suite of benchmarks or an individual benchmark file. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum BenchmarkOrSuite { Suite(Suite), Benchmark(PathBuf), @@ -53,7 +53,7 @@ impl FromStr for BenchmarkOrSuite { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Suite { path: PathBuf, benchmarks: Vec, diff --git a/crates/cli/src/summarize.rs b/crates/cli/src/summarize.rs index 39f0c922..a33d5bee 100644 --- a/crates/cli/src/summarize.rs +++ b/crates/cli/src/summarize.rs @@ -1,29 +1,29 @@ use anyhow::Result; +use clap::Parser; use sightglass_analysis::summarize; use sightglass_data::Format; use std::{ fs::File, io::{self, BufReader}, }; -use structopt::StructOpt; /// Summarize benchmark output; accepts raw benchmark results in `stdin` (i.e., /// from `sightglass-cli benchmark ...`) and prints the summarized results to /// `stdout`. -#[derive(Debug, StructOpt)] -#[structopt(name = "summarize")] +#[derive(Debug, Parser)] +#[command(name = "summarize")] pub struct SummarizeCommand { /// Path to the file(s) that will be read from, or none to indicate stdin (default). - #[structopt(short = "f")] + #[arg(short = 'f')] input_file: Option>, /// The format of the input data. Either 'json' or 'csv'. - #[structopt(short = "i", long = "input-format", default_value = "json")] + #[arg(short = 'i', long = "input-format", default_value = "json")] input_format: Format, /// The format of the output data. Either 'json' or 'csv'; if unspecified, print the output in /// human-readable form. - #[structopt(short = "o", long = "output-format")] + #[arg(short = 'o', long = "output-format")] output_format: Option, } diff --git a/crates/cli/src/upload.rs b/crates/cli/src/upload.rs index ba71927b..91ef5878 100644 --- a/crates/cli/src/upload.rs +++ b/crates/cli/src/upload.rs @@ -1,47 +1,47 @@ use anyhow::{Context, Result}; +use clap::Parser; use sightglass_data::{Format, Measurement}; use sightglass_upload::{upload, upload_package, MeasurementPackage}; use std::{ fs::File, io::{self, BufReader, Read}, }; -use structopt::StructOpt; /// Upload benchmark output to an ElasticSearch server; accepts raw benchmark /// results in `stdin` (i.e., from `sightglass-cli benchmark ...`). -#[derive(Debug, StructOpt)] -#[structopt(name = "upload-elastic")] +#[derive(Debug, Parser)] +#[command(name = "upload-elastic")] pub struct UploadCommand { /// The format of the input data. Either 'json' or 'csv'. - #[structopt(short = "i", long = "input-format", default_value = "json")] + #[arg(short = 'i', long = "input-format", default_value = "json")] input_format: Format, /// Path to the file that will be read from, or none to indicate stdin /// (default). - #[structopt(short = "f", long = "input-file")] + #[arg(short = 'f', long = "input-file")] input_file: Option, /// The URL of a server receiving results; this command only understands how /// to upload results to an ElasticSearch server; e.g., /// `http://localhost:9200`. - #[structopt(index = 1, default_value = "http://localhost:9200", value_name = "URL")] + #[arg(default_value = "http://localhost:9200", value_name = "URL")] server: String, /// Setting this flag will prevent any uploading to the server. Instead, /// the command will emit a JSON "package" to stdout that can be used to /// upload at a later time, see `--from-package`. - #[structopt(short = "d", long = "dry-run")] + #[arg(short = 'd', long = "dry-run")] dry_run: bool, /// Path to a file containing a package of measurements and fingerprint data /// to be uploaded. If this is set, `--input-file` and `--input-format` are /// ignored. - #[structopt(short = "p", long = "from-package")] + #[arg(short = 'p', long = "from-package")] from_package: Option, /// The number of measurements to upload together; this can speed up the /// upload. Defaults to `2000`. - #[structopt(short = "b", long = "batch-size", default_value = "2000")] + #[arg(short = 'b', long = "batch-size", default_value = "2000")] batch_size: usize, } diff --git a/crates/cli/src/validate.rs b/crates/cli/src/validate.rs index 6124784e..85741043 100644 --- a/crates/cli/src/validate.rs +++ b/crates/cli/src/validate.rs @@ -1,20 +1,15 @@ use anyhow::Result; +use clap::Parser; use sightglass_build::WasmBenchmark; use std::path::PathBuf; -use structopt::StructOpt; /// Check that a Wasm benchmark is runnable in this tool. -#[derive(StructOpt, Debug)] -#[structopt(name = "validate")] +#[derive(Parser, Debug)] +#[command(name = "validate")] pub struct ValidateCommand { /// The path to the WebAssembly benchmark module; this file should import `bench.start` and /// `bench.end`. - #[structopt( - index = 1, - required = true, - value_name = "WASMFILE", - parse(from_os_str) - )] + #[arg(required = true, value_name = "WASMFILE")] benchmark: PathBuf, } diff --git a/crates/cli/tests/all/benchmark.rs b/crates/cli/tests/all/benchmark.rs index 1ce18645..23fe9b02 100644 --- a/crates/cli/tests/all/benchmark.rs +++ b/crates/cli/tests/all/benchmark.rs @@ -3,7 +3,7 @@ use assert_cmd::prelude::*; use predicates::prelude::*; use sightglass_data::Measurement; use std::path::PathBuf; - +use tempfile::TempDir; #[test] fn benchmark_phase_compilation() { sightglass_cli_benchmark() @@ -194,3 +194,64 @@ fn benchmark_effect_size() -> anyhow::Result<()> { Ok(()) } + +/// --output-file writes raw JSON to a file rather than stdout. +#[test] +fn benchmark_output_file() -> anyhow::Result<()> { + let dir = TempDir::new()?; + let out = dir.path().join("out.json"); + sightglass_cli_benchmark() + .arg("--raw") + .arg("--processes") + .arg("1") + .arg("--iterations-per-process") + .arg("1") + .arg("--output-file") + .arg(&out) + .arg(benchmark("noop")) + .assert() + .success(); + assert!(out.exists(), "output file was not created"); + let content = std::fs::read_to_string(&out)?; + assert!( + serde_json::from_str::(&content).is_ok(), + "output file is not valid JSON" + ); + Ok(()) +} + +/// --name overrides the engine name in the output. +#[test] +fn benchmark_name_override() { + sightglass_cli_benchmark() + .arg("--raw") + .arg("--processes") + .arg("1") + .arg("--iterations-per-process") + .arg("1") + .arg("--name") + .arg("my-custom-engine") + .arg("--") + .arg(benchmark("noop")) + .assert() + .success() + .stdout(predicate::str::contains("my-custom-engine")); +} + +/// --measure time produces output with nanosecond events. +#[test] +fn benchmark_measure_noop() { + sightglass_cli_benchmark() + .arg("--raw") + .arg("--processes") + .arg("1") + .arg("--iterations-per-process") + .arg("1") + .arg("--measure") + .arg("time") + .arg("--") + .arg(benchmark("noop")) + .assert() + .success() + .stdout(predicate::str::contains("nanoseconds")); +} diff --git a/crates/cli/tests/all/clean.rs b/crates/cli/tests/all/clean.rs new file mode 100644 index 00000000..6b952119 --- /dev/null +++ b/crates/cli/tests/all/clean.rs @@ -0,0 +1,11 @@ +use super::util::sightglass_cli; +use assert_cmd::prelude::*; + +/// clean does not accept unknown flags. +#[test] +fn clean_rejects_unknown_flags() { + sightglass_cli() + .args(["clean", "--unknown-flag"]) + .assert() + .failure(); +} diff --git a/crates/cli/tests/all/effect_size.rs b/crates/cli/tests/all/effect_size.rs new file mode 100644 index 00000000..2d59ee2a --- /dev/null +++ b/crates/cli/tests/all/effect_size.rs @@ -0,0 +1,98 @@ +use super::util::sightglass_cli; +use assert_cmd::prelude::*; +use predicates::prelude::*; + +fn multi_engine_v38_json() -> &'static str { + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/multi_engine_v38.json") +} + +fn multi_engine_v38_epoch_json() -> &'static str { + concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/multi_engine_v38_epoch.json" + ) +} + +/// effect-size reads two-engine JSON and prints a human-readable comparison by default. +#[test] +fn effect_size_human_readable() { + sightglass_cli() + .args([ + "effect-size", + "-f", + multi_engine_v38_json(), + "-f", + multi_engine_v38_epoch_json(), + ]) + .assert() + .success() + .stdout( + predicate::str::contains("cycles").and( + predicate::str::contains("Δ = ") + .or(predicate::str::contains("No difference in performance.")), + ), + ); +} + +/// effect-size with --output-format json produces parseable JSON. +#[test] +fn effect_size_output_format_json() { + let assert = sightglass_cli() + .args([ + "effect-size", + "-f", + multi_engine_v38_json(), + "-f", + multi_engine_v38_epoch_json(), + "--output-format", + "json", + ]) + .assert() + .success(); + + let stdout = std::str::from_utf8(&assert.get_output().stdout).unwrap(); + assert!( + serde_json::from_str::(stdout).is_ok(), + "stdout was not valid JSON: {stdout}" + ); +} + +/// effect-size with --output-format csv produces a CSV header row. +#[test] +fn effect_size_output_format_csv() { + sightglass_cli() + .args([ + "effect-size", + "-f", + multi_engine_v38_json(), + "-f", + multi_engine_v38_epoch_json(), + "--output-format", + "csv", + ]) + .assert() + .success() + .stdout(predicate::str::contains("mean")); +} + +/// effect-size with a nonexistent input file fails with an error. +#[test] +fn effect_size_missing_file_fails() { + sightglass_cli() + .args(["effect-size", "-f", "nonexistent_file_xyz.json"]) + .assert() + .failure(); +} + +/// effect-size with a single-engine file (no comparison possible) exits non-zero. +#[test] +fn effect_size_single_engine_fails() { + sightglass_cli() + .args([ + "effect-size", + "-f", + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/results.json"), + ]) + .assert() + .failure(); +} diff --git a/crates/cli/tests/all/main.rs b/crates/cli/tests/all/main.rs index 0fd72683..08f468b8 100644 --- a/crates/cli/tests/all/main.rs +++ b/crates/cli/tests/all/main.rs @@ -1,9 +1,13 @@ mod benchmark; +mod clean; +mod effect_size; mod fingerprint; mod help; mod pca_metrics; mod report; +mod summarize; mod upload; mod util; +mod validate; fn main() {} diff --git a/crates/cli/tests/all/summarize.rs b/crates/cli/tests/all/summarize.rs new file mode 100644 index 00000000..dc6aeed5 --- /dev/null +++ b/crates/cli/tests/all/summarize.rs @@ -0,0 +1,55 @@ +use super::util::sightglass_cli; +use assert_cmd::prelude::*; +use predicates::prelude::*; + +fn results_json() -> &'static str { + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/results.json") +} + +/// summarize reads raw JSON and prints a human-readable table by default. +#[test] +fn summarize_human_readable() { + sightglass_cli() + .args(["summarize", "-f", results_json()]) + .assert() + .success() + .stdout( + predicate::str::contains("compilation") + .or(predicate::str::contains("Compilation")) + .and(predicate::str::contains("cycles")), + ); +} + +/// summarize --output-format json produces parseable JSON. +#[test] +fn summarize_output_format_json() { + let assert = sightglass_cli() + .args(["summarize", "-f", results_json(), "--output-format", "json"]) + .assert() + .success(); + + let stdout = std::str::from_utf8(&assert.get_output().stdout).unwrap(); + assert!( + serde_json::from_str::(stdout).is_ok(), + "stdout was not valid JSON: {stdout}" + ); +} + +/// summarize --output-format csv produces a CSV header row. +#[test] +fn summarize_output_format_csv() { + sightglass_cli() + .args(["summarize", "-f", results_json(), "--output-format", "csv"]) + .assert() + .success() + .stdout(predicate::str::contains("mean")); +} + +/// summarize with a nonexistent input file fails. +#[test] +fn summarize_missing_file_fails() { + sightglass_cli() + .args(["summarize", "-f", "nonexistent_xyz.json"]) + .assert() + .failure(); +} diff --git a/crates/cli/tests/all/validate.rs b/crates/cli/tests/all/validate.rs new file mode 100644 index 00000000..0b9313cb --- /dev/null +++ b/crates/cli/tests/all/validate.rs @@ -0,0 +1,24 @@ +use super::util::{benchmark, sightglass_cli}; +use assert_cmd::prelude::*; + +/// validate accepts a valid benchmark .wasm file and exits successfully. +#[test] +fn validate_valid_benchmark() { + sightglass_cli() + .arg("validate") + .arg(benchmark("noop")) + .assert() + .success(); +} + +/// validate rejects a non-wasm file (e.g. a JSON fixture). +#[test] +fn validate_non_wasm_file_fails() { + sightglass_cli() + .args([ + "validate", + concat!(env!("CARGO_MANIFEST_DIR"), "/tests/results.json"), + ]) + .assert() + .failure(); +} diff --git a/crates/data/src/format.rs b/crates/data/src/format.rs index 7a8e54eb..68d0282b 100644 --- a/crates/data/src/format.rs +++ b/crates/data/src/format.rs @@ -4,29 +4,26 @@ use core::fmt; use csv::ReaderBuilder; use serde::{de::DeserializeOwned, Serialize}; use std::{ - cell::Cell, io::{Read, Write}, str::FromStr, }; /// Describes the input/output formats for the data structures in the `sightglass-data` crate. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Format { /// The JSON format. Json, /// The CSV format. Csv { - /// Indicates whether the CSV headers are present during reading and writing. - headers: Cell, + /// Whether to include a header row when reading or writing. + headers: bool, }, } impl Format { /// Construct a CSV formatter; allows setting the `headers` parameter more easily. pub fn csv(headers: bool) -> Self { - Self::Csv { - headers: Cell::from(headers), - } + Self::Csv { headers } } /// Read a list of `T` using the selected format. @@ -39,7 +36,7 @@ impl Format { Format::Json => serde_json::from_reader(reader)?, Format::Csv { headers } => { let mut reader = ReaderBuilder::new() - .has_headers(headers.take()) + .has_headers(*headers) .from_reader(reader); reader.deserialize().map(|r| r.unwrap()).collect() } @@ -56,7 +53,7 @@ impl Format { Format::Json => serde_json::to_writer(writer, objects)?, Format::Csv { headers } => { let mut csv = csv::WriterBuilder::new() - .has_headers(headers.take()) + .has_headers(*headers) .from_writer(writer); for o in objects { csv.serialize(o)?; @@ -67,7 +64,7 @@ impl Format { Ok(()) } - /// Write a list of `T` using the selected format. + /// Write a single `T` using the selected format. pub fn write_one(&self, object: T, writer: W) -> Result<()> where T: Serialize, @@ -77,7 +74,7 @@ impl Format { Format::Json => serde_json::to_writer(writer, &object)?, Format::Csv { headers } => { let mut csv = csv::WriterBuilder::new() - .has_headers(headers.take()) + .has_headers(*headers) .from_writer(writer); csv.serialize(&object)?; csv.flush()?; @@ -102,9 +99,7 @@ impl FromStr for Format { fn from_str(s: &str) -> Result { match s { "json" => Ok(Format::Json), - "csv" => Ok(Format::Csv { - headers: Cell::from(true), - }), + "csv" => Ok(Format::Csv { headers: true }), _ => Err("output format must be either 'json' or 'csv'"), } }