[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: cargo\n    directory: \"/\"\n    schedule:\n      interval: monthly\n      time: \"04:00\"\n      timezone: Europe/Berlin\n    open-pull-requests-limit: 2\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n"
  },
  {
    "path": ".github/workflows/CICD.yml",
    "content": "name: CICD\n\nenv:\n  CICD_INTERMEDIATES_DIR: \"_cicd-intermediates\"\n  MSRV_FEATURES: \"\"\n\non:\n  workflow_dispatch:\n  pull_request:\n  push:\n    branches:\n      - master\n    tags:\n      - '*'\n\njobs:\n  crate_metadata:\n    name: Extract crate metadata\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v5\n    - name: Extract crate information\n      id: crate_metadata\n      run: |\n        cargo metadata --no-deps --format-version 1 | jq -r '\n          .packages[0] |\n          [\n            \"name=\" + .name,\n            \"version=\" + .version,\n            \"maintainer=\" + (.authors[0] // \"\"),\n            \"homepage=\" + (.homepage // \"\"),\n            \"msrv=\" + (.rust_version // \"\"),\n            \"bin-name=\" + ( (.targets[] | select(.kind[0] == \"bin\") | .name) // .name )\n          ] |\n          join(\"\\n\")\n        ' | tee -a $GITHUB_OUTPUT\n    outputs:\n      name: ${{ steps.crate_metadata.outputs.name }}\n      version: ${{ steps.crate_metadata.outputs.version }}\n      maintainer: ${{ steps.crate_metadata.outputs.maintainer }}\n      homepage: ${{ steps.crate_metadata.outputs.homepage }}\n      msrv: ${{ steps.crate_metadata.outputs.msrv }}\n      bin-name: ${{ steps.crate_metadata.outputs.bin-name }}\n\n  ensure_cargo_fmt:\n    name: Ensure 'cargo fmt' has been run\n    runs-on: ubuntu-24.04\n    steps:\n    - uses: dtolnay/rust-toolchain@stable\n      with:\n        components: rustfmt\n    - uses: actions/checkout@v5\n    - run: cargo fmt -- --check\n\n  min_version:\n    name: Minimum supported rust version\n    runs-on: ubuntu-24.04\n    needs: crate_metadata\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v5\n\n    - name: Install rust toolchain (v${{ needs.crate_metadata.outputs.msrv }})\n      uses: dtolnay/rust-toolchain@master\n      with:\n        toolchain: ${{ needs.crate_metadata.outputs.msrv }}\n        components: clippy\n    - name: Run clippy (on minimum supported rust version to prevent warnings we can't fix)\n      run: cargo clippy --locked --all-targets ${{ env.MSRV_FEATURES }}\n    - name: Run tests\n      run: cargo test --locked ${{ env.MSRV_FEATURES }}\n\n  build:\n    name: ${{ matrix.job.target }} (${{ matrix.job.os }})\n    runs-on: ${{ matrix.job.os }}\n    needs: crate_metadata\n    strategy:\n      fail-fast: false\n      matrix:\n        job:\n          - { target: aarch64-unknown-linux-gnu   , os: ubuntu-24.04, use-cross: true }\n          - { target: arm-unknown-linux-gnueabihf , os: ubuntu-24.04, use-cross: true }\n          - { target: arm-unknown-linux-musleabihf, os: ubuntu-24.04, use-cross: true }\n          - { target: i686-pc-windows-msvc        , os: windows-2022                  }\n          - { target: i686-unknown-linux-gnu      , os: ubuntu-24.04, use-cross: true }\n          - { target: i686-unknown-linux-musl     , os: ubuntu-24.04, use-cross: true }\n          - { target: x86_64-apple-darwin         , os: macos-15                      }\n          - { target: aarch64-apple-darwin        , os: macos-15                      }\n          # - { target: x86_64-pc-windows-gnu       , os: windows-2022                  }\n          - { target: x86_64-pc-windows-msvc      , os: windows-2022                  }\n          - { target: x86_64-unknown-linux-gnu    , os: ubuntu-24.04, use-cross: true }\n          - { target: x86_64-unknown-linux-musl   , os: ubuntu-24.04, use-cross: true }\n    env:\n      BUILD_CMD: cargo\n    steps:\n    - name: Checkout source code\n      uses: actions/checkout@v5\n\n    - name: Install prerequisites\n      shell: bash\n      run: |\n        case ${{ matrix.job.target }} in\n          arm-unknown-linux-*) sudo apt-get -y update ; sudo apt-get -y install gcc-arm-linux-gnueabihf ;;\n          aarch64-unknown-linux-gnu) sudo apt-get -y update ; sudo apt-get -y install gcc-aarch64-linux-gnu ;;\n        esac\n\n    - name: Install Rust toolchain\n      uses: dtolnay/rust-toolchain@stable\n      with:\n        targets: ${{ matrix.job.target }}\n\n    - name: Install cross\n      if: matrix.job.use-cross\n      uses: taiki-e/install-action@v2\n      with:\n        tool: cross\n\n    - name: Overwrite build command env variable\n      if: matrix.job.use-cross\n      shell: bash\n      run: echo \"BUILD_CMD=cross\" >> $GITHUB_ENV\n\n    - name: Show version information (Rust, cargo, GCC)\n      shell: bash\n      run: |\n        set -x\n        gcc --version || true\n        rustup -V\n        rustup toolchain list\n        rustup default\n        cargo -V\n        rustc -V\n\n    - name: Build\n      shell: bash\n      run: $BUILD_CMD build --locked --release --target=${{ matrix.job.target }}\n\n    - name: Set binary name & path\n      id: bin\n      shell: bash\n      run: |\n        # Figure out suffix of binary\n        EXE_suffix=\"\"\n        case ${{ matrix.job.target }} in\n          *-pc-windows-*) EXE_suffix=\".exe\" ;;\n        esac;\n\n        # Setup paths\n        BIN_NAME=\"${{ needs.crate_metadata.outputs.bin-name }}${EXE_suffix}\"\n        BIN_PATH=\"target/${{ matrix.job.target }}/release/${BIN_NAME}\"\n\n        # Let subsequent steps know where to find the binary\n        echo \"BIN_PATH=${BIN_PATH}\" >> $GITHUB_OUTPUT\n        echo \"BIN_NAME=${BIN_NAME}\" >> $GITHUB_OUTPUT\n\n    - name: Set testing options\n      id: test-options\n      shell: bash\n      run: |\n        # test only library unit tests and binary for arm-type targets\n        unset CARGO_TEST_OPTIONS\n        unset CARGO_TEST_OPTIONS ; case ${{ matrix.job.target }} in arm-* | aarch64-*) CARGO_TEST_OPTIONS=\"--bin ${{ steps.bin.outputs.BIN_NAME }}\" ;; esac;\n        echo \"CARGO_TEST_OPTIONS=${CARGO_TEST_OPTIONS}\" >> $GITHUB_OUTPUT\n\n    - name: Run tests\n      shell: bash\n      run: $BUILD_CMD test --locked --target=${{ matrix.job.target }} ${{ steps.test-options.outputs.CARGO_TEST_OPTIONS}}\n\n    - name: Create tarball\n      id: package\n      shell: bash\n      run: |\n        PKG_suffix=\".tar.gz\" ; case ${{ matrix.job.target }} in *-pc-windows-*) PKG_suffix=\".zip\" ;; esac;\n        PKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-v${{ needs.crate_metadata.outputs.version }}-${{ matrix.job.target }}\n        PKG_NAME=${PKG_BASENAME}${PKG_suffix}\n        echo \"PKG_NAME=${PKG_NAME}\" >> $GITHUB_OUTPUT\n\n        PKG_STAGING=\"${{ env.CICD_INTERMEDIATES_DIR }}/package\"\n        ARCHIVE_DIR=\"${PKG_STAGING}/${PKG_BASENAME}/\"\n        mkdir -p \"${ARCHIVE_DIR}\"\n        mkdir -p \"${ARCHIVE_DIR}/autocomplete\"\n\n        # Binary\n        cp \"${{ steps.bin.outputs.BIN_PATH }}\" \"$ARCHIVE_DIR\"\n\n        # README, LICENSE and CHANGELOG files\n        cp \"README.md\" \"LICENSE-MIT\" \"LICENSE-APACHE\" \"CHANGELOG.md\" \"$ARCHIVE_DIR\"\n\n        # Man page\n        cp 'doc/${{ needs.crate_metadata.outputs.name }}.1' \"$ARCHIVE_DIR\"\n\n        # Autocompletion files\n        cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.bash' \"$ARCHIVE_DIR/autocomplete/\"\n        cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.fish' \"$ARCHIVE_DIR/autocomplete/\"\n        cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'_${{ needs.crate_metadata.outputs.name }}.ps1' \"$ARCHIVE_DIR/autocomplete/\"\n        cp 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'_${{ needs.crate_metadata.outputs.name }}' \"$ARCHIVE_DIR/autocomplete/\"\n\n        # base compressed package\n        pushd \"${PKG_STAGING}/\" >/dev/null\n        case ${{ matrix.job.target }} in\n          *-pc-windows-*) 7z -y a \"${PKG_NAME}\" \"${PKG_BASENAME}\"/* | tail -2 ;;\n          *) tar czf \"${PKG_NAME}\" \"${PKG_BASENAME}\"/* ;;\n        esac;\n        popd >/dev/null\n\n        # Let subsequent steps know where to find the compressed package\n        echo \"PKG_PATH=${PKG_STAGING}/${PKG_NAME}\" >> $GITHUB_OUTPUT\n\n    - name: Create Debian package\n      id: debian-package\n      shell: bash\n      if: startsWith(matrix.job.os, 'ubuntu')\n      run: |\n        COPYRIGHT_YEARS=\"2018 - \"$(date \"+%Y\")\n        DPKG_STAGING=\"${{ env.CICD_INTERMEDIATES_DIR }}/debian-package\"\n        DPKG_DIR=\"${DPKG_STAGING}/dpkg\"\n        mkdir -p \"${DPKG_DIR}\"\n\n        DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}\n        DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }}-musl\n        case ${{ matrix.job.target }} in *-musl*) DPKG_BASENAME=${{ needs.crate_metadata.outputs.name }}-musl ; DPKG_CONFLICTS=${{ needs.crate_metadata.outputs.name }} ;; esac;\n        DPKG_VERSION=${{ needs.crate_metadata.outputs.version }}\n\n        unset DPKG_ARCH\n        case ${{ matrix.job.target }} in\n          aarch64-*-linux-*) DPKG_ARCH=arm64 ;;\n          arm-*-linux-*hf) DPKG_ARCH=armhf ;;\n          i686-*-linux-*) DPKG_ARCH=i686 ;;\n          x86_64-*-linux-*) DPKG_ARCH=amd64 ;;\n          *) DPKG_ARCH=notset ;;\n        esac;\n\n        DPKG_NAME=\"${DPKG_BASENAME}_${DPKG_VERSION}_${DPKG_ARCH}.deb\"\n        echo \"DPKG_NAME=${DPKG_NAME}\" >> $GITHUB_OUTPUT\n\n        # Binary\n        install -Dm755 \"${{ steps.bin.outputs.BIN_PATH }}\" \"${DPKG_DIR}/usr/bin/${{ steps.bin.outputs.BIN_NAME }}\"\n\n        # Man page\n        install -Dm644 'doc/${{ needs.crate_metadata.outputs.name }}.1' \"${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1\"\n        gzip -n --best \"${DPKG_DIR}/usr/share/man/man1/${{ needs.crate_metadata.outputs.name }}.1\"\n\n        # Autocompletion files\n        install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.bash' \"${DPKG_DIR}/usr/share/bash-completion/completions/${{ needs.crate_metadata.outputs.name }}\"\n        install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'${{ needs.crate_metadata.outputs.name }}.fish' \"${DPKG_DIR}/usr/share/fish/vendor_completions.d/${{ needs.crate_metadata.outputs.name }}.fish\"\n        install -Dm644 'target/${{ matrix.job.target }}/release/build/${{ needs.crate_metadata.outputs.name }}'*/out/'_${{ needs.crate_metadata.outputs.name }}' \"${DPKG_DIR}/usr/share/zsh/vendor-completions/_${{ needs.crate_metadata.outputs.name }}\"\n\n        # README and LICENSE\n        install -Dm644 \"README.md\" \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/README.md\"\n        install -Dm644 \"LICENSE-MIT\" \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-MIT\"\n        install -Dm644 \"LICENSE-APACHE\" \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/LICENSE-APACHE\"\n        install -Dm644 \"CHANGELOG.md\" \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog\"\n        gzip -n --best \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/changelog\"\n\n        cat > \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright\" <<EOF\n        Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n        Upstream-Name: ${{ needs.crate_metadata.outputs.name }}\n        Source: ${{ needs.crate_metadata.outputs.homepage }}\n\n        Files: *\n        Copyright: ${{ needs.crate_metadata.outputs.maintainer }}\n        Copyright: $COPYRIGHT_YEARS ${{ needs.crate_metadata.outputs.maintainer }}\n        License: Apache-2.0 or MIT\n\n        License: Apache-2.0\n          On Debian systems, the complete text of the Apache-2.0 can be found in the\n          file /usr/share/common-licenses/Apache-2.0.\n\n        License: MIT\n          Permission is hereby granted, free of charge, to any\n          person obtaining a copy of this software and associated\n          documentation files (the \"Software\"), to deal in the\n          Software without restriction, including without\n          limitation the rights to use, copy, modify, merge,\n          publish, distribute, sublicense, and/or sell copies of\n          the Software, and to permit persons to whom the Software\n          is furnished to do so, subject to the following\n          conditions:\n          .\n          The above copyright notice and this permission notice\n          shall be included in all copies or substantial portions\n          of the Software.\n          .\n          THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF\n          ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED\n          TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A\n          PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT\n          SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n          CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n          OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR\n          IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\n          DEALINGS IN THE SOFTWARE.\n        EOF\n          chmod 644 \"${DPKG_DIR}/usr/share/doc/${DPKG_BASENAME}/copyright\"\n\n          # control file\n          mkdir -p \"${DPKG_DIR}/DEBIAN\"\n          cat > \"${DPKG_DIR}/DEBIAN/control\" <<EOF\n        Package: ${DPKG_BASENAME}\n        Version: ${DPKG_VERSION}\n        Section: utils\n        Priority: optional\n        Maintainer: ${{ needs.crate_metadata.outputs.maintainer }}\n        Homepage: ${{ needs.crate_metadata.outputs.homepage }}\n        Architecture: ${DPKG_ARCH}\n        Provides: ${{ needs.crate_metadata.outputs.name }}\n        Conflicts: ${DPKG_CONFLICTS}\n        Description: A command-line benchmarking tool\n        EOF\n\n        DPKG_PATH=\"${DPKG_STAGING}/${DPKG_NAME}\"\n        echo \"DPKG_PATH=${DPKG_PATH}\" >> $GITHUB_OUTPUT\n\n        # build dpkg\n        fakeroot dpkg-deb --build \"${DPKG_DIR}\" \"${DPKG_PATH}\"\n\n    - name: \"Artifact upload: tarball\"\n      uses: actions/upload-artifact@master\n      with:\n        name: ${{ steps.package.outputs.PKG_NAME }}\n        path: ${{ steps.package.outputs.PKG_PATH }}\n\n    - name: \"Artifact upload: Debian package\"\n      uses: actions/upload-artifact@master\n      if: steps.debian-package.outputs.DPKG_NAME\n      with:\n        name: ${{ steps.debian-package.outputs.DPKG_NAME }}\n        path: ${{ steps.debian-package.outputs.DPKG_PATH }}\n\n    - name: Check for release\n      id: is-release\n      shell: bash\n      run: |\n        unset IS_RELEASE ; if [[ $GITHUB_REF =~ ^refs/tags/v[0-9].* ]]; then IS_RELEASE='true' ; fi\n        echo \"IS_RELEASE=${IS_RELEASE}\" >> $GITHUB_OUTPUT\n\n    - name: Publish archives and packages\n      uses: softprops/action-gh-release@v2\n      if: steps.is-release.outputs.IS_RELEASE\n      with:\n        files: |\n          ${{ steps.package.outputs.PKG_PATH }}\n          ${{ steps.debian-package.outputs.DPKG_PATH }}\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n  winget:\n    name: Publish to Winget\n    runs-on: ubuntu-latest\n    needs: build\n    if: startsWith(github.ref, 'refs/tags/v')\n    steps:\n      - uses: vedantmgoyal2009/winget-releaser@v2\n        with:\n          identifier: sharkdp.hyperfine\n          installers-regex: '-pc-windows-msvc\\.zip$'\n          token: ${{ secrets.WINGET_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "\n/target/\n**/*.rs.bk\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# v1.20.0\n\n## Features\n\n- Add `--reference-name` option to give a meaningful name to the reference command, see #808 (@niklasdewally)\n- The `--ignore-failure` option now supports a comma-separated list of exit codes to ignore (e.g., `--ignore-failure=1,2`), see #836 (@sharkdp)\n- Python scripts: Add `--time-unit` option to `advanced_statistics.py` (@sharkdp)\n- Python scripts: Add new `plot_benchmarks.py` script for plotting collections of benchmarks, see #806 (@marxin)\n\n## Bugfixes\n\n- Fix bug where naming individual commands with parameter scan was not working correctly, see #794 (@teofr)\n\n## Other\n\n- Restrict `cat` tests to Unix environments, see #776 and #777 (@ritvikos)\n\n# v1.19.0\n\n## Features\n\n- Add a new `--reference <cmd>` option to specify a reference command for the relative speed comparison, see #579, #577 and #744 (@sharkdp)\n- Add `--conclude` argument (analog to `--prepare`), see #565 and #719 (@jackoconnordev)\n- Allow `--output=…` to appear once for each command, enabling use cases like `hyperfine --output=null my-cmd --output=./file.log my-cmd`, see #529 and #775 (@sharkdp)\n- The environment variable `$HYPERFINE_ITERATION` will now contain the current iteration number for each benchmarked command, see #775 (@sharkdp)\n- Add iteration information to failure error message, see #771 and #772 (@sharkdp)\n- Python scripts: \n  - legend modification parameters and output DPI, see #758 (@Spreadcat)\n  - Nicer whiskers plot, see #727 (@serpent7776)\n\n## Bugfixes\n\n- ETA not clearly visible on terminals with a block cursor, see #698 and #699 (@overclockworked64)\n- Fix zsh completions, see #717 (@xzfc)\n\n## Other\n\n- Build binaries for aarch64-apple-darwin, see #728 (@Phault)\n- Various cleanups (@hamirmahal, @one230six)\n\n# v1.18.0\n\n## Features\n\n- Add support for microseconds via `--time-unit microsecond`, see #684 (@sharkdp)\n\n## Bugfixes\n\n- Proper argument quoting on Windows CMD, see #296 and #678 (@PedroWitzel)\n\n\n# v1.17.0\n\n## Features\n\n- Add new `--sort` option to control the order in the rel. speed comparison and in markup export formats, see #601, #614, #655 (@sharkdp)\n- Parameters which are unused in the command line are now displayed in parentheses, see #600 and #644 (@sharkdp).\n- Added `--log-count` option for histogram plots, see `scripts/plot_histogram.py` (@sharkdp)\n\n## Changes\n\n- Updated hyperfine to use `windows-sys` instead of the unmaintained `winapi`, see #624, #639, #636, #641 (@clemenswasser)\n- Silenced deprecation warning in Python scripts, see #633 (@nicovank)\n- Major update of the man page, see 0ce6578, #647 (@sharkdp)\n\n## Bugfixes\n\n- Do not export intermediate results to stdout when using `-` as a file name, see #640 and #643 (@sharkdp)\n- Markup exporting does not fail if benchmark results are zero, see #642 (@sharkdp)\n\n\n# v1.16.1\n\n## Bugfixes\n\n- Fix line-wrapping of `--help` text (@sharkdp)\n- Fix `--input=null` (@sharkdp)\n\n\n# v1.16.0\n\n## Features\n\n- Added new `--input` option, see #541 and #563 (@snease)\n- Added possibility to specify `-` as the filename in the\n  `--export-*` options, see #615 and #623 (@humblepenguinn)\n\n## Changes\n\n- Improve hints for outlier warnings if `--warmup` or `--prepare` are in use already,\n  see #570 (@sharkdp)\n\n## Bugfixes\n\n- Fix uncolored output on Windows if `TERM` is not set, see #583 (@nabijaczleweli)\n- On Windows, only run `cmd.exe` with the `/C` option. Use `-c` for all other shells.\n  See #568 and #582 (@FilipAndersson245)\n\n## Other\n\n- Thanks to @berombau for working on dependency upgrades, see #584\n- Fixed installationm on Windows, see #595 and #596 (@AntoniosBarotsis)\n\n\n# v1.15.0\n\n## Features\n\n- Disable colorized output in case of `TERM=dumb` or `NO_COLOR=1`, see #542 and #555 (@nabijaczleweli)\n- Add new (experimental) `--min-benchmarking-time <secs>` option, see #527 (@sharkdp)\n\n## Bugfixes\n\n- Fix user and kernel times on Windows, see #368 and #538 (@clemenswasser)\n\n## Other\n\n- Improve `--help` texts of `--export-*` options, see #506 and #522 (@Engineer-of-Efficiency)\n\n\n# v1.14.0\n\n## Features\n\n- Add a new `--output={null,pipe,inherit,<FILE>}` option to control\n  where the output of the benchmarked program is redirected (if at all),\n  see #377 and #509 (@tavianator, originally suggested by @BurntSushi)\n- Add Emacs org-mode as a new export format, see #491 (@ppaulweber)\n\n\n# v1.13.0\n\n## Features\n\n- Added a new `--shell=none`/`-N` option to disable the intermediate\n  shell for executing the benchmarked commands. Hyperfine normally\n  measures and subtracts the shell spawning time, but the intermediate\n  shell always introduces a certain level of measurement noise. Using\n  `--shell=none`/`-N` allows users to benchmark very fast commands\n  (with a runtime on the order of a few milliseconds). See #336, #429,\n  and #487 (@cipriancraciun and @sharkdp)\n- Added `--setup`/`-s` option that can be used to run `make all` or\n  similar. It runs once per set of tests, like `--cleanup`/`-c` (@avar)\n- Added new `plot_progression.py` script to debug background interference\n  effects.\n\n## Changes\n\n- Breaking change: the `-s` short option for `--style` is now used for\n  the new `--setup` option.\n- The environment offset randomization is now also available on Windows,\n  see #484\n\n## Other\n\n- Improved documentation and test coverage, cleaned up code base for\n  future improvements.\n\n\n# v1.12.0\n\n## Features\n\n- `--command-name` can now take parameter names from `--parameter-*` options, see #351 and #391 (@silathdiir)\n- Exit codes (or signals) are now printed in cases of command failures, see #342 (@KaindlJulian)\n- Exit codes are now part of the JSON output, see #371 (@JordiChauzi)\n- Colorized output should now be enabled on Windows by default, see #427\n\n## Changes\n\n- When `--export-*` commands are used, result files are created before benchmark execution\n  to fail early in case of, e.g., wrong permissions. See #306 (@s1ck).\n- When `--export-*` options are used, result files are written after each individual\n  benchmark command instead of writing after all benchmarks have finished. See #306 (@s1ck).\n- Reduce number of shell startup time measurements from 200 to 50, generally speeding up benchmarks. See #378\n- User and system time are now in consistent time units, see #408 and #409 (@film42)\n\n\n\n# v1.11.0\n\n## Features\n\n- The `-L`/`--parameter-list` option can now be specified multiple times to\n  evaluate all possible combinations of the listed parameters:\n\n  ``` bash\n  hyperfine -L number 1,2 -L letter a,b,c \\\n      \"echo {number}{letter}\" \\\n      \"printf '%s\\n' {number}{letter}\"\n  # runs 12 benchmarks: 2 commands (echo and printf) times 6 combinations of\n  # the \"letter\" and \"number\" parameters\n  ```\n\n  See: #253, #318 (@wchargin)\n\n- Add CLI option to identify a command with a custom name, see #326 (@scampi)\n\n## Changes\n\n- When parameters are used with `--parameter-list` or `--parameter-scan`, the JSON export format\n  now contains a dictionary `parameters` instead of a single key `parameter`. See #253, #318.\n- The `plot_parametrized.py` script now infers the parameter name, and its `--parameter-name`\n  argument has been deprecated. See #253, #318.\n\n## Bugfixes\n\n- Fix a bug in the outlier detection which would only detect \"slow outliers\" but not the fast\n  ones (runs that are much faster than the rest of the benchmarking runs), see #329\n- Better error messages for very fast commands that would lead to inf/nan results in the relative\n  speed comparison, see #319\n- Show error message if `--warmup` or `--*runs` arguments can not be parsed, see #337\n- Keep output colorized when the output is not interactive and `--style=full` or `--style=color` is used.\n\n\n# v1.10.0\n\n## Features\n\n- Hyperfine now comes with shell completion files for Bash, Zsh, Fish\n  and PowerShell, see #290 (@four0000four).\n- Hyperfine now comes with a basic man page, see #257 (@cadeef)\n- During execution of benchmarks, hyperfine will now set a `HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET` environment variable in order to randomize the memory layout. See #235 and #241 for references and details.\n- A few enhancements for the histogram plotting scripts and the\n  advanced statistics script\n- Updates for the `plot_whisker.py` script, see #275 (@ghaiklor)\n\n## Bugfixes\n\n- Fix Spin Icon on Windows, see #229\n- A few typos have been fixed, see #292 (@McMartin)\n\n## Packaging\n\n- `hyperfine` is now available on MacPorts for macOS, see #281 (@herbygillot)\n- `hyperfine` is now available on OpenBSD, see #289 (@minusf)\n\nPackage authors: note that Hyperfine now comes with a set of shell completion files and a man page (see above)\n\n# v1.9.0\n\n## Features\n\n- The new `--parameter-list <VAR> <VALUES>` option can be used to run\n  a parametrized benchmark on a user-specified list of values.\n  This is similar to `--parameter-scan <VAR> <MIN> <MAX>`, but doesn't\n  necessarily required numeric arguments.\n\n  ``` bash\n  hyperfine --parameter-list compiler \"gcc,clang\" \\\n      \"{compiler} -O2 main.cpp\"\n  ```\n\n  See: #227, #234 (@JuanPotato)\n\n- Added `none` as a possible choice for the `--style` option to\n  run `hyperfine` without any output, see #193 (@knidarkness)\n\n- Added a few new scripts for plotting various types of benchmark\n  results (https://github.com/sharkdp/hyperfine/tree/master/scripts)\n\n## Changes\n\n- The `--prepare` command is now also run during the warmup\n  phase, see #182 (@sseemayer)\n\n- Better estimation of the remaining benchmark time due to an update\n  of the `indicatif` crate.\n\n## Other\n\n- `hyperfine` is now available on NixOS, see #240 (@tuxinaut)\n\n# v1.8.0\n\n## Features\n\n- The `--prepare <CMD>` option can now be specified multiple times to\n  run specific preparation commands for each of the benchmarked programs:\n\n  ``` bash\n  hyperfine --prepare \"make clean; git checkout master\"  \"make\" \\\n            --prepare \"make clean; git checkout feature\" \"make\"\n  ```\n\n  See: #216, #218 (@iamsauravsharma)\n\n- Added a new [`welch_ttest.py`](https://github.com/sharkdp/hyperfine/blob/master/scripts/welch_ttest.py) script to test whether or not the two benchmark\n  results are the same, see #222 (@uetchy)\n\n- The Markdown export has been improved. The relative speed is now exported\n  with a higher precision (see #208) and includes the standard deviation\n  (see #225).\n\n## Other\n\n- Improved documentation for [`scripts`](https://github.com/sharkdp/hyperfine/tree/master/scripts) folder (@matthieusb)\n\n# v1.7.0\n\n## Features\n\n- Added a new `-D`,`--parameter-step-size` option that can be used to control\n  the step size for `--parameter-scan` benchmarks. In addition, decimal numbers\n  are now allowed for parameter scans. For example, the following command runs\n  `sleep 0.3`, `sleep 0.5` and `sleep 0.7`:\n  ``` bash\n  hyperfine --parameter-scan delay 0.3 0.7 -D 0.2 'sleep {delay}'\n  ```\n  For more details, see #184 (@piyushrungta25)\n\n## Other\n\n- hyperfine is now in the official Alpine repositories, see #177 (@maxice8, @5paceToast)\n- hyperfine is now in the official Fedora repositories, see #196 (@ignatenkobrain)\n- hyperfine is now in the official Arch Linux repositories\n- hyperfine can be installed on FreeBSD, see #204 (@0mp)\n- Enabled LTO for slightly smaller binary sizes, see #179 (@Calinou)\n- Various small improvements all over the code base, see #194 (@phimuemue)\n\n# v1.6.0\n\n## Features\n\n- Added a `-c, --cleanup <CMD>` option to execute `CMD` after the completion of all benchmarking runs for a given command. This is useful if the commands to be benchmarked produce artifacts that need to be cleaned up. See #91 (@RalfJung and @colinwahl)\n- Add parameter values (for `--parameter-scan` benchmarks) to exported CSV and JSON files. See #131 (@bbannier)\n- Added AsciiDoc export option, see #137 (@5paceToast)\n- The relative speed is now part of the Markdown export, see #127 (@mathiasrw and @sharkdp).\n- The *median* run time is now exported via CSV and JSON, see #171 (@hosewiejacke and @sharkdp).\n\n## Other\n\n- Hyperfine has been updated to Rust 2018 (@AnderEnder). The minimum supported Rust version is now 1.31.\n\n# v1.5.0\n\n## Features\n\n- Show the number of runs in `hyperfine`s output (@tcmal)\n- Added two Python scripts to post-process exported benchmark results (see [`scripts/`](https://github.com/sharkdp/hyperfine/tree/master/scripts) folder)\n\n## Other\n\n- Refined `--help` text for the `--export-*` flags (@psteinb)\n- Added Snapcraft file (@popey)\n- Small improvements in the progress bar \"experience\".\n\n# v1.4.0\n\n## Features\n\n- Added `-S`/`--shell` option to override the default shell, see #61 (@mqudsi and @jasonpeacock)\n- Added `-u`/`--time-unit` option to change the unit of time (`second` or `millisecond`), see #80 (@jasonpeacock)\n- Markdown export auto-selects time unit, see #71 (@jasonpeacock)\n\n# v1.3.0\n\n## Feature\n\n- Compute and print standard deviation of the speed ratio, see #83 (@Shnatsel)\n- More compact output format, see #70  (@jasonpeacock)\n- Added `--style=color`, see #70 (@jasonpeacock)\n- Added options to specify the max/exact numbers of runs, see #77 (@orium)\n\n## Bugfixes\n\n- Change Windows `cmd` interpreter to `cmd.exe` to prevent accidentally calling other programs, see #74 (@tathanhdinh)\n\n## Other\n\n- Binary releases for Windows are now available, see #87\n\n# v1.2.0\n\n- Support parameters in preparation commands, see #68 (@siiptuo)\n- Updated dependencies, see #69. The minimum required Rust version is now 1.24.\n\n# v1.1.0\n\n* Added `--show-output` option (@chrisduerr and @sevagh)\n* Refactoring work (@stevepentland)\n\n# v1.0.0\n\n## Features\n\n* Support for various export-formats like CSV, JSON and Markdown - see #38, #44, #49, #42 (@stevepentland)\n* Summary output that compares the different benchmarks, see #6 (@stevepentland)\n* Parameterized benchmarks via `-P`, `--parameter-scan <VAR> <MIN> <MAX>`, see #19\n\n## Thanks\n\nI'd like to say a big THANK YOU to @stevepentland for implementing new features,\nfor reviewing pull requests and for giving very valuable feedback.\n\n# v0.5.0\n\n* Proper Windows support (@stevepentland)\n* Added `--style auto/basic/nocolor/full` option (@stevepentland)\n* Correctly estimate the full execution time, see #27 (@rleungx)\n* Added Void Linux install instructions (@wpbirney)\n\n# v0.4.0\n\n- New `--style` option to disable output coloring and interactive CLI features, see #24 (@stevepentland)\n- Statistical outlier detection, see #23 #18\n\n# v0.3.0\n\n## Features\n\n- In addition to 'real' (wall clock) time, Hyperfine can now also measure 'user' and 'system' time (see #5).\n- Added `--prepare` option that can be used to clear up disk caches before timing runs, for example (see #8).\n\n## Other\n\n- [Arch Linux package](https://aur.archlinux.org/packages/hyperfine) for Hyperfine (@jD91mZM2).\n- Ubuntu/Debian packages are now are available.\n\n# v0.2.0\n\nInitial public release\n"
  },
  {
    "path": "CITATION.cff",
    "content": "cff-version: 1.2.0\ntitle: hyperfine\nmessage: >-\n  If you use this software in scientific\n  publications, please consider citing it using the\n  metadata from this file.\ntype: software\nauthors:\n  - given-names: David\n    family-names: Peter\n    email: mail@david-peter.de\n    orcid: 'https://orcid.org/0000-0001-7950-9915'\nrepository-code: 'https://github.com/sharkdp/hyperfine'\nabstract: A command-line benchmarking tool.\nlicense: MIT\nversion: 1.16.1\ndate-released: '2023-03-21'\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nauthors = [\"David Peter <mail@david-peter.de>\"]\ncategories = [\"command-line-utilities\"]\ndescription = \"A command-line benchmarking tool\"\nhomepage = \"https://github.com/sharkdp/hyperfine\"\nlicense = \"MIT OR Apache-2.0\"\nname = \"hyperfine\"\nreadme = \"README.md\"\nrepository = \"https://github.com/sharkdp/hyperfine\"\nversion = \"1.20.0\"\nedition = \"2018\"\nbuild = \"build.rs\"\nrust-version = \"1.88.0\"\n\n[features]\n# Use the nightly feature windows_process_extensions_main_thread_handle\nwindows_process_extensions_main_thread_handle = []\n\n[dependencies]\ncolored = \"2.1\"\nindicatif = \"=0.17.4\"\nstatistical = \"1.0\"\ncsv = \"1.3\"\nserde = { version = \"1.0\", features = [\"derive\"] }\nserde_json = \"1.0\"\nrust_decimal = \"1.36\"\nrand = \"0.8\"\nshell-words = \"1.0\"\nthiserror = \"2.0\"\nanyhow = \"1.0\"\n\n[target.'cfg(not(windows))'.dependencies]\nlibc = \"0.2\"\n\n[target.'cfg(windows)'.dependencies]\nwindows-sys = { version = \"0.59\", features = [\n    \"Win32_Foundation\",\n    \"Win32_Security\",\n    \"Win32_System_JobObjects\",\n    \"Win32_System_LibraryLoader\",\n    \"Win32_System_Threading\",\n] }\n\n[target.'cfg(all(windows, not(windows_process_extensions_main_thread_handle)))'.dependencies]\nonce_cell = \"1.19\"\n\n[target.'cfg(target_os=\"linux\")'.dependencies]\nnix = { version = \"0.29\", features = [\"zerocopy\"] }\n\n[dependencies.clap]\nversion = \"4\"\ndefault-features = false\nfeatures = [\n    \"suggestions\",\n    \"color\",\n    \"wrap_help\",\n    \"cargo\",\n    \"help\",\n    \"usage\",\n    \"error-context\",\n]\n\n[dev-dependencies]\napprox = \"0.5\"\nassert_cmd = \"2.0\"\ninsta = { version = \"1.41.1\", features = [\"yaml\"] }\npredicates = \"3.1\"\ntempfile = \"3.23\"\n\n[profile.dev.package]\ninsta.opt-level = 3\nsimilar.opt-level = 3\n\n[build-dependencies]\nclap = \"4.5.48\"\nclap_complete = \"4.2.1\"\n\n[profile.release]\nlto = true\nstrip = true\ncodegen-units = 1\n"
  },
  {
    "path": "LICENSE-APACHE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "LICENSE-MIT",
    "content": "MIT License\n\nCopyright (c) 2018-2022 David Peter, and all hyperfine contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# hyperfine\n[![CICD](https://github.com/sharkdp/hyperfine/actions/workflows/CICD.yml/badge.svg)](https://github.com/sharkdp/hyperfine/actions/workflows/CICD.yml)\n[![Version info](https://img.shields.io/crates/v/hyperfine.svg)](https://crates.io/crates/hyperfine)\n[中文](https://github.com/chinanf-boy/hyperfine-zh)\n\nA command-line benchmarking tool.\n\n**Demo**: Benchmarking [`fd`](https://github.com/sharkdp/fd) and\n[`find`](https://www.gnu.org/software/findutils/):\n\n![hyperfine](https://i.imgur.com/z19OYxE.gif)\n\n### Sponsors\n\nA special *thank you* goes to our biggest <a href=\"doc/sponsors.md\">sponsor</a>:<br>\n\n<a href=\"https://www.warp.dev/hyperfine\">\n  <img src=\"doc/sponsors/warp-logo.png\" width=\"200\" alt=\"Warp\">\n  <br>\n  <strong>Warp, the intelligent terminal</strong>\n  <br>\n  <sub>Available on MacOS, Linux, Windows</sub>\n</a>\n\n## Features\n\n* Statistical analysis across multiple runs.\n* Support for arbitrary shell commands.\n* Constant feedback about the benchmark progress and current estimates.\n* Warmup runs can be executed before the actual benchmark.\n* Cache-clearing commands can be set up before each timing run.\n* Statistical outlier detection to detect interference from other programs and caching effects.\n* Export results to various formats: CSV, JSON, Markdown, AsciiDoc.\n* Parameterized benchmarks (e.g. vary the number of threads).\n* Cross-platform\n\n## Usage\n\n### Basic benchmarks\n\nTo run a benchmark, you can simply call `hyperfine <command>...`. The argument(s) can be any\nshell command. For example:\n```sh\nhyperfine 'sleep 0.3'\n```\n\nHyperfine will automatically determine the number of runs to perform for each command. By default,\nit will perform *at least* 10 benchmarking runs and measure for at least 3 seconds. To change this,\nyou can use the `-r`/`--runs` option:\n```sh\nhyperfine --runs 5 'sleep 0.3'\n```\n\nIf you want to compare the runtimes of different programs, you can pass multiple commands:\n```sh\nhyperfine 'hexdump file' 'xxd file'\n```\n\n### Warmup runs and preparation commands\n\nFor programs that perform a lot of disk I/O, the benchmarking results can be heavily influenced\nby disk caches and whether they are cold or warm.\n\nIf you want to run the benchmark on a warm cache, you can use the `-w`/`--warmup` option to\nperform a certain number of program executions before the actual benchmark:\n```sh\nhyperfine --warmup 3 'grep -R TODO *'\n```\n\nConversely, if you want to run the benchmark for a cold cache, you can use the `-p`/`--prepare`\noption to run a special command before *each* timing run. For example, to clear harddisk caches\non Linux, you can run\n```sh\nsync; echo 3 | sudo tee /proc/sys/vm/drop_caches\n```\nTo use this specific command with hyperfine, call `sudo -v` to temporarily gain sudo permissions\nand then call:\n```sh\nhyperfine --prepare 'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches' 'grep -R TODO *'\n```\n\n### Parameterized benchmarks\n\nIf you want to run a series of benchmarks where a single parameter is varied (say, the number of\nthreads), you can use the `-P`/`--parameter-scan` option and call:\n```sh\nhyperfine --prepare 'make clean' --parameter-scan num_threads 1 12 'make -j {num_threads}'\n```\nThis also works with decimal numbers. The `-D`/`--parameter-step-size` option can be used\nto control the step size:\n```sh\nhyperfine --parameter-scan delay 0.3 0.7 -D 0.2 'sleep {delay}'\n```\nThis runs `sleep 0.3`, `sleep 0.5` and `sleep 0.7`.\n\nFor non-numeric parameters, you can also supply a list of values with the `-L`/`--parameter-list`\noption:\n```\nhyperfine -L compiler gcc,clang '{compiler} -O2 main.cpp'\n```\n\n### Intermediate shell\n\nBy default, commands are executed using a predefined shell (`/bin/sh` on Unix, `cmd.exe` on Windows).\nIf you want to use a different shell, you can use the `-S, --shell <SHELL>` option:\n```sh\nhyperfine --shell zsh 'for i in {1..10000}; do echo test; done'\n```\n\nNote that hyperfine always *corrects for the shell spawning time*. To do this, it performs a calibration\nprocedure where it runs the shell with an empty command (multiple times), to measure the startup time\nof the shell. It will then subtract this time from the total to show the actual time used by the command\nin question.\n\nIf you want to run a benchmark *without an intermediate shell*, you can use the `-N` or `--shell=none`\noption. This is helpful for very fast commands (< 5 ms) where the shell startup overhead correction would\nproduce a significant amount of noise. Note that you cannot use shell syntax like `*` or `~` in this case.\n```\nhyperfine -N 'grep TODO /home/user'\n```\n\n\n### Shell functions and aliases\n\nIf you are using bash, you can export shell functions to directly benchmark them with hyperfine:\n\n```bash\nmy_function() { sleep 1; }\nexport -f my_function\nhyperfine --shell=bash my_function\n```\n\nOtherwise, inline them into or source them from the benchmarked program:\n\n```sh\nhyperfine 'my_function() { sleep 1; }; my_function'\n\necho 'alias my_alias=\"sleep 1\"' > /tmp/my_alias.sh\nhyperfine '. /tmp/my_alias.sh; my_alias'\n```\n\n### Exporting results\n\nHyperfine has multiple options for exporting benchmark results to CSV, JSON, Markdown and other\nformats (see `--help` text for details).\n\n#### Markdown\n\nYou can use the `--export-markdown <file>` option to create tables like the following:\n\n| Command | Mean [s] | Min [s] | Max [s] | Relative |\n|:---|---:|---:|---:|---:|\n| `find . -iregex '.*[0-9]\\.jpg$'` | 2.275 ± 0.046 | 2.243 | 2.397 | 9.79 ± 0.22 |\n| `find . -iname '*[0-9].jpg'` | 1.427 ± 0.026 | 1.405 | 1.468 | 6.14 ± 0.13 |\n| `fd -HI '.*[0-9]\\.jpg$'` | 0.232 ± 0.002 | 0.230 | 0.236 | 1.00 |\n\n#### JSON\n\nThe JSON output is useful if you want to analyze the benchmark results in more detail. The\n[`scripts/`](https://github.com/sharkdp/hyperfine/tree/master/scripts) folder includes a lot\nof helpful Python programs to further analyze benchmark results and create helpful\nvisualizations, like a histogram of runtimes or a whisker plot to compare\nmultiple benchmarks:\n\n| ![](doc/histogram.png) | ![](doc/whisker.png) |\n|---:|---:|\n\n\n### Detailed benchmark flowchart\n\nThe following chart explains the execution order of various timing runs when using options\nlike `--warmup`, `--prepare <cmd>`, `--setup <cmd>` or `--cleanup <cmd>`:\n\n![](doc/execution-order.png)\n\n## Installation\n\n[![Packaging status](https://repology.org/badge/vertical-allrepos/hyperfine.svg?columns=3&exclude_unsupported=1)](https://repology.org/project/hyperfine/versions)\n\n### On Ubuntu\n\nOn Ubuntu, hyperfine can be installed [from the official repositories](https://launchpad.net/ubuntu/+source/rust-hyperfine):\n```\napt install hyperfine\n```\n\nAlternatively, for the latest version, you can download the appropriate `.deb` package from the [Release page](https://github.com/sharkdp/hyperfine/releases) and install it via `dpkg`:\n```\nwget https://github.com/sharkdp/hyperfine/releases/download/v1.20.0/hyperfine_1.20.0_amd64.deb\nsudo dpkg -i hyperfine_1.20.0_amd64.deb\n```\n\n### On Fedora\n\nOn Fedora, hyperfine can be installed from the official repositories:\n\n```sh\ndnf install hyperfine\n```\n\n### On Alpine Linux\n\nOn Alpine Linux, hyperfine can be installed [from the official repositories](https://pkgs.alpinelinux.org/packages?name=hyperfine):\n```\napk add hyperfine\n```\n\n### On Arch Linux\n\nOn Arch Linux, hyperfine can be installed [from the official repositories](https://archlinux.org/packages/extra/x86_64/hyperfine/):\n```\npacman -S hyperfine\n```\n\n### On Debian Linux\n\nOn Debian Linux, hyperfine can be installed [from the official repositories](https://packages.debian.org/hyperfine):\n```\napt install hyperfine\n```\n\n### On Exherbo Linux\n\nOn Exherbo Linux, hyperfine can be installed [from the rust repositories](https://gitlab.exherbo.org/exherbo/rust/-/tree/master/packages/sys-apps/hyperfine):\n```\ncave resolve -x repository/rust\ncave resolve -x hyperfine\n```\n\n### On Funtoo Linux\n\nOn Funtoo Linux, hyperfine can be installed [from core-kit](https://github.com/funtoo/core-kit/tree/1.4-release/app-benchmarks/hyperfine):\n```\nemerge app-benchmarks/hyperfine\n```\n\n### On NixOS\n\nOn NixOS, hyperfine can be installed [from the official repositories](https://nixos.org/nixos/packages.html?query=hyperfine):\n```\nnix-env -i hyperfine\n```\n\n### On Flox\n\nOn Flox, hyperfine can be installed as follows.\n```\nflox install hyperfine\n```\nHyperfine's version in Flox follows that of Nix.\n\n### On openSUSE\n\nOn openSUSE, hyperfine can be installed [from the official repositories](https://software.opensuse.org/package/hyperfine):\n```\nzypper install hyperfine\n```\n\n### On Void Linux\n\nHyperfine can be installed via xbps\n\n```\nxbps-install -S hyperfine\n```\n\n### On macOS\n\nHyperfine can be installed via [Homebrew](https://brew.sh):\n```\nbrew install hyperfine\n```\n\nOr you can install using [MacPorts](https://www.macports.org):\n```\nsudo port selfupdate\nsudo port install hyperfine\n```\n\n### On FreeBSD\n\nHyperfine can be installed via pkg:\n```\npkg install hyperfine\n```\n\n### On OpenBSD\n\n```\ndoas pkg_add hyperfine\n```\n\n### On Windows\n\nHyperfine can be installed via [Chocolatey](https://community.chocolatey.org/packages/hyperfine), [Scoop](https://scoop.sh/#/apps?q=hyperfine&s=0&d=1&o=true&id=8f7c10f75ecf5f9e42a862c615257328e2f70f61), or [Winget](https://github.com/microsoft/winget-pkgs/tree/master/manifests/s/sharkdp/hyperfine):\n```\nchoco install hyperfine\n```\n```\nscoop install hyperfine\n```\n```\nwinget install hyperfine\n```\n\n### With conda\n\nHyperfine can be installed via [`conda`](https://conda.io/en/latest/) from the [`conda-forge`](https://anaconda.org/conda-forge/hyperfine) channel:\n```\nconda install -c conda-forge hyperfine\n```\n\n### With cargo (Linux, macOS, Windows)\n\nHyperfine can be installed from source via [cargo](https://doc.rust-lang.org/cargo/):\n```\ncargo install --locked hyperfine\n```\n\nMake sure that you use Rust 1.76 or newer.\n\n### From binaries (Linux, macOS, Windows)\n\nDownload the corresponding archive from the [Release page](https://github.com/sharkdp/hyperfine/releases).\n\n## Alternative tools\n\nHyperfine is inspired by [bench](https://github.com/Gabriella439/bench).\n\n## Integration with other tools\n\n[Chronologer](https://github.com/dandavison/chronologer) is a tool that uses `hyperfine` to\nvisualize changes in benchmark timings across your Git history.\n\n[Bencher](https://github.com/bencherdev/bencher) is a continuous benchmarking tool that supports `hyperfine` to\ntrack benchmarks and catch performance regressions in CI.\n\nDrop hyperfine JSON outputs onto the [Venz](https://try.venz.dev) chart to visualize the results,\nand manage hyperfine configurations.\n\nMake sure to check out the [`scripts` folder](https://github.com/sharkdp/hyperfine/tree/master/scripts)\nin this repository for a set of tools to work with `hyperfine` benchmark results.\n\n## Origin of the name\n\nThe name *hyperfine* was chosen in reference to the hyperfine levels of caesium 133 which play a crucial role in the\n[definition of our base unit of time](https://en.wikipedia.org/wiki/Second#History_of_definition)\n— the second.\n\n## Citing hyperfine\n\nThank you for considering to cite hyperfine in your research work. Please see the information\nin the sidebar on how to properly cite hyperfine.\n\n## License\n\n`hyperfine` is dual-licensed under the terms of the MIT License and the Apache License 2.0.\n\nSee the [LICENSE-APACHE](LICENSE-APACHE) and [LICENSE-MIT](LICENSE-MIT) files for details.\n"
  },
  {
    "path": "build.rs",
    "content": "use std::fs;\n\nuse clap_complete::{generate_to, Shell};\n\ninclude!(\"src/cli.rs\");\n\nfn main() {\n    let var = std::env::var_os(\"SHELL_COMPLETIONS_DIR\").or_else(|| std::env::var_os(\"OUT_DIR\"));\n    let outdir = match var {\n        None => return,\n        Some(outdir) => outdir,\n    };\n    fs::create_dir_all(&outdir).unwrap();\n\n    let mut command = build_command();\n    for shell in [\n        Shell::Bash,\n        Shell::Fish,\n        Shell::Zsh,\n        Shell::PowerShell,\n        Shell::Elvish,\n    ] {\n        generate_to(shell, &mut command, \"hyperfine\", &outdir).unwrap();\n    }\n}\n"
  },
  {
    "path": "doc/hyperfine.1",
    "content": ".TH HYPERFINE 1\n.SH NAME\nhyperfine \\- command\\-line benchmarking tool\n.SH SYNOPSIS\n.B hyperfine\n.RB [ \\-ihVN ]\n.RB [ \\-\\-warmup\n.IR NUM ]\n.RB [ \\-\\-min\\-runs\n.IR NUM ]\n.RB [ \\-\\-max\\-runs\n.IR NUM ]\n.RB [ \\-\\-runs\n.IR NUM ]\n.RB [ \\-\\-setup\n.IR CMD ]\n.RB [ \\-\\-prepare\n.IR CMD ]\n.RB [ \\-\\-conclude\n.IR CMD ]\n.RB [ \\-\\-cleanup\n.IR CMD ]\n.RB [ \\-\\-parameter\\-scan\n.IR VAR\n.IR MIN\n.IR MAX ]\n.RB [ \\-\\-parameter\\-step\\-size\n.IR DELTA ]\n.RB [ \\-\\-parameter\\-list\n.IR VAR\n.IR VALUES ]\n.RB [ \\-\\-shell\n.IR SHELL ]\n.RB [ \\-\\-style\n.IR TYPE ]\n.RB [ \\-\\-sort\n.IR METHOD ]\n.RB [ \\-\\-time-unit\n.IR UNIT ]\n.RB [ \\-\\-export\\-asciidoc\n.IR FILE ]\n.RB [ \\-\\-export\\-csv\n.IR FILE ]\n.RB [ \\-\\-export\\-json\n.IR FILE ]\n.RB [ \\-\\-export\\-markdown\n.IR FILE ]\n.RB [ \\-\\-export\\-orgmode\n.IR FILE ]\n.RB [ \\-\\-output\n.IR WHERE ]\n.RB [ \\-\\-input\n.IR WHERE ]\n.RB [ \\-\\-command\\-name\n.IR NAME ]\n.RI [ COMMAND... ]\n.SH DESCRIPTION\nA command\\-line benchmarking tool which includes:\n.LP\n.RS\n* Statistical analysis across multiple runs\n.RE\n.RS\n* Support for arbitrary shell commands\n.RE\n.RS\n* Constant feedback about the benchmark progress and current estimates\n.RE\n.RS\n* Warmup runs can be executed before the actual benchmark\n.RE\n.RS\n* Cache-clearing commands can be set up before each timing run\n.RE\n.RS\n* Statistical outlier detection to detect interference from other programs and caching effects\n.RE\n.RS\n* Export results to various formats: CSV, JSON, Markdown, AsciiDoc\n.RE\n.RS\n* Parameterized benchmarks (e.g. vary the number of threads)\n.RE\n.SH OPTIONS\n.HP\n\\fB\\-w\\fR, \\fB\\-\\-warmup\\fR \\fINUM\\fP\n.IP\nPerform \\fINUM\\fP warmup runs before the actual benchmark. This can be used\nto fill (disk) caches for I/O\\-heavy programs.\n.HP\n\\fB\\-m\\fR, \\fB\\-\\-min\\-runs\\fR \\fINUM\\fP\n.IP\nPerform at least \\fINUM\\fP runs for each command. Default: 10.\n.HP\n\\fB\\-M\\fR, \\fB\\-\\-max\\-runs\\fR \\fINUM\\fP\n.IP\nPerform at most \\fINUM\\fP runs for each command. By default, there is no\nlimit.\n.HP\n\\fB\\-r\\fR, \\fB\\-\\-runs\\fR \\fINUM\\fP\n.IP\nPerform exactly \\fINUM\\fP runs for each command. If this option is not specified,\n\\fBhyperfine\\fR automatically determines the number of runs.\n.HP\n\\fB\\-s\\fR, \\fB\\-\\-setup\\fR \\fICMD...\\fP\n.IP\nExecute \\fICMD\\fP once before each set of timing runs. This is useful\nfor compiling your software or with the provided parameters, or to do any\nother work that should happen once before a series of benchmark runs,\nnot every time as would happen with the \\fB\\-\\-prepare\\fR option.\n.HP\n\\fB\\-p\\fR, \\fB\\-\\-prepare\\fR \\fICMD...\\fP\n.IP\nExecute \\fICMD\\fP before each timing run. This is useful for clearing disk caches,\nfor example.\nThe \\fB\\-\\-prepare\\fR option can be specified once for all commands or multiple times,\nonce for each command. In the latter case, each preparation command will be\nrun prior to the corresponding benchmark command.\n.HP\n\\fB\\-\\-conclude\\fR \\fICMD...\\fP\n.IP\nExecute \\fICMD\\fP after each timing run. This is useful for clearing disk caches,\nfor example.\nThe \\fB\\-\\-conclude\\fR option can be specified once for all commands or multiple times,\nonce for each command. In the latter case, each conclusion command will be\nrun after the corresponding benchmark command.\n.HP\n\\fB\\-c\\fR, \\fB\\-\\-cleanup\\fR \\fICMD...\\fP\n.IP\nExecute \\fICMD\\fP after the completion of all benchmarking runs for each individual\ncommand to be benchmarked. This is useful if the commands to be benchmarked\nproduce artifacts that need to be cleaned up. It only runs once a series of\nbenchmark runs, as opposed to \\fB\\-\\-conclude\\fR option which runs after\nevery run.\n.HP\n\\fB\\-P\\fR, \\fB\\-\\-parameter\\-scan\\fR \\fIVAR\\fP \\fIMIN\\fP \\fIMAX\\fP\n.IP\nPerform benchmark runs for each value in the range \\fIMIN..MAX\\fP. Replaces the\nstring '{\\fIVAR\\fP}' in each command by the current parameter value.\n.IP\n.RS\nExample:\n.RS\n\\fBhyperfine\\fR \\fB\\-P\\fR threads 1 8 'make \\-j {threads}'\n.RE\n.RE\n.IP\nThis performs benchmarks for 'make \\-j 1', 'make \\-j 2', ..., 'make \\-j 8'.\n.IP\nTo have the value increase following different patterns, use shell\narithmetics.\n.IP\n.RS\nExample:\n.RS\n\\fBhyperfine\\fR \\fB\\-P\\fR size 0 3 'sleep $((2**{size}))'\n.RE\n.RE\n.IP\nThis performs benchmarks with power of 2 increases: 'sleep 1', 'sleep\n2', 'sleep 4', ...\n.IP\nThe exact syntax may vary depending on your shell and OS.\n.HP\n\\fB\\-D\\fR, \\fB\\-\\-parameter\\-step\\-size\\fR \\fIDELTA\\fP\n.IP\nThis argument requires \\fB\\-\\-parameter\\-scan\\fR to be specified as well. Traverse the\nrange \\fIMIN..MAX\\fP in steps of \\fIDELTA\\fP.\n.IP\n.RS\nExample:\n.RS\n\\fBhyperfine\\fR \\fB\\-P\\fR delay 0.3 0.7 \\fB\\-D\\fR 0.2 'sleep {delay}'\n.RE\n.RE\n.IP\nThis performs benchmarks for 'sleep 0.3', 'sleep 0.5' and 'sleep 0.7'.\n.HP\n\\fB\\-L\\fR, \\fB\\-\\-parameter\\-list\\fR \\fIVAR\\fP \\fIVALUES\\fP\n.IP\nPerform benchmark runs for each value in the comma\\-separated list of \\fIVALUES\\fP.\nReplaces the string '{\\fIVAR\\fP}' in each command by the current parameter value.\n.IP\n.RS\nExample:\n.RS\n\\fBhyperfine\\fR \\fB\\-L\\fR compiler gcc,clang '{compiler} \\-O2 main.cpp'\n.RE\n.RE\n.IP\nThis performs benchmarks for 'gcc \\-O2 main.cpp' and 'clang \\-O2 main.cpp'.\n.IP\nThe option can be specified multiple times to run benchmarks for all\npossible parameter combinations.\n.HP\n\\fB\\-S\\fR, \\fB\\-\\-shell\\fR \\fISHELL\\fP\n.IP\nSet the shell to use for executing benchmarked commands. This can be\nthe name or the path to the shell executable, or a full command line\nlike \"bash \\fB\\-\\-norc\\fR\". It can also be set to \"default\" to explicitly\nselect the default shell on this platform. Finally, this can also be\nset to \"none\" to disable the shell. In this case, commands will be\nexecuted directly. They can still have arguments, but more complex\nthings like \"sleep 0.1; sleep 0.2\" are not possible without a shell.\n.HP\n\\fB\\-N\\fR\n.IP\nAn alias for '\\-\\-shell=none'.\n.HP\n\\fB\\-i\\fR, \\fB\\-\\-ignore\\-failure\\fR\n.IP\nIgnore non\\-zero exit codes of the benchmarked programs.\n.HP\n\\fB\\-\\-style\\fR \\fITYPE\\fP\n.IP\nSet output style \\fITYPE\\fP (default: auto). Set this to 'basic' to disable output\ncoloring and interactive elements. Set it to 'full' to enable all effects even\nif no interactive terminal was detected. Set this to 'nocolor' to keep the\ninteractive output without any colors. Set this to 'color' to keep the colors\nwithout any interactive output. Set this to 'none' to disable all the output\nof the tool.\n.HP\n\\fB\\-\\-sort\\fR \\fIMETHOD\\fP\n.IP\nSpecify the sort order of the speed comparison summary and the\nexported tables for markup formats (Markdown, AsciiDoc, org\\-mode):\n.RS\n.IP \"auto (default)\"\nthe speed comparison will be ordered by time and\nthe markup tables will be ordered by command (input order).\n.IP \"command\"\norder benchmarks in the way they were specified\n.IP \"mean\\-time\"\norder benchmarks by mean runtime\n.RE\n.HP\n\\fB\\-u\\fR, \\fB\\-\\-time\\-unit\\fR \\fIUNIT\\fP\n.IP\nSet the time unit to be used. Possible values: microsecond, millisecond, second. If\nthe option is not given, the time unit is determined automatically.\nThis option affects the standard output as well as all export formats\nexcept for CSV and JSON.\n.HP\n\\fB\\-\\-export\\-asciidoc\\fR \\fIFILE\\fP \n.IP\nExport the timing summary statistics as an AsciiDoc table to the given \\fIFILE\\fP.\nThe output time unit can be changed using the \\fB\\-\\-time\\-unit\\fR option.\n.HP\n\\fB\\-\\-export\\-csv\\fR \\fIFILE\\fP\n.IP\nExport the timing summary statistics as CSV to the given \\fIFILE\\fP. If you need the\ntiming results for each individual run, use the JSON export format.\nThe output time unit is always seconds.\n.HP\n\\fB\\-\\-export\\-json\\fR \\fIFILE\\fP\n.IP\nExport the timing summary statistics and timings of individual runs as JSON to\nthe given \\fIFILE\\fP. The output time unit is always seconds.\n.HP\n\\fB\\-\\-export\\-markdown\\fR \\fIFILE\\fP\n.IP\nExport the timing summary statistics as a Markdown table to the given \\fIFILE\\fP.\nThe output time unit can be changed using the \\fB\\-\\-time\\-unit\\fR option.\n.HP\n\\fB\\-\\-export\\-orgmode\\fR \\fIFILE\\fP\n.IP\nExport the timing summary statistics as an Emacs org\\-mode table to the\ngiven \\fIFILE\\fP. The output time unit can be changed using the \\fB\\-\\-time\\-unit\\fR option.\n.HP\n\\fB\\-\\-show\\-output\\fR\n.IP\nPrint the stdout and stderr of the benchmark instead of suppressing it. This\nwill increase the time it takes for benchmarks to run, so it should only be\nused for debugging purposes or when trying to benchmark output speed.\n.HP\n\\fB\\-\\-output\\fR \\fIWHERE\\fP\n.IP\nControl where the output of the benchmark is redirected. Note that\nsome programs like 'grep' detect when standard output is \\fI\\,/dev/null\\/\\fP and\napply certain optimizations. To avoid that, consider using\n\\-\\-output=pipe.\n.IP\n\\fIWHERE\\fP can be:\n.RS\n.IP null\nRedirect output to \\fI\\,/dev/null\\/\\fP (the default).\n.IP pipe\nFeed the output through a pipe before discarding it.\n.IP inherit\nDon't redirect the output at all (same as \\&'\\-\\-show\\-output').\n.IP \"<FILE>\"\nWrite the output to the given file.\n.RE\n.IP\nThis option can be specified once for all commands or multiple times,\nonce for each command. Note: If you want to log the output of each and\nevery iteration, you can use a shell redirection and the $HYPERFINE_ITERATION\nenvironment variable: 'my-command > output-${HYPERFINE_ITERATION}.log'\n.HP\n\\fB\\-\\-input\\fR \\fIWHERE\\fP\n.IP\nControl where the input of the benchmark comes from.\n.IP\n\\fIWHERE\\fP can be:\n.RS\n.IP null\nRead from \\fI\\,/dev/null\\/\\fP (the default).\n.IP \"<FILE>\"\nRead the input from the given file.\n.RE\n.HP\n\\fB\\-n\\fR, \\fB\\-\\-command\\-name\\fR \\fiNAME\\fP\n.IP\nGive a meaningful \\fiNAME\\fP to a command. This can be specified multiple times\nif several commands are benchmarked.\n.HP\n\\fB\\-h\\fR, \\fB\\-\\-help\\fR\n.IP\nPrint help\n.HP\n\\fB\\-V\\fR, \\fB\\-\\-version\\fR\n.IP\nPrint version\n.SH EXAMPLES\n.LP\nBasic benchmark of 'find . -name todo.txt':\n.RS\n.nf\n\\fBhyperfine\\fR 'find . -name todo.txt'\n.fi\n.RE\n.LP\nPerform benchmarks for 'sleep 0.2' and 'sleep 3.2' with a minimum 5 runs each:\n.RS\n.nf\n\\fBhyperfine\\fR \\fB\\-\\-min\\-runs\\fR 5 'sleep 0.2' 'sleep 3.2'\n.fi\n.RE\n.LP\nPerform a benchmark of 'grep' with a warm disk cache by executing 3 runs up front that are not part\nof the measurement: \n.RS\n.nf\n\\fBhyperfine\\fR \\fB\\-\\-warmup\\fR 3 'grep -R TODO *'\n.fi\n.RE\n.LP\nExport the results of a parameter scan benchmark to a markdown table: \n.RS\n.nf\n\\fBhyperfine\\fR \\fB\\-\\-export\\-markdown\\fR output.md \\fB\\-\\-parameter-scan\\fR time 1 5 'sleep {time}'\n.fi\n.RE\n.LP\nDemonstrate when each of \\fB\\-\\-setup\\fR, \\fB\\-\\-prepare\\fR, \\fB\\-\\-conclude\\fR, \\fIcmd\\fP and \\fB\\-\\-cleanup\\fR will run:\n.RS\n.nf\n\\fBhyperfine\\fR \\fB\\-L\\fR n 1,2 \\fB\\-r\\fR 2 \\fB\\-\\-show-output\\fR \\\\\n\t\\fB\\-\\-setup\\fR 'echo setup n={n}' \\\\\n\t\\fB\\-\\-prepare\\fR 'echo prepare={n}' \\\\\n\t\\fB\\-\\-conclude\\fR 'echo conclude={n}' \\\\\n\t\\fB\\-\\-cleanup\\fR 'echo cleanup n={n}' \\\\\n\t'echo command n={n}'\n.fi\n.RE\n.RE\n.SH AUTHOR\n.LP\nDavid Peter <mail@david-peter.de>\n.LP\nSource, bug tracker, and additional information can be found on GitHub:\n.I https://github.com/sharkdp/hyperfine\n"
  },
  {
    "path": "doc/sponsors.md",
    "content": "## Sponsors\n\n`hyperfine` development is sponsored by many individuals and companies. Thank you very much!\n\nPlease note, that being sponsored does not affect the individuality of the `hyperfine`\nproject or affect the maintainers' actions in any way.\nWe remain impartial and continue to assess pull requests solely on merit - the\nfeatures added, bugs solved, and effect on the overall complexity of the code.\nNo issue will have a different priority based on sponsorship status of the\nreporter.\n\nContributions from anybody are most welcomed.\n\nIf you want to see our biggest sponsors, check the top of [`README.md`](../README.md#sponsors).\n"
  },
  {
    "path": "scripts/README.md",
    "content": "This folder contains scripts that can be used in combination with hyperfines `--export-json` option.\n\n### Example:\n\n```bash\nhyperfine 'sleep 0.020' 'sleep 0.021' 'sleep 0.022' --export-json sleep.json\n./plot_whisker.py sleep.json\n```\n\n### Pre-requisites\n\nTo make these scripts work, you will need `numpy`, `matplotlib` and `scipy`.\n\nIf you have a Python package manager that understands [PEP-723](https://peps.python.org/pep-0723/)\ninline script requirements like [`uv`](https://github.com/astral-sh/uv) or [`pipx`](https://github.com/pypa/pipx),\nyou can directly run the scripts using\n\n```bash\nuv run plot_whisker.py sleep.json\n```\n\nOtherwise, install the dependencies via your system package manager or using `pip`:\n\n```bash\npip install numpy matplotlib scipy  # pip3, if you are using python3\n```\n"
  },
  {
    "path": "scripts/advanced_statistics.py",
    "content": "#!/usr/bin/env python\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#     \"numpy\",\n# ]\n# ///\n\nimport argparse\nimport json\nfrom enum import Enum\n\nimport numpy as np\n\n\nclass Unit(Enum):\n    SECOND = 1\n    MILLISECOND = 2\n\n    def factor(self):\n        match self:\n            case Unit.SECOND:\n                return 1\n            case Unit.MILLISECOND:\n                return 1e3\n\n    def __str__(self):\n        match self:\n            case Unit.SECOND:\n                return \"s\"\n            case Unit.MILLISECOND:\n                return \"ms\"\n\n\nparser = argparse.ArgumentParser()\nparser.add_argument(\"file\", help=\"JSON file with benchmark results\")\nparser.add_argument(\n    \"--time-unit\",\n    help=\"The unit of time.\",\n    default=\"second\",\n    action=\"store\",\n    choices=[\"second\", \"millisecond\"],\n    dest=\"unit\",\n)\nargs = parser.parse_args()\n\nunit = Unit.MILLISECOND if args.unit == \"millisecond\" else Unit.SECOND\nunit_str = str(unit)\n\nwith open(args.file) as f:\n    results = json.load(f)[\"results\"]\n\ncommands = [b[\"command\"] for b in results]\ntimes = [b[\"times\"] for b in results]\n\nfor command, ts in zip(commands, times):\n    ts = [t * unit.factor() for t in ts]\n\n    p05 = np.percentile(ts, 5)\n    p25 = np.percentile(ts, 25)\n    p75 = np.percentile(ts, 75)\n    p95 = np.percentile(ts, 95)\n\n    iqr = p75 - p25\n\n    print(f\"Command '{command}'\")\n    print(f\"  runs:   {len(ts):8d}\")\n    print(f\"  mean:   {np.mean(ts):8.3f} {unit_str}\")\n    print(f\"  stddev: {np.std(ts, ddof=1):8.3f} {unit_str}\")\n    print(f\"  median: {np.median(ts):8.3f} {unit_str}\")\n    print(f\"  min:    {np.min(ts):8.3f} {unit_str}\")\n    print(f\"  max:    {np.max(ts):8.3f} {unit_str}\")\n    print()\n    print(\"  percentiles:\")\n    print(f\"     P_05 .. P_95:    {p05:.3f} {unit_str} .. {p95:.3f} {unit_str}\")\n    print(\n        f\"     P_25 .. P_75:    {p25:.3f} {unit_str} .. {p75:.3f} {unit_str}  (IQR = {iqr:.3f} {unit_str})\"\n    )\n    print()\n"
  },
  {
    "path": "scripts/plot_benchmark_comparison.py",
    "content": "#!/usr/bin/env python\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#     \"matplotlib\",\n#     \"pyqt6\",\n#     \"numpy\",\n# ]\n# ///\n\n\"\"\"\nThis script shows `hyperfine` benchmark results as a bar plot grouped by command.\nNote all the input files must contain results for all commands.\n\"\"\"\n\nimport argparse\nimport json\nimport pathlib\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nparser = argparse.ArgumentParser(description=__doc__)\nparser.add_argument(\n    \"files\", nargs=\"+\", type=pathlib.Path, help=\"JSON files with benchmark results\"\n)\nparser.add_argument(\"--title\", help=\"Plot Title\")\nparser.add_argument(\n    \"--benchmark-names\", nargs=\"+\", help=\"Names of the benchmark groups\"\n)\nparser.add_argument(\"-o\", \"--output\", help=\"Save image to the given filename\")\n\nargs = parser.parse_args()\n\ncommands = None\ndata = []\ninputs = []\n\nif args.benchmark_names:\n    assert len(args.files) == len(\n        args.benchmark_names\n    ), \"Number of benchmark names must match the number of input files.\"\n\nfor i, filename in enumerate(args.files):\n    with open(filename) as f:\n        results = json.load(f)[\"results\"]\n    benchmark_commands = [b[\"command\"] for b in results]\n    if commands is None:\n        commands = benchmark_commands\n    else:\n        assert (\n            commands == benchmark_commands\n        ), f\"Unexpected commands in {filename}: {benchmark_commands}, expected: {commands}\"\n    data.append([round(b[\"mean\"], 2) for b in results])\n    if args.benchmark_names:\n        inputs.append(args.benchmark_names[i])\n    else:\n        inputs.append(filename.stem)\n\ndata = np.transpose(data)\nx = np.arange(len(inputs))  # the label locations\nwidth = 0.25  # the width of the bars\n\nfig, ax = plt.subplots(layout=\"constrained\")\nfig.set_figheight(5)\nfig.set_figwidth(10)\nfor i, command in enumerate(commands):\n    offset = width * (i + 1)\n    rects = ax.bar(x + offset, data[i], width, label=command)\n\nax.set_xticks(x + 0.5, inputs)\nax.grid(visible=True, axis=\"y\")\n\nif args.title:\n    plt.title(args.title)\nplt.xlabel(\"Benchmark\")\nplt.ylabel(\"Time [s]\")\nplt.legend(title=\"Command\")\n\nif args.output:\n    plt.savefig(args.output)\nelse:\n    plt.show()\n"
  },
  {
    "path": "scripts/plot_histogram.py",
    "content": "#!/usr/bin/env python\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#     \"matplotlib\",\n#     \"pyqt6\",\n#     \"numpy\",\n# ]\n# ///\n\n\"\"\"This program shows `hyperfine` benchmark results as a histogram.\"\"\"\n\nimport argparse\nimport json\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\nparser = argparse.ArgumentParser(description=__doc__)\nparser.add_argument(\"file\", help=\"JSON file with benchmark results\")\nparser.add_argument(\"--title\", help=\"Plot title\")\nparser.add_argument(\n    \"--labels\", help=\"Comma-separated list of entries for the plot legend\"\n)\nparser.add_argument(\"--bins\", help=\"Number of bins (default: auto)\")\nparser.add_argument(\n    \"--legend-location\",\n    help=\"Location of the legend on plot (default: upper center)\",\n    choices=[\n        \"upper center\",\n        \"lower center\",\n        \"right\",\n        \"left\",\n        \"best\",\n        \"upper left\",\n        \"upper right\",\n        \"lower left\",\n        \"lower right\",\n        \"center left\",\n        \"center right\",\n        \"center\",\n    ],\n    default=\"upper center\",\n)\nparser.add_argument(\n    \"--type\", help=\"Type of histogram (*bar*, barstacked, step, stepfilled)\"\n)\nparser.add_argument(\"-o\", \"--output\", help=\"Save image to the given filename.\")\nparser.add_argument(\n    \"--t-min\", metavar=\"T\", help=\"Minimum time to be displayed (seconds)\"\n)\nparser.add_argument(\n    \"--t-max\", metavar=\"T\", help=\"Maximum time to be displayed (seconds)\"\n)\nparser.add_argument(\n    \"--log-count\",\n    help=\"Use a logarithmic y-axis for the event count\",\n    action=\"store_true\",\n)\n\nargs = parser.parse_args()\n\nwith open(args.file) as f:\n    results = json.load(f)[\"results\"]\n\nif args.labels:\n    labels = args.labels.split(\",\")\nelse:\n    labels = [b[\"command\"] for b in results]\nall_times = [b[\"times\"] for b in results]\n\nt_min = float(args.t_min) if args.t_min else np.min(list(map(np.min, all_times)))\nt_max = float(args.t_max) if args.t_max else np.max(list(map(np.max, all_times)))\n\nbins = int(args.bins) if args.bins else \"auto\"\nhisttype = args.type if args.type else \"bar\"\n\nplt.figure(figsize=(10, 5))\nplt.hist(\n    all_times,\n    label=labels,\n    bins=bins,\n    histtype=histtype,\n    range=(t_min, t_max),\n)\nplt.legend(\n    loc=args.legend_location,\n    fancybox=True,\n    shadow=True,\n    prop={\"size\": 10, \"family\": [\"Source Code Pro\", \"Fira Mono\", \"Courier New\"]},\n)\n\nplt.xlabel(\"Time [s]\")\nif args.title:\n    plt.title(args.title)\n\nif args.log_count:\n    plt.yscale(\"log\")\nelse:\n    plt.ylim(0, None)\n\nif args.output:\n    plt.savefig(args.output, dpi=600)\nelse:\n    plt.show()\n"
  },
  {
    "path": "scripts/plot_parametrized.py",
    "content": "#!/usr/bin/env python\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#     \"matplotlib\",\n#     \"pyqt6\",\n# ]\n# ///\n\n\"\"\"This program shows parametrized `hyperfine` benchmark results as an\nerrorbar plot.\"\"\"\n\nimport argparse\nimport json\nimport sys\n\nimport matplotlib.pyplot as plt\n\nparser = argparse.ArgumentParser(description=__doc__)\nparser.add_argument(\"file\", help=\"JSON file with benchmark results\", nargs=\"+\")\nparser.add_argument(\n    \"--parameter-name\",\n    metavar=\"name\",\n    type=str,\n    help=\"Deprecated; parameter names are now inferred from benchmark files\",\n)\nparser.add_argument(\n    \"--log-x\", help=\"Use a logarithmic x (parameter) axis\", action=\"store_true\"\n)\nparser.add_argument(\n    \"--log-time\", help=\"Use a logarithmic time axis\", action=\"store_true\"\n)\nparser.add_argument(\n    \"--titles\", help=\"Comma-separated list of titles for the plot legend\"\n)\nparser.add_argument(\"-o\", \"--output\", help=\"Save image to the given filename.\")\n\nargs = parser.parse_args()\nif args.parameter_name is not None:\n    sys.stderr.write(\n        \"warning: --parameter-name is deprecated; names are inferred from \"\n        \"benchmark results\\n\"\n    )\n\n\ndef die(msg):\n    sys.stderr.write(f\"fatal: {msg}\\n\")\n    sys.exit(1)\n\n\ndef extract_parameters(results):\n    \"\"\"Return `(parameter_name: str, parameter_values: List[float])`.\"\"\"\n    if not results:\n        die(\"no benchmark data to plot\")\n    (names, values) = zip(*(unique_parameter(b) for b in results))\n    names = frozenset(names)\n    if len(names) != 1:\n        die(\n            f\"benchmarks must all have the same parameter name, but found: {sorted(names)}\"\n        )\n    return (next(iter(names)), list(values))\n\n\ndef unique_parameter(benchmark):\n    \"\"\"Return the unique parameter `(name: str, value: float)`, or die.\"\"\"\n    params_dict = benchmark.get(\"parameters\", {})\n    if not params_dict:\n        die(\"benchmarks must have exactly one parameter, but found none\")\n    if len(params_dict) > 1:\n        die(\n            f\"benchmarks must have exactly one parameter, but found multiple: {sorted(params_dict)}\"\n        )\n    [(name, value)] = params_dict.items()\n    return (name, float(value))\n\n\nparameter_name = None\n\nfor filename in args.file:\n    with open(filename) as f:\n        results = json.load(f)[\"results\"]\n\n    (this_parameter_name, parameter_values) = extract_parameters(results)\n    if parameter_name is not None and this_parameter_name != parameter_name:\n        die(\n            f\"files must all have the same parameter name, but found {parameter_name!r} vs. {this_parameter_name!r}\"\n        )\n    parameter_name = this_parameter_name\n\n    times_mean = [b[\"mean\"] for b in results]\n    times_stddev = [b[\"stddev\"] for b in results]\n\n    plt.errorbar(x=parameter_values, y=times_mean, yerr=times_stddev, capsize=2)\n\nplt.xlabel(parameter_name)\nplt.ylabel(\"Time [s]\")\n\nif args.log_time:\n    plt.yscale(\"log\")\nelse:\n    plt.ylim(0, None)\n\nif args.log_x:\n    plt.xscale(\"log\")\n\nif args.titles:\n    plt.legend(args.titles.split(\",\"))\n\nif args.output:\n    plt.savefig(args.output)\nelse:\n    plt.show()\n"
  },
  {
    "path": "scripts/plot_progression.py",
    "content": "#!/usr/bin/env python\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#     \"pyqt6\",\n#     \"matplotlib\",\n#     \"numpy\",\n# ]\n# ///\n\n\"\"\"This program shows `hyperfine` benchmark results in a sequential way\nin order to debug possible background interference, caching effects,\nthermal throttling and similar effects.\n\"\"\"\n\nimport argparse\nimport json\n\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\ndef moving_average(times, num_runs):\n    times_padded = np.pad(\n        times, (num_runs // 2, num_runs - 1 - num_runs // 2), mode=\"edge\"\n    )\n    kernel = np.ones(num_runs) / num_runs\n    return np.convolve(times_padded, kernel, mode=\"valid\")\n\n\nparser = argparse.ArgumentParser(description=__doc__)\nparser.add_argument(\"file\", help=\"JSON file with benchmark results\")\nparser.add_argument(\"--title\", help=\"Plot Title\")\nparser.add_argument(\"-o\", \"--output\", help=\"Save image to the given filename.\")\nparser.add_argument(\n    \"-w\",\n    \"--moving-average-width\",\n    type=int,\n    metavar=\"num_runs\",\n    help=\"Width of the moving-average window (default: N/5)\",\n)\nparser.add_argument(\n    \"--no-moving-average\",\n    action=\"store_true\",\n    help=\"Do not show moving average curve\",\n)\n\n\nargs = parser.parse_args()\n\nwith open(args.file) as f:\n    results = json.load(f)[\"results\"]\n\nfor result in results:\n    label = result[\"command\"]\n    times = result[\"times\"]\n    num = len(times)\n    nums = range(num)\n\n    plt.scatter(x=nums, y=times, marker=\".\")\n    plt.ylim([0, None])\n    plt.xlim([-1, num])\n\n    if not args.no_moving_average:\n        moving_average_width = (\n            num // 5 if args.moving_average_width is None else args.moving_average_width\n        )\n\n        average = moving_average(times, moving_average_width)\n        plt.plot(nums, average, \"-\")\n\nif args.title:\n    plt.title(args.title)\n\nlegend = []\nfor result in results:\n    legend.append(result[\"command\"])\n    if not args.no_moving_average:\n        legend.append(\"moving average\")\nplt.legend(legend)\n\nplt.ylabel(\"Time [s]\")\n\nif args.output:\n    plt.savefig(args.output)\nelse:\n    plt.show()\n"
  },
  {
    "path": "scripts/plot_whisker.py",
    "content": "#!/usr/bin/env python\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#     \"matplotlib\",\n#     \"pyqt6\",\n# ]\n# ///\n\n\"\"\"This program shows `hyperfine` benchmark results as a box and whisker plot.\n\nQuoting from the matplotlib documentation:\n    The box extends from the lower to upper quartile values of the data, with\n    a line at the median. The whiskers extend from the box to show the range\n    of the data. Flier points are those past the end of the whiskers.\n\"\"\"\n\nimport argparse\nimport json\n\nimport matplotlib.pyplot as plt\n\nparser = argparse.ArgumentParser(description=__doc__)\nparser.add_argument(\"file\", help=\"JSON file with benchmark results\")\nparser.add_argument(\"--title\", help=\"Plot Title\")\nparser.add_argument(\"--sort-by\", choices=[\"median\"], help=\"Sort method\")\nparser.add_argument(\n    \"--labels\", help=\"Comma-separated list of entries for the plot legend\"\n)\nparser.add_argument(\"-o\", \"--output\", help=\"Save image to the given filename.\")\n\nargs = parser.parse_args()\n\nwith open(args.file, encoding=\"utf-8\") as f:\n    results = json.load(f)[\"results\"]\n\nif args.labels:\n    labels = args.labels.split(\",\")\nelse:\n    labels = [b[\"command\"] for b in results]\ntimes = [b[\"times\"] for b in results]\n\nif args.sort_by == \"median\":\n    medians = [b[\"median\"] for b in results]\n    indices = sorted(range(len(labels)), key=lambda k: medians[k])\n    labels = [labels[i] for i in indices]\n    times = [times[i] for i in indices]\n\nplt.figure(figsize=(10, 6), constrained_layout=True)\nboxplot = plt.boxplot(times, vert=True, patch_artist=True)\ncmap = plt.get_cmap(\"rainbow\")\ncolors = [cmap(val / len(times)) for val in range(len(times))]\n\nfor patch, color in zip(boxplot[\"boxes\"], colors):\n    patch.set_facecolor(color)\n\nif args.title:\n    plt.title(args.title)\nplt.legend(handles=boxplot[\"boxes\"], labels=labels, loc=\"best\", fontsize=\"medium\")\nplt.ylabel(\"Time [s]\")\nplt.ylim(0, None)\nplt.xticks(list(range(1, len(labels) + 1)), labels, rotation=45)\nif args.output:\n    plt.savefig(args.output)\nelse:\n    plt.show()\n"
  },
  {
    "path": "scripts/ruff.toml",
    "content": "target-version = \"py310\"\n\n[lint]\nextend-select = [\"I\", \"UP\", \"RUF\"]\n"
  },
  {
    "path": "scripts/welch_ttest.py",
    "content": "#!/usr/bin/env python\n# /// script\n# requires-python = \">=3.10\"\n# dependencies = [\n#     \"scipy\",\n# ]\n# ///\n\n\"\"\"This script performs Welch's t-test on a JSON export file with two\nbenchmark results to test whether or not the two distributions are\nthe same.\"\"\"\n\nimport argparse\nimport json\nimport sys\n\nfrom scipy import stats\n\nparser = argparse.ArgumentParser(description=__doc__)\nparser.add_argument(\"file\", help=\"JSON file with two benchmark results\")\nargs = parser.parse_args()\n\nwith open(args.file) as f:\n    results = json.load(f)[\"results\"]\n\nif len(results) != 2:\n    print(\"The input file has to contain exactly two benchmarks\")\n    sys.exit(1)\n\na, b = (x[\"command\"] for x in results[:2])\nX, Y = (x[\"times\"] for x in results[:2])\n\nprint(f\"Command 1: {a}\")\nprint(f\"Command 2: {b}\\n\")\n\nt, p = stats.ttest_ind(X, Y, equal_var=False)\nth = 0.05\ndispose = p < th\nprint(f\"t = {t:.3}, p = {p:.3}\")\nprint()\n\nif dispose:\n    print(f\"There is a difference between the two benchmarks (p < {th}).\")\nelse:\n    print(f\"The two benchmarks are almost the same (p >= {th}).\")\n"
  },
  {
    "path": "src/benchmark/benchmark_result.rs",
    "content": "use std::collections::BTreeMap;\n\nuse serde::Serialize;\n\nuse crate::util::units::Second;\n\n/// Set of values that will be exported.\n// NOTE: `serde` is used for JSON serialization, but not for CSV serialization due to the\n// `parameters` map. Update `src/hyperfine/export/csv.rs` with new fields, as appropriate.\n#[derive(Debug, Default, Clone, Serialize, PartialEq)]\npub struct BenchmarkResult {\n    /// The full command line of the program that is being benchmarked\n    pub command: String,\n\n    /// The full command line of the program that is being benchmarked, possibly including a list of\n    /// parameters that were not used in the command line template.\n    #[serde(skip_serializing)]\n    pub command_with_unused_parameters: String,\n\n    /// The average run time\n    pub mean: Second,\n\n    /// The standard deviation of all run times. Not available if only one run has been performed\n    pub stddev: Option<Second>,\n\n    /// The median run time\n    pub median: Second,\n\n    /// Time spent in user mode\n    pub user: Second,\n\n    /// Time spent in kernel mode\n    pub system: Second,\n\n    /// Minimum of all measured times\n    pub min: Second,\n\n    /// Maximum of all measured times\n    pub max: Second,\n\n    /// All run time measurements\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub times: Option<Vec<Second>>,\n\n    /// Maximum memory usage of the process, in bytes\n    #[serde(skip_serializing_if = \"Option::is_none\")]\n    pub memory_usage_byte: Option<Vec<u64>>,\n\n    /// Exit codes of all command invocations\n    pub exit_codes: Vec<Option<i32>>,\n\n    /// Parameter values for this benchmark\n    #[serde(skip_serializing_if = \"BTreeMap::is_empty\")]\n    pub parameters: BTreeMap<String, String>,\n}\n"
  },
  {
    "path": "src/benchmark/executor.rs",
    "content": "#[cfg(windows)]\nuse std::os::windows::process::CommandExt;\nuse std::process::ExitStatus;\n\nuse crate::command::Command;\nuse crate::options::{\n    CmdFailureAction, CommandInputPolicy, CommandOutputPolicy, Options, OutputStyleOption, Shell,\n};\nuse crate::output::progress_bar::get_progress_bar;\nuse crate::timer::{execute_and_measure, TimerResult};\nuse crate::util::randomized_environment_offset;\nuse crate::util::units::Second;\n\nuse super::timing_result::TimingResult;\n\nuse anyhow::{bail, Context, Result};\nuse statistical::mean;\n\npub enum BenchmarkIteration {\n    NonBenchmarkRun,\n    Warmup(u64),\n    Benchmark(u64),\n}\n\nimpl BenchmarkIteration {\n    pub fn to_env_var_value(&self) -> Option<String> {\n        match self {\n            BenchmarkIteration::NonBenchmarkRun => None,\n            BenchmarkIteration::Warmup(i) => Some(format!(\"warmup-{}\", i)),\n            BenchmarkIteration::Benchmark(i) => Some(format!(\"{}\", i)),\n        }\n    }\n}\n\npub trait Executor {\n    /// Run the given command and measure the execution time\n    fn run_command_and_measure(\n        &self,\n        command: &Command<'_>,\n        iteration: BenchmarkIteration,\n        command_failure_action: Option<CmdFailureAction>,\n        output_policy: &CommandOutputPolicy,\n    ) -> Result<(TimingResult, ExitStatus)>;\n\n    /// Perform a calibration of this executor. For example,\n    /// when running commands through a shell, we need to\n    /// measure the shell spawning time separately in order\n    /// to subtract it from the full runtime later.\n    fn calibrate(&mut self) -> Result<()>;\n\n    /// Return the time overhead for this executor when\n    /// performing a measurement. This should return the time\n    /// that is being used in addition to the actual runtime\n    /// of the command.\n    fn time_overhead(&self) -> Second;\n}\n\nfn run_command_and_measure_common(\n    mut command: std::process::Command,\n    iteration: BenchmarkIteration,\n    command_failure_action: CmdFailureAction,\n    command_input_policy: &CommandInputPolicy,\n    command_output_policy: &CommandOutputPolicy,\n    command_name: &str,\n) -> Result<TimerResult> {\n    let stdin = command_input_policy.get_stdin()?;\n    let (stdout, stderr) = command_output_policy.get_stdout_stderr()?;\n    command.stdin(stdin).stdout(stdout).stderr(stderr);\n\n    command.env(\n        \"HYPERFINE_RANDOMIZED_ENVIRONMENT_OFFSET\",\n        randomized_environment_offset::value(),\n    );\n\n    if let Some(value) = iteration.to_env_var_value() {\n        command.env(\"HYPERFINE_ITERATION\", value);\n    }\n\n    let result = execute_and_measure(command)\n        .with_context(|| format!(\"Failed to run command '{command_name}'\"))?;\n\n    if !result.status.success() {\n        use crate::util::exit_code::extract_exit_code;\n\n        let should_fail = match command_failure_action {\n            CmdFailureAction::RaiseError => true,\n            CmdFailureAction::IgnoreAllFailures => false,\n            CmdFailureAction::IgnoreSpecificFailures(ref codes) => {\n                // Only fail if the exit code is not in the list of codes to ignore\n                if let Some(exit_code) = extract_exit_code(result.status) {\n                    !codes.contains(&exit_code)\n                } else {\n                    // If we can't extract an exit code, treat it as a failure\n                    true\n                }\n            }\n        };\n\n        if should_fail {\n            let when = match iteration {\n                BenchmarkIteration::NonBenchmarkRun => \"a non-benchmark run\".to_string(),\n                BenchmarkIteration::Warmup(0) => \"the first warmup run\".to_string(),\n                BenchmarkIteration::Warmup(i) => format!(\"warmup iteration {i}\"),\n                BenchmarkIteration::Benchmark(0) => \"the first benchmark run\".to_string(),\n                BenchmarkIteration::Benchmark(i) => format!(\"benchmark iteration {i}\"),\n            };\n            bail!(\n                \"{cause} in {when}. Use the '-i'/'--ignore-failure' option if you want to ignore this. \\\n                Alternatively, use the '--show-output' option to debug what went wrong.\",\n                cause=result.status.code().map_or(\n                    \"The process has been terminated by a signal\".into(),\n                    |c| format!(\"Command terminated with non-zero exit code {c}\")\n\n                ),\n            );\n        }\n    }\n\n    Ok(result)\n}\n\npub struct RawExecutor<'a> {\n    options: &'a Options,\n}\n\nimpl<'a> RawExecutor<'a> {\n    pub fn new(options: &'a Options) -> Self {\n        RawExecutor { options }\n    }\n}\n\nimpl Executor for RawExecutor<'_> {\n    fn run_command_and_measure(\n        &self,\n        command: &Command<'_>,\n        iteration: BenchmarkIteration,\n        command_failure_action: Option<CmdFailureAction>,\n        output_policy: &CommandOutputPolicy,\n    ) -> Result<(TimingResult, ExitStatus)> {\n        let result = run_command_and_measure_common(\n            command.get_command()?,\n            iteration,\n            command_failure_action.unwrap_or_else(|| self.options.command_failure_action.clone()),\n            &self.options.command_input_policy,\n            output_policy,\n            &command.get_command_line(),\n        )?;\n\n        Ok((\n            TimingResult {\n                time_real: result.time_real,\n                time_user: result.time_user,\n                time_system: result.time_system,\n                memory_usage_byte: result.memory_usage_byte,\n            },\n            result.status,\n        ))\n    }\n\n    fn calibrate(&mut self) -> Result<()> {\n        Ok(())\n    }\n\n    fn time_overhead(&self) -> Second {\n        0.0\n    }\n}\n\npub struct ShellExecutor<'a> {\n    options: &'a Options,\n    shell: &'a Shell,\n    shell_spawning_time: Option<TimingResult>,\n}\n\nimpl<'a> ShellExecutor<'a> {\n    pub fn new(shell: &'a Shell, options: &'a Options) -> Self {\n        ShellExecutor {\n            shell,\n            options,\n            shell_spawning_time: None,\n        }\n    }\n}\n\nimpl Executor for ShellExecutor<'_> {\n    fn run_command_and_measure(\n        &self,\n        command: &Command<'_>,\n        iteration: BenchmarkIteration,\n        command_failure_action: Option<CmdFailureAction>,\n        output_policy: &CommandOutputPolicy,\n    ) -> Result<(TimingResult, ExitStatus)> {\n        let on_windows_cmd = cfg!(windows) && *self.shell == Shell::Default(\"cmd.exe\");\n        let mut command_builder = self.shell.command();\n        command_builder.arg(if on_windows_cmd { \"/C\" } else { \"-c\" });\n\n        // Windows needs special treatment for its behavior on parsing cmd arguments\n        if on_windows_cmd {\n            #[cfg(windows)]\n            command_builder.raw_arg(command.get_command_line());\n        } else {\n            command_builder.arg(command.get_command_line());\n        }\n\n        let mut result = run_command_and_measure_common(\n            command_builder,\n            iteration,\n            command_failure_action.unwrap_or_else(|| self.options.command_failure_action.clone()),\n            &self.options.command_input_policy,\n            output_policy,\n            &command.get_command_line(),\n        )?;\n\n        // Subtract shell spawning time\n        if let Some(spawning_time) = self.shell_spawning_time {\n            result.time_real = (result.time_real - spawning_time.time_real).max(0.0);\n            result.time_user = (result.time_user - spawning_time.time_user).max(0.0);\n            result.time_system = (result.time_system - spawning_time.time_system).max(0.0);\n        }\n\n        Ok((\n            TimingResult {\n                time_real: result.time_real,\n                time_user: result.time_user,\n                time_system: result.time_system,\n                memory_usage_byte: result.memory_usage_byte,\n            },\n            result.status,\n        ))\n    }\n\n    /// Measure the average shell spawning time\n    fn calibrate(&mut self) -> Result<()> {\n        const COUNT: u64 = 50;\n        let progress_bar = if self.options.output_style != OutputStyleOption::Disabled {\n            Some(get_progress_bar(\n                COUNT,\n                \"Measuring shell spawning time\",\n                self.options.output_style,\n            ))\n        } else {\n            None\n        };\n\n        let mut times_real: Vec<Second> = vec![];\n        let mut times_user: Vec<Second> = vec![];\n        let mut times_system: Vec<Second> = vec![];\n\n        for _ in 0..COUNT {\n            // Just run the shell without any command\n            let res = self.run_command_and_measure(\n                &Command::new(None, \"\"),\n                BenchmarkIteration::NonBenchmarkRun,\n                None,\n                &CommandOutputPolicy::Null,\n            );\n\n            match res {\n                Err(_) => {\n                    let shell_cmd = if cfg!(windows) {\n                        format!(\"{} /C \\\"\\\"\", self.shell)\n                    } else {\n                        format!(\"{} -c \\\"\\\"\", self.shell)\n                    };\n\n                    bail!(\n                        \"Could not measure shell execution time. Make sure you can run '{}'.\",\n                        shell_cmd\n                    );\n                }\n                Ok((r, _)) => {\n                    times_real.push(r.time_real);\n                    times_user.push(r.time_user);\n                    times_system.push(r.time_system);\n                }\n            }\n\n            if let Some(bar) = progress_bar.as_ref() {\n                bar.inc(1)\n            }\n        }\n\n        if let Some(bar) = progress_bar.as_ref() {\n            bar.finish_and_clear()\n        }\n\n        self.shell_spawning_time = Some(TimingResult {\n            time_real: mean(&times_real),\n            time_user: mean(&times_user),\n            time_system: mean(&times_system),\n            memory_usage_byte: 0,\n        });\n\n        Ok(())\n    }\n\n    fn time_overhead(&self) -> Second {\n        self.shell_spawning_time.unwrap().time_real\n    }\n}\n\n#[derive(Clone)]\npub struct MockExecutor {\n    shell: Option<String>,\n}\n\nimpl MockExecutor {\n    pub fn new(shell: Option<String>) -> Self {\n        MockExecutor { shell }\n    }\n\n    fn extract_time<S: AsRef<str>>(sleep_command: S) -> Second {\n        assert!(sleep_command.as_ref().starts_with(\"sleep \"));\n        sleep_command\n            .as_ref()\n            .trim_start_matches(\"sleep \")\n            .parse::<Second>()\n            .unwrap()\n    }\n}\n\nimpl Executor for MockExecutor {\n    fn run_command_and_measure(\n        &self,\n        command: &Command<'_>,\n        _iteration: BenchmarkIteration,\n        _command_failure_action: Option<CmdFailureAction>,\n        _output_policy: &CommandOutputPolicy,\n    ) -> Result<(TimingResult, ExitStatus)> {\n        #[cfg(unix)]\n        let status = {\n            use std::os::unix::process::ExitStatusExt;\n            ExitStatus::from_raw(0)\n        };\n\n        #[cfg(windows)]\n        let status = {\n            use std::os::windows::process::ExitStatusExt;\n            ExitStatus::from_raw(0)\n        };\n\n        Ok((\n            TimingResult {\n                time_real: Self::extract_time(command.get_command_line()),\n                time_user: 0.0,\n                time_system: 0.0,\n                memory_usage_byte: 0,\n            },\n            status,\n        ))\n    }\n\n    fn calibrate(&mut self) -> Result<()> {\n        Ok(())\n    }\n\n    fn time_overhead(&self) -> Second {\n        match &self.shell {\n            None => 0.0,\n            Some(shell) => Self::extract_time(shell),\n        }\n    }\n}\n\n#[test]\nfn test_mock_executor_extract_time() {\n    assert_eq!(MockExecutor::extract_time(\"sleep 0.1\"), 0.1);\n}\n"
  },
  {
    "path": "src/benchmark/mod.rs",
    "content": "pub mod benchmark_result;\npub mod executor;\npub mod relative_speed;\npub mod scheduler;\npub mod timing_result;\n\nuse std::cmp;\n\nuse crate::benchmark::executor::BenchmarkIteration;\nuse crate::command::Command;\nuse crate::options::{\n    CmdFailureAction, CommandOutputPolicy, ExecutorKind, Options, OutputStyleOption,\n};\nuse crate::outlier_detection::{modified_zscores, OUTLIER_THRESHOLD};\nuse crate::output::format::{format_duration, format_duration_unit};\nuse crate::output::progress_bar::get_progress_bar;\nuse crate::output::warnings::{OutlierWarningOptions, Warnings};\nuse crate::parameter::ParameterNameAndValue;\nuse crate::util::exit_code::extract_exit_code;\nuse crate::util::min_max::{max, min};\nuse crate::util::units::Second;\nuse benchmark_result::BenchmarkResult;\nuse timing_result::TimingResult;\n\nuse anyhow::{anyhow, Result};\nuse colored::*;\nuse statistical::{mean, median, standard_deviation};\n\nuse self::executor::Executor;\n\n/// Threshold for warning about fast execution time\npub const MIN_EXECUTION_TIME: Second = 5e-3;\n\npub struct Benchmark<'a> {\n    number: usize,\n    command: &'a Command<'a>,\n    options: &'a Options,\n    executor: &'a dyn Executor,\n}\n\nimpl<'a> Benchmark<'a> {\n    pub fn new(\n        number: usize,\n        command: &'a Command<'a>,\n        options: &'a Options,\n        executor: &'a dyn Executor,\n    ) -> Self {\n        Benchmark {\n            number,\n            command,\n            options,\n            executor,\n        }\n    }\n\n    /// Run setup, cleanup, or preparation commands\n    fn run_intermediate_command(\n        &self,\n        command: &Command<'_>,\n        error_output: &'static str,\n        output_policy: &CommandOutputPolicy,\n    ) -> Result<TimingResult> {\n        self.executor\n            .run_command_and_measure(\n                command,\n                executor::BenchmarkIteration::NonBenchmarkRun,\n                Some(CmdFailureAction::RaiseError),\n                output_policy,\n            )\n            .map(|r| r.0)\n            .map_err(|_| anyhow!(error_output))\n    }\n\n    /// Run the command specified by `--setup`.\n    fn run_setup_command(\n        &self,\n        parameters: impl IntoIterator<Item = ParameterNameAndValue<'a>>,\n        output_policy: &CommandOutputPolicy,\n    ) -> Result<TimingResult> {\n        let command = self\n            .options\n            .setup_command\n            .as_ref()\n            .map(|setup_command| Command::new_parametrized(None, setup_command, parameters));\n\n        let error_output = \"The setup command terminated with a non-zero exit code. \\\n                            Append ' || true' to the command if you are sure that this can be ignored.\";\n\n        Ok(command\n            .map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy))\n            .transpose()?\n            .unwrap_or_default())\n    }\n\n    /// Run the command specified by `--cleanup`.\n    fn run_cleanup_command(\n        &self,\n        parameters: impl IntoIterator<Item = ParameterNameAndValue<'a>>,\n        output_policy: &CommandOutputPolicy,\n    ) -> Result<TimingResult> {\n        let command = self\n            .options\n            .cleanup_command\n            .as_ref()\n            .map(|cleanup_command| Command::new_parametrized(None, cleanup_command, parameters));\n\n        let error_output = \"The cleanup command terminated with a non-zero exit code. \\\n                            Append ' || true' to the command if you are sure that this can be ignored.\";\n\n        Ok(command\n            .map(|cmd| self.run_intermediate_command(&cmd, error_output, output_policy))\n            .transpose()?\n            .unwrap_or_default())\n    }\n\n    /// Run the command specified by `--prepare`.\n    fn run_preparation_command(\n        &self,\n        command: &Command<'_>,\n        output_policy: &CommandOutputPolicy,\n    ) -> Result<TimingResult> {\n        let error_output = \"The preparation command terminated with a non-zero exit code. \\\n                            Append ' || true' to the command if you are sure that this can be ignored.\";\n\n        self.run_intermediate_command(command, error_output, output_policy)\n    }\n\n    /// Run the command specified by `--conclude`.\n    fn run_conclusion_command(\n        &self,\n        command: &Command<'_>,\n        output_policy: &CommandOutputPolicy,\n    ) -> Result<TimingResult> {\n        let error_output = \"The conclusion command terminated with a non-zero exit code. \\\n                            Append ' || true' to the command if you are sure that this can be ignored.\";\n\n        self.run_intermediate_command(command, error_output, output_policy)\n    }\n\n    /// Run the benchmark for a single command\n    pub fn run(&self) -> Result<BenchmarkResult> {\n        if self.options.output_style != OutputStyleOption::Disabled {\n            println!(\n                \"{}{}: {}\",\n                \"Benchmark \".bold(),\n                (self.number + 1).to_string().bold(),\n                self.command.get_name_with_unused_parameters(),\n            );\n        }\n\n        let mut times_real: Vec<Second> = vec![];\n        let mut times_user: Vec<Second> = vec![];\n        let mut times_system: Vec<Second> = vec![];\n        let mut memory_usage_byte: Vec<u64> = vec![];\n        let mut exit_codes: Vec<Option<i32>> = vec![];\n        let mut all_succeeded = true;\n\n        let output_policy = &self.options.command_output_policies[self.number];\n\n        let preparation_command = self.options.preparation_command.as_ref().map(|values| {\n            let preparation_command = if values.len() == 1 {\n                &values[0]\n            } else {\n                &values[self.number]\n            };\n            Command::new_parametrized(\n                None,\n                preparation_command,\n                self.command.get_parameters().iter().cloned(),\n            )\n        });\n\n        let run_preparation_command = || {\n            preparation_command\n                .as_ref()\n                .map(|cmd| self.run_preparation_command(cmd, output_policy))\n                .transpose()\n        };\n\n        let conclusion_command = self.options.conclusion_command.as_ref().map(|values| {\n            let conclusion_command = if values.len() == 1 {\n                &values[0]\n            } else {\n                &values[self.number]\n            };\n            Command::new_parametrized(\n                None,\n                conclusion_command,\n                self.command.get_parameters().iter().cloned(),\n            )\n        });\n        let run_conclusion_command = || {\n            conclusion_command\n                .as_ref()\n                .map(|cmd| self.run_conclusion_command(cmd, output_policy))\n                .transpose()\n        };\n\n        self.run_setup_command(self.command.get_parameters().iter().cloned(), output_policy)?;\n\n        // Warmup phase\n        if self.options.warmup_count > 0 {\n            let progress_bar = if self.options.output_style != OutputStyleOption::Disabled {\n                Some(get_progress_bar(\n                    self.options.warmup_count,\n                    \"Performing warmup runs\",\n                    self.options.output_style,\n                ))\n            } else {\n                None\n            };\n\n            for i in 0..self.options.warmup_count {\n                let _ = run_preparation_command()?;\n                let _ = self.executor.run_command_and_measure(\n                    self.command,\n                    BenchmarkIteration::Warmup(i),\n                    None,\n                    output_policy,\n                )?;\n                let _ = run_conclusion_command()?;\n                if let Some(bar) = progress_bar.as_ref() {\n                    bar.inc(1)\n                }\n            }\n            if let Some(bar) = progress_bar.as_ref() {\n                bar.finish_and_clear()\n            }\n        }\n\n        // Set up progress bar (and spinner for initial measurement)\n        let progress_bar = if self.options.output_style != OutputStyleOption::Disabled {\n            Some(get_progress_bar(\n                self.options.run_bounds.min,\n                \"Initial time measurement\",\n                self.options.output_style,\n            ))\n        } else {\n            None\n        };\n\n        let preparation_result = run_preparation_command()?;\n        let preparation_overhead =\n            preparation_result.map_or(0.0, |res| res.time_real + self.executor.time_overhead());\n\n        // Initial timing run\n        let (res, status) = self.executor.run_command_and_measure(\n            self.command,\n            BenchmarkIteration::Benchmark(0),\n            None,\n            output_policy,\n        )?;\n        let success = status.success();\n\n        let conclusion_result = run_conclusion_command()?;\n        let conclusion_overhead =\n            conclusion_result.map_or(0.0, |res| res.time_real + self.executor.time_overhead());\n\n        // Determine number of benchmark runs\n        let runs_in_min_time = (self.options.min_benchmarking_time\n            / (res.time_real\n                + self.executor.time_overhead()\n                + preparation_overhead\n                + conclusion_overhead)) as u64;\n\n        let count = {\n            let min = cmp::max(runs_in_min_time, self.options.run_bounds.min);\n\n            self.options\n                .run_bounds\n                .max\n                .as_ref()\n                .map(|max| cmp::min(min, *max))\n                .unwrap_or(min)\n        };\n\n        let count_remaining = count - 1;\n\n        // Save the first result\n        times_real.push(res.time_real);\n        times_user.push(res.time_user);\n        times_system.push(res.time_system);\n        memory_usage_byte.push(res.memory_usage_byte);\n        exit_codes.push(extract_exit_code(status));\n\n        all_succeeded = all_succeeded && success;\n\n        // Re-configure the progress bar\n        if let Some(bar) = progress_bar.as_ref() {\n            bar.set_length(count)\n        }\n        if let Some(bar) = progress_bar.as_ref() {\n            bar.inc(1)\n        }\n\n        // Gather statistics (perform the actual benchmark)\n        for i in 0..count_remaining {\n            run_preparation_command()?;\n\n            let msg = {\n                let mean = format_duration(mean(&times_real), self.options.time_unit);\n                format!(\"Current estimate: {}\", mean.to_string().green())\n            };\n\n            if let Some(bar) = progress_bar.as_ref() {\n                bar.set_message(msg.to_owned())\n            }\n\n            let (res, status) = self.executor.run_command_and_measure(\n                self.command,\n                BenchmarkIteration::Benchmark(i + 1),\n                None,\n                output_policy,\n            )?;\n            let success = status.success();\n\n            times_real.push(res.time_real);\n            times_user.push(res.time_user);\n            times_system.push(res.time_system);\n            memory_usage_byte.push(res.memory_usage_byte);\n            exit_codes.push(extract_exit_code(status));\n\n            all_succeeded = all_succeeded && success;\n\n            if let Some(bar) = progress_bar.as_ref() {\n                bar.inc(1)\n            }\n\n            run_conclusion_command()?;\n        }\n\n        if let Some(bar) = progress_bar.as_ref() {\n            bar.finish_and_clear()\n        }\n\n        // Compute statistical quantities\n        let t_num = times_real.len();\n        let t_mean = mean(&times_real);\n        let t_stddev = if times_real.len() > 1 {\n            Some(standard_deviation(&times_real, Some(t_mean)))\n        } else {\n            None\n        };\n        let t_median = median(&times_real);\n        let t_min = min(&times_real);\n        let t_max = max(&times_real);\n\n        let user_mean = mean(&times_user);\n        let system_mean = mean(&times_system);\n\n        // Formatting and console output\n        let (mean_str, time_unit) = format_duration_unit(t_mean, self.options.time_unit);\n        let min_str = format_duration(t_min, Some(time_unit));\n        let max_str = format_duration(t_max, Some(time_unit));\n        let num_str = format!(\"{t_num} runs\");\n\n        let user_str = format_duration(user_mean, Some(time_unit));\n        let system_str = format_duration(system_mean, Some(time_unit));\n\n        if self.options.output_style != OutputStyleOption::Disabled {\n            if times_real.len() == 1 {\n                println!(\n                    \"  Time ({} ≡):        {:>8}  {:>8}     [User: {}, System: {}]\",\n                    \"abs\".green().bold(),\n                    mean_str.green().bold(),\n                    \"        \", // alignment\n                    user_str.blue(),\n                    system_str.blue()\n                );\n            } else {\n                let stddev_str = format_duration(t_stddev.unwrap(), Some(time_unit));\n\n                println!(\n                    \"  Time ({} ± {}):     {:>8} ± {:>8}    [User: {}, System: {}]\",\n                    \"mean\".green().bold(),\n                    \"σ\".green(),\n                    mean_str.green().bold(),\n                    stddev_str.green(),\n                    user_str.blue(),\n                    system_str.blue()\n                );\n\n                println!(\n                    \"  Range ({} … {}):   {:>8} … {:>8}    {}\",\n                    \"min\".cyan(),\n                    \"max\".purple(),\n                    min_str.cyan(),\n                    max_str.purple(),\n                    num_str.dimmed()\n                );\n            }\n        }\n\n        // Warnings\n        let mut warnings = vec![];\n\n        // Check execution time\n        if matches!(self.options.executor_kind, ExecutorKind::Shell(_))\n            && times_real.iter().any(|&t| t < MIN_EXECUTION_TIME)\n        {\n            warnings.push(Warnings::FastExecutionTime);\n        }\n\n        // Check program exit codes\n        if !all_succeeded {\n            warnings.push(Warnings::NonZeroExitCode);\n        }\n\n        // Run outlier detection\n        let scores = modified_zscores(&times_real);\n\n        let outlier_warning_options = OutlierWarningOptions {\n            warmup_in_use: self.options.warmup_count > 0,\n            prepare_in_use: self\n                .options\n                .preparation_command\n                .as_ref()\n                .map(|v| v.len())\n                .unwrap_or(0)\n                > 0,\n        };\n\n        if scores[0] > OUTLIER_THRESHOLD {\n            warnings.push(Warnings::SlowInitialRun(\n                times_real[0],\n                outlier_warning_options,\n            ));\n        } else if scores.iter().any(|&s| s.abs() > OUTLIER_THRESHOLD) {\n            warnings.push(Warnings::OutliersDetected(outlier_warning_options));\n        }\n\n        if !warnings.is_empty() {\n            eprintln!(\" \");\n\n            for warning in &warnings {\n                eprintln!(\"  {}: {}\", \"Warning\".yellow(), warning);\n            }\n        }\n\n        if self.options.output_style != OutputStyleOption::Disabled {\n            println!(\" \");\n        }\n\n        self.run_cleanup_command(self.command.get_parameters().iter().cloned(), output_policy)?;\n\n        Ok(BenchmarkResult {\n            command: self.command.get_name(),\n            command_with_unused_parameters: self.command.get_name_with_unused_parameters(),\n            mean: t_mean,\n            stddev: t_stddev,\n            median: t_median,\n            user: user_mean,\n            system: system_mean,\n            min: t_min,\n            max: t_max,\n            times: Some(times_real),\n            memory_usage_byte: Some(memory_usage_byte),\n            exit_codes,\n            parameters: self\n                .command\n                .get_parameters()\n                .iter()\n                .map(|(name, value)| (name.to_string(), value.to_string()))\n                .collect(),\n        })\n    }\n}\n"
  },
  {
    "path": "src/benchmark/relative_speed.rs",
    "content": "use std::cmp::Ordering;\n\nuse super::benchmark_result::BenchmarkResult;\nuse crate::{options::SortOrder, util::units::Scalar};\n\n#[derive(Debug)]\npub struct BenchmarkResultWithRelativeSpeed<'a> {\n    pub result: &'a BenchmarkResult,\n    pub relative_speed: Scalar,\n    pub relative_speed_stddev: Option<Scalar>,\n    pub is_reference: bool,\n    // Less means faster\n    pub relative_ordering: Ordering,\n}\n\npub fn compare_mean_time(l: &BenchmarkResult, r: &BenchmarkResult) -> Ordering {\n    l.mean.partial_cmp(&r.mean).unwrap_or(Ordering::Equal)\n}\n\npub fn fastest_of(results: &[BenchmarkResult]) -> &BenchmarkResult {\n    results\n        .iter()\n        .min_by(|&l, &r| compare_mean_time(l, r))\n        .expect(\"at least one benchmark result\")\n}\n\nfn compute_relative_speeds<'a>(\n    results: &'a [BenchmarkResult],\n    reference: &'a BenchmarkResult,\n    sort_order: SortOrder,\n) -> Vec<BenchmarkResultWithRelativeSpeed<'a>> {\n    let mut results: Vec<_> = results\n        .iter()\n        .map(|result| {\n            let is_reference = result == reference;\n            let relative_ordering = compare_mean_time(result, reference);\n\n            if result.mean == 0.0 {\n                return BenchmarkResultWithRelativeSpeed {\n                    result,\n                    relative_speed: if is_reference { 1.0 } else { f64::INFINITY },\n                    relative_speed_stddev: None,\n                    is_reference,\n                    relative_ordering,\n                };\n            }\n\n            let ratio = match relative_ordering {\n                Ordering::Less => reference.mean / result.mean,\n                Ordering::Equal => 1.0,\n                Ordering::Greater => result.mean / reference.mean,\n            };\n\n            // https://en.wikipedia.org/wiki/Propagation_of_uncertainty#Example_formulas\n            // Covariance asssumed to be 0, i.e. variables are assumed to be independent\n            let ratio_stddev = match (result.stddev, reference.stddev) {\n                (Some(result_stddev), Some(fastest_stddev)) => Some(\n                    ratio\n                        * ((result_stddev / result.mean).powi(2)\n                            + (fastest_stddev / reference.mean).powi(2))\n                        .sqrt(),\n                ),\n                _ => None,\n            };\n\n            BenchmarkResultWithRelativeSpeed {\n                result,\n                relative_speed: ratio,\n                relative_speed_stddev: ratio_stddev,\n                is_reference,\n                relative_ordering,\n            }\n        })\n        .collect();\n\n    match sort_order {\n        SortOrder::Command => {}\n        SortOrder::MeanTime => {\n            results.sort_unstable_by(|r1, r2| compare_mean_time(r1.result, r2.result));\n        }\n    }\n\n    results\n}\n\npub fn compute_with_check_from_reference<'a>(\n    results: &'a [BenchmarkResult],\n    reference: &'a BenchmarkResult,\n    sort_order: SortOrder,\n) -> Option<Vec<BenchmarkResultWithRelativeSpeed<'a>>> {\n    if fastest_of(results).mean == 0.0 || reference.mean == 0.0 {\n        return None;\n    }\n\n    Some(compute_relative_speeds(results, reference, sort_order))\n}\n\npub fn compute_with_check(\n    results: &[BenchmarkResult],\n    sort_order: SortOrder,\n) -> Option<Vec<BenchmarkResultWithRelativeSpeed<'_>>> {\n    let fastest = fastest_of(results);\n\n    if fastest.mean == 0.0 {\n        return None;\n    }\n\n    Some(compute_relative_speeds(results, fastest, sort_order))\n}\n\n/// Same as compute_with_check, potentially resulting in relative speeds of infinity\npub fn compute(\n    results: &[BenchmarkResult],\n    sort_order: SortOrder,\n) -> Vec<BenchmarkResultWithRelativeSpeed<'_>> {\n    let fastest = fastest_of(results);\n\n    compute_relative_speeds(results, fastest, sort_order)\n}\n\n#[cfg(test)]\nfn create_result(name: &str, mean: Scalar) -> BenchmarkResult {\n    use std::collections::BTreeMap;\n\n    BenchmarkResult {\n        command: name.into(),\n        command_with_unused_parameters: name.into(),\n        mean,\n        stddev: Some(1.0),\n        median: mean,\n        user: mean,\n        system: 0.0,\n        min: mean,\n        max: mean,\n        times: None,\n        memory_usage_byte: None,\n        exit_codes: Vec::new(),\n        parameters: BTreeMap::new(),\n    }\n}\n\n#[test]\nfn test_compute_relative_speed() {\n    use approx::assert_relative_eq;\n\n    let results = vec![\n        create_result(\"cmd1\", 3.0),\n        create_result(\"cmd2\", 2.0),\n        create_result(\"cmd3\", 5.0),\n    ];\n\n    let annotated_results = compute_with_check(&results, SortOrder::Command).unwrap();\n\n    assert_relative_eq!(1.5, annotated_results[0].relative_speed);\n    assert_relative_eq!(1.0, annotated_results[1].relative_speed);\n    assert_relative_eq!(2.5, annotated_results[2].relative_speed);\n}\n\n#[test]\nfn test_compute_relative_speed_with_reference() {\n    use approx::assert_relative_eq;\n\n    let results = vec![create_result(\"cmd2\", 2.0), create_result(\"cmd3\", 5.0)];\n    let reference = create_result(\"cmd2\", 4.0);\n\n    let annotated_results =\n        compute_with_check_from_reference(&results, &reference, SortOrder::Command).unwrap();\n\n    assert_relative_eq!(2.0, annotated_results[0].relative_speed);\n    assert_relative_eq!(1.25, annotated_results[1].relative_speed);\n}\n\n#[test]\nfn test_compute_relative_speed_for_zero_times() {\n    let results = vec![create_result(\"cmd1\", 1.0), create_result(\"cmd2\", 0.0)];\n\n    let annotated_results = compute_with_check(&results, SortOrder::Command);\n\n    assert!(annotated_results.is_none());\n}\n"
  },
  {
    "path": "src/benchmark/scheduler.rs",
    "content": "use super::benchmark_result::BenchmarkResult;\nuse super::executor::{Executor, MockExecutor, RawExecutor, ShellExecutor};\nuse super::{relative_speed, Benchmark};\nuse colored::*;\nuse std::cmp::Ordering;\n\nuse crate::command::{Command, Commands};\nuse crate::export::ExportManager;\nuse crate::options::{ExecutorKind, Options, OutputStyleOption, SortOrder};\n\nuse anyhow::Result;\n\npub struct Scheduler<'a> {\n    commands: &'a Commands<'a>,\n    options: &'a Options,\n    export_manager: &'a ExportManager,\n    results: Vec<BenchmarkResult>,\n}\n\nimpl<'a> Scheduler<'a> {\n    pub fn new(\n        commands: &'a Commands,\n        options: &'a Options,\n        export_manager: &'a ExportManager,\n    ) -> Self {\n        Self {\n            commands,\n            options,\n            export_manager,\n            results: vec![],\n        }\n    }\n\n    pub fn run_benchmarks(&mut self) -> Result<()> {\n        let mut executor: Box<dyn Executor> = match self.options.executor_kind {\n            ExecutorKind::Raw => Box::new(RawExecutor::new(self.options)),\n            ExecutorKind::Mock(ref shell) => Box::new(MockExecutor::new(shell.clone())),\n            ExecutorKind::Shell(ref shell) => Box::new(ShellExecutor::new(shell, self.options)),\n        };\n\n        let reference = self\n            .options\n            .reference_command\n            .as_ref()\n            .map(|cmd| Command::new(self.options.reference_name.as_deref(), cmd));\n\n        executor.calibrate()?;\n\n        for (number, cmd) in reference.iter().chain(self.commands.iter()).enumerate() {\n            self.results\n                .push(Benchmark::new(number, cmd, self.options, &*executor).run()?);\n\n            // We export results after each individual benchmark, because\n            // we would risk losing them if a later benchmark fails.\n            self.export_manager.write_results(&self.results, true)?;\n        }\n\n        Ok(())\n    }\n\n    pub fn print_relative_speed_comparison(&self) {\n        if self.options.output_style == OutputStyleOption::Disabled {\n            return;\n        }\n\n        if self.results.len() < 2 {\n            return;\n        }\n\n        let reference = self\n            .options\n            .reference_command\n            .as_ref()\n            .map(|_| &self.results[0])\n            .unwrap_or_else(|| relative_speed::fastest_of(&self.results));\n\n        if let Some(annotated_results) = relative_speed::compute_with_check_from_reference(\n            &self.results,\n            reference,\n            self.options.sort_order_speed_comparison,\n        ) {\n            match self.options.sort_order_speed_comparison {\n                SortOrder::MeanTime => {\n                    println!(\"{}\", \"Summary\".bold());\n\n                    let reference = annotated_results.iter().find(|r| r.is_reference).unwrap();\n                    let others = annotated_results.iter().filter(|r| !r.is_reference);\n\n                    println!(\n                        \"  {} ran\",\n                        reference.result.command_with_unused_parameters.cyan()\n                    );\n\n                    for item in others {\n                        let stddev = if let Some(stddev) = item.relative_speed_stddev {\n                            format!(\" ± {}\", format!(\"{:.2}\", stddev).green())\n                        } else {\n                            \"\".into()\n                        };\n                        let comparator = match item.relative_ordering {\n                            Ordering::Less => format!(\n                                \"{}{} times slower than\",\n                                format!(\"{:8.2}\", item.relative_speed).bold().green(),\n                                stddev\n                            ),\n                            Ordering::Greater => format!(\n                                \"{}{} times faster than\",\n                                format!(\"{:8.2}\", item.relative_speed).bold().green(),\n                                stddev\n                            ),\n                            Ordering::Equal => format!(\n                                \"    As fast ({}{}) as\",\n                                format!(\"{:.2}\", item.relative_speed).bold().green(),\n                                stddev\n                            ),\n                        };\n                        println!(\n                            \"{} {}\",\n                            comparator,\n                            &item.result.command_with_unused_parameters.magenta()\n                        );\n                    }\n                }\n                SortOrder::Command => {\n                    println!(\"{}\", \"Relative speed comparison\".bold());\n\n                    for item in annotated_results {\n                        println!(\n                            \"  {}{}  {}\",\n                            format!(\"{:10.2}\", item.relative_speed).bold().green(),\n                            if item.is_reference {\n                                \"        \".into()\n                            } else if let Some(stddev) = item.relative_speed_stddev {\n                                format!(\" ± {}\", format!(\"{stddev:5.2}\").green())\n                            } else {\n                                \"        \".into()\n                            },\n                            &item.result.command_with_unused_parameters,\n                        );\n                    }\n                }\n            }\n        } else {\n            eprintln!(\n                \"{}: The benchmark comparison could not be computed as some benchmark times are zero. \\\n                 This could be caused by background interference during the initial calibration phase \\\n                 of hyperfine, in combination with very fast commands (faster than a few milliseconds). \\\n                 Try to re-run the benchmark on a quiet system. If you did not do so already, try the \\\n                 --shell=none/-N option. If it does not help either, you command is most likely too fast \\\n                 to be accurately benchmarked by hyperfine.\",\n                 \"Note\".bold().red()\n            );\n        }\n    }\n\n    pub fn final_export(&self) -> Result<()> {\n        self.export_manager.write_results(&self.results, false)\n    }\n}\n\n#[cfg(test)]\nfn generate_results(args: &[&'static str]) -> Result<Vec<BenchmarkResult>> {\n    use crate::cli::get_cli_arguments;\n\n    let args = [\"hyperfine\", \"--debug-mode\", \"--style=none\"]\n        .iter()\n        .chain(args);\n    let cli_arguments = get_cli_arguments(args);\n    let mut options = Options::from_cli_arguments(&cli_arguments)?;\n\n    assert_eq!(options.executor_kind, ExecutorKind::Mock(None));\n\n    let commands = Commands::from_cli_arguments(&cli_arguments)?;\n    let export_manager = ExportManager::from_cli_arguments(\n        &cli_arguments,\n        options.time_unit,\n        options.sort_order_exports,\n    )?;\n\n    options.validate_against_command_list(&commands)?;\n\n    let mut scheduler = Scheduler::new(&commands, &options, &export_manager);\n\n    scheduler.run_benchmarks()?;\n    Ok(scheduler.results)\n}\n\n#[test]\nfn scheduler_basic() -> Result<()> {\n    insta::assert_yaml_snapshot!(generate_results(&[\"--runs=2\", \"sleep 0.123\", \"sleep 0.456\"])?, @r#\"\n    - command: sleep 0.123\n      mean: 0.123\n      stddev: 0\n      median: 0.123\n      user: 0\n      system: 0\n      min: 0.123\n      max: 0.123\n      times:\n        - 0.123\n        - 0.123\n      memory_usage_byte:\n        - 0\n        - 0\n      exit_codes:\n        - 0\n        - 0\n    - command: sleep 0.456\n      mean: 0.456\n      stddev: 0\n      median: 0.456\n      user: 0\n      system: 0\n      min: 0.456\n      max: 0.456\n      times:\n        - 0.456\n        - 0.456\n      memory_usage_byte:\n        - 0\n        - 0\n      exit_codes:\n        - 0\n        - 0\n    \"#);\n\n    Ok(())\n}\n"
  },
  {
    "path": "src/benchmark/timing_result.rs",
    "content": "use crate::util::units::Second;\n\n/// Results from timing a single command\n#[derive(Debug, Default, Copy, Clone)]\npub struct TimingResult {\n    /// Wall clock time\n    pub time_real: Second,\n\n    /// Time spent in user mode\n    pub time_user: Second,\n\n    /// Time spent in kernel mode\n    pub time_system: Second,\n\n    /// Maximum amount of memory used, in bytes\n    pub memory_usage_byte: u64,\n}\n"
  },
  {
    "path": "src/cli.rs",
    "content": "use std::ffi::OsString;\n\nuse clap::{\n    builder::NonEmptyStringValueParser, crate_version, Arg, ArgAction, ArgMatches, Command,\n    ValueHint,\n};\n\npub fn get_cli_arguments<'a, I, T>(args: I) -> ArgMatches\nwhere\n    I: IntoIterator<Item = T>,\n    T: Into<OsString> + Clone + 'a,\n{\n    let command = build_command();\n    command.get_matches_from(args)\n}\n\n/// Build the clap command for parsing command line arguments\nfn build_command() -> Command {\n    Command::new(\"hyperfine\")\n        .version(crate_version!())\n        .next_line_help(true)\n        .hide_possible_values(true)\n        .about(\"A command-line benchmarking tool.\")\n        .help_expected(true)\n        .max_term_width(80)\n        .arg(\n            Arg::new(\"command\")\n                .help(\"The command to benchmark. This can be the name of an executable, a command \\\n                       line like \\\"grep -i todo\\\" or a shell command like \\\"sleep 0.5 && echo test\\\". \\\n                       The latter is only available if the shell is not explicitly disabled via \\\n                       '--shell=none'. If multiple commands are given, hyperfine will show a \\\n                       comparison of the respective runtimes.\")\n                .required(true)\n                .action(ArgAction::Append)\n                .value_hint(ValueHint::CommandString)\n                .value_parser(NonEmptyStringValueParser::new()),\n        )\n        .arg(\n            Arg::new(\"warmup\")\n                .long(\"warmup\")\n                .short('w')\n                .value_name(\"NUM\")\n                .action(ArgAction::Set)\n                .help(\n                    \"Perform NUM warmup runs before the actual benchmark. This can be used \\\n                     to fill (disk) caches for I/O-heavy programs.\",\n                ),\n        )\n        .arg(\n            Arg::new(\"min-runs\")\n                .long(\"min-runs\")\n                .short('m')\n                .action(ArgAction::Set)\n                .value_name(\"NUM\")\n                .help(\"Perform at least NUM runs for each command (default: 10).\"),\n        )\n        .arg(\n            Arg::new(\"max-runs\")\n                .long(\"max-runs\")\n                .short('M')\n                .action(ArgAction::Set)\n                .value_name(\"NUM\")\n                .help(\"Perform at most NUM runs for each command. By default, there is no limit.\"),\n        )\n        .arg(\n            Arg::new(\"runs\")\n                .long(\"runs\")\n                .conflicts_with_all([\"max-runs\", \"min-runs\"])\n                .short('r')\n                .action(ArgAction::Set)\n                .value_name(\"NUM\")\n                .help(\"Perform exactly NUM runs for each command. If this option is not specified, \\\n                       hyperfine automatically determines the number of runs.\"),\n        )\n        .arg(\n            Arg::new(\"setup\")\n                .long(\"setup\")\n                .short('s')\n                .action(ArgAction::Set)\n                .value_name(\"CMD\")\n                .value_hint(ValueHint::CommandString)\n                .help(\n                    \"Execute CMD before each set of timing runs. This is useful for \\\n                     compiling your software with the provided parameters, or to do any \\\n                     other work that should happen once before a series of benchmark runs, \\\n                     not every time as would happen with the --prepare option.\"\n                ),\n        )\n        .arg(\n            Arg::new(\"reference\")\n                .long(\"reference\")\n                .action(ArgAction::Set)\n                .value_name(\"CMD\")\n                .help(\n                    \"The reference command for the relative comparison of results. \\\n                    If this is unset, results are compared with the fastest command as reference.\"\n                )\n        )\n        .arg(\n            Arg::new(\"reference-name\")\n                .long(\"reference-name\")\n                .action(ArgAction::Set)\n                .value_name(\"CMD\")\n                .help(\"Give a meaningful name to the reference command.\")\n                .requires(\"reference\")\n        )\n        .arg(\n            Arg::new(\"prepare\")\n                .long(\"prepare\")\n                .short('p')\n                .action(ArgAction::Append)\n                .num_args(1)\n                .value_name(\"CMD\")\n                .value_hint(ValueHint::CommandString)\n                .help(\n                    \"Execute CMD before each timing run. This is useful for \\\n                     clearing disk caches, for example.\\nThe --prepare option can \\\n                     be specified once for all commands or multiple times, once for \\\n                     each command. In the latter case, each preparation command will \\\n                     be run prior to the corresponding benchmark command.\",\n                ),\n        )\n        .arg(\n            Arg::new(\"conclude\")\n                .long(\"conclude\")\n                .short('C')\n                .action(ArgAction::Append)\n                .num_args(1)\n                .value_name(\"CMD\")\n                .value_hint(ValueHint::CommandString)\n                .help(\n                    \"Execute CMD after each timing run. This is useful for killing \\\n                     long-running processes started (e.g. a web server started in --prepare), \\\n                     for example.\\nThe --conclude option can be specified once for all \\\n                     commands or multiple times, once for each command. In the latter case, \\\n                     each conclude command will be run after the corresponding benchmark \\\n                     command.\",\n                ),\n        )\n        .arg(\n            Arg::new(\"cleanup\")\n                .long(\"cleanup\")\n                .short('c')\n                .action(ArgAction::Set)\n                .value_name(\"CMD\")\n                .value_hint(ValueHint::CommandString)\n                .help(\n                    \"Execute CMD after the completion of all benchmarking \\\n                     runs for each individual command to be benchmarked. \\\n                     This is useful if the commands to be benchmarked produce \\\n                     artifacts that need to be cleaned up.\"\n                ),\n        )\n        .arg(\n            Arg::new(\"parameter-scan\")\n                .long(\"parameter-scan\")\n                .short('P')\n                .action(ArgAction::Set)\n                .allow_hyphen_values(true)\n                .value_names([\"VAR\", \"MIN\", \"MAX\"])\n                .help(\n                    \"Perform benchmark runs for each value in the range MIN..MAX. Replaces the \\\n                     string '{VAR}' in each command by the current parameter value.\\n\\n  \\\n                     Example:  hyperfine -P threads 1 8 'make -j {threads}'\\n\\n\\\n                     This performs benchmarks for 'make -j 1', 'make -j 2', …, 'make -j 8'.\\n\\n\\\n                     To have the value increase following different patterns, use shell arithmetics.\\n\\n  \\\n                     Example: hyperfine -P size 0 3 'sleep $((2**{size}))'\\n\\n\\\n                     This performs benchmarks with power of 2 increases: 'sleep 1', 'sleep 2', 'sleep 4', …\\n\\\n                     The exact syntax may vary depending on your shell and OS.\"\n                ),\n        )\n        .arg(\n            Arg::new(\"parameter-step-size\")\n                .long(\"parameter-step-size\")\n                .short('D')\n                .action(ArgAction::Set)\n                .value_names([\"DELTA\"])\n                .requires(\"parameter-scan\")\n                .help(\n                    \"This argument requires --parameter-scan to be specified as well. \\\n                     Traverse the range MIN..MAX in steps of DELTA.\\n\\n  \\\n                     Example:  hyperfine -P delay 0.3 0.7 -D 0.2 'sleep {delay}'\\n\\n\\\n                     This performs benchmarks for 'sleep 0.3', 'sleep 0.5' and 'sleep 0.7'.\",\n                ),\n        )\n        .arg(\n            Arg::new(\"parameter-list\")\n                .long(\"parameter-list\")\n                .short('L')\n                .action(ArgAction::Append)\n                .allow_hyphen_values(true)\n                .value_names([\"VAR\", \"VALUES\"])\n                .conflicts_with_all([\"parameter-scan\", \"parameter-step-size\"])\n                .help(\n                    \"Perform benchmark runs for each value in the comma-separated list VALUES. \\\n                     Replaces the string '{VAR}' in each command by the current parameter value\\\n                     .\\n\\nExample:  hyperfine -L compiler gcc,clang '{compiler} -O2 main.cpp'\\n\\n\\\n                     This performs benchmarks for 'gcc -O2 main.cpp' and 'clang -O2 main.cpp'.\\n\\n\\\n                     The option can be specified multiple times to run benchmarks for all \\\n                     possible parameter combinations.\\n\"\n                ),\n        )\n        .arg(\n            Arg::new(\"shell\")\n                .long(\"shell\")\n                .short('S')\n                .action(ArgAction::Set)\n                .value_name(\"SHELL\")\n                .overrides_with(\"shell\")\n                .value_hint(ValueHint::CommandString)\n                .help(\"Set the shell to use for executing benchmarked commands. This can be the \\\n                       name or the path to the shell executable, or a full command line \\\n                       like \\\"bash --norc\\\". It can also be set to \\\"default\\\" to explicitly select \\\n                       the default shell on this platform. Finally, this can also be set to \\\n                       \\\"none\\\" to disable the shell. In this case, commands will be executed \\\n                       directly. They can still have arguments, but more complex things like \\\n                       \\\"sleep 0.1; sleep 0.2\\\" are not possible without a shell.\")\n        )\n        .arg(\n            Arg::new(\"no-shell\")\n                .short('N')\n                .action(ArgAction::SetTrue)\n                .conflicts_with_all([\"shell\", \"debug-mode\"])\n                .help(\"An alias for '--shell=none'.\")\n        )\n        .arg(\n            Arg::new(\"ignore-failure\")\n                .long(\"ignore-failure\")\n                .action(ArgAction::Set)\n                .value_name(\"MODE\")\n                .num_args(0..=1)\n                .default_missing_value(\"all-non-zero\")\n                .require_equals(true)\n                .short('i')\n                .help(\"Ignore failures of the benchmarked programs. Without a value or with \\\n                       'all-non-zero', all non-zero exit codes are ignored. You can also provide \\\n                       a comma-separated list of exit codes to ignore (e.g., --ignore-failure=1,2).\"),\n        )\n        .arg(\n            Arg::new(\"style\")\n                .long(\"style\")\n                .action(ArgAction::Set)\n                .value_name(\"TYPE\")\n                .value_parser([\"auto\", \"basic\", \"full\", \"nocolor\", \"color\", \"none\"])\n                .help(\n                    \"Set output style type (default: auto). Set this to 'basic' to disable output \\\n                     coloring and interactive elements. Set it to 'full' to enable all effects \\\n                     even if no interactive terminal was detected. Set this to 'nocolor' to \\\n                     keep the interactive output without any colors. Set this to 'color' to keep \\\n                     the colors without any interactive output. Set this to 'none' to disable all \\\n                     the output of the tool.\",\n                ),\n        )\n        .arg(\n            Arg::new(\"sort\")\n            .long(\"sort\")\n            .action(ArgAction::Set)\n            .value_name(\"METHOD\")\n            .value_parser([\"auto\", \"command\", \"mean-time\"])\n            .default_value(\"auto\")\n            .hide_default_value(true)\n            .help(\n                \"Specify the sort order of the speed comparison summary and the exported tables for \\\n                 markup formats (Markdown, AsciiDoc, org-mode):\\n  \\\n                   * 'auto' (default): the speed comparison will be ordered by time and\\n    \\\n                     the markup tables will be ordered by command (input order).\\n  \\\n                   * 'command': order benchmarks in the way they were specified\\n  \\\n                   * 'mean-time': order benchmarks by mean runtime\\n\"\n            ),\n        )\n        .arg(\n            Arg::new(\"time-unit\")\n                .long(\"time-unit\")\n                .short('u')\n                .action(ArgAction::Set)\n                .value_name(\"UNIT\")\n                .value_parser([\"microsecond\", \"millisecond\", \"second\"])\n                .help(\"Set the time unit to be used. Possible values: microsecond, millisecond, second. \\\n                       If the option is not given, the time unit is determined automatically. \\\n                       This option affects the standard output as well as all export formats except for CSV and JSON.\"),\n        )\n        .arg(\n            Arg::new(\"export-asciidoc\")\n                .long(\"export-asciidoc\")\n                .action(ArgAction::Set)\n                .value_name(\"FILE\")\n                .value_hint(ValueHint::FilePath)\n                .help(\"Export the timing summary statistics as an AsciiDoc table to the given FILE. \\\n                       The output time unit can be changed using the --time-unit option.\"),\n        )\n        .arg(\n            Arg::new(\"export-csv\")\n                .long(\"export-csv\")\n                .action(ArgAction::Set)\n                .value_name(\"FILE\")\n                .value_hint(ValueHint::FilePath)\n                .help(\"Export the timing summary statistics as CSV to the given FILE. If you need \\\n                       the timing results for each individual run, use the JSON export format. \\\n                       The output time unit is always seconds.\"),\n        )\n        .arg(\n            Arg::new(\"export-json\")\n                .long(\"export-json\")\n                .action(ArgAction::Set)\n                .value_name(\"FILE\")\n                .value_hint(ValueHint::FilePath)\n                .help(\"Export the timing summary statistics and timings of individual runs as JSON to the given FILE. \\\n                       The output time unit is always seconds\"),\n        )\n        .arg(\n            Arg::new(\"export-markdown\")\n                .long(\"export-markdown\")\n                .action(ArgAction::Set)\n                .value_name(\"FILE\")\n                .value_hint(ValueHint::FilePath)\n                .help(\"Export the timing summary statistics as a Markdown table to the given FILE. \\\n                       The output time unit can be changed using the --time-unit option.\"),\n        )\n        .arg(\n            Arg::new(\"export-orgmode\")\n                .long(\"export-orgmode\")\n                .action(ArgAction::Set)\n                .value_name(\"FILE\")\n                .value_hint(ValueHint::FilePath)\n                .help(\"Export the timing summary statistics as an Emacs org-mode table to the given FILE. \\\n                       The output time unit can be changed using the --time-unit option.\"),\n        )\n        .arg(\n            Arg::new(\"show-output\")\n                .long(\"show-output\")\n                .action(ArgAction::SetTrue)\n                .conflicts_with(\"style\")\n                .help(\n                    \"Print the stdout and stderr of the benchmark instead of suppressing it. \\\n                     This will increase the time it takes for benchmarks to run, \\\n                     so it should only be used for debugging purposes or \\\n                     when trying to benchmark output speed.\",\n                ),\n        )\n        .arg(\n            Arg::new(\"output\")\n                .long(\"output\")\n                .conflicts_with(\"show-output\")\n                .action(ArgAction::Append)\n                .value_name(\"WHERE\")\n                .help(\n                    \"Control where the output of the benchmark is redirected. Note \\\n                     that some programs like 'grep' detect when standard output is \\\n                     /dev/null and apply certain optimizations. To avoid that, consider \\\n                     using '--output=pipe'.\\n\\\n                     \\n\\\n                     <WHERE> can be:\\n\\\n                     \\n  \\\n                       null:     Redirect output to /dev/null (the default).\\n\\\n                     \\n  \\\n                       pipe:     Feed the output through a pipe before discarding it.\\n\\\n                     \\n  \\\n                       inherit:  Don't redirect the output at all (same as '--show-output').\\n\\\n                     \\n  \\\n                       <FILE>:   Write the output to the given file.\\n\\n\\\n                    This option can be specified once for all commands or multiple times, once for \\\n                    each command. Note: If you want to log the output of each and every iteration, \\\n                    you can use a shell redirection and the '$HYPERFINE_ITERATION' environment variable:\\n    \\\n                    hyperfine 'my-command > output-${HYPERFINE_ITERATION}.log'\\n\\n\",\n                ),\n        )\n        .arg(\n            Arg::new(\"input\")\n                .long(\"input\")\n                .action(ArgAction::Set)\n                .num_args(1)\n                .value_name(\"WHERE\")\n                .help(\"Control where the input of the benchmark comes from.\\n\\\n                       \\n\\\n                       <WHERE> can be:\\n\\\n                       \\n  \\\n                         null:     Read from /dev/null (the default).\\n\\\n                       \\n  \\\n                         <FILE>:   Read the input from the given file.\"),\n        )\n        .arg(\n            Arg::new(\"command-name\")\n                .long(\"command-name\")\n                .short('n')\n                .action(ArgAction::Append)\n                .num_args(1)\n                .value_name(\"NAME\")\n                .help(\"Give a meaningful name to a command. This can be specified multiple times \\\n                       if several commands are benchmarked.\"),\n        )\n        // This option is hidden for now, as it is not yet clear yet if we want to 'stabilize' this,\n        // see discussion in https://github.com/sharkdp/hyperfine/issues/527\n        .arg(\n            Arg::new(\"min-benchmarking-time\")\n            .long(\"min-benchmarking-time\")\n            .action(ArgAction::Set)\n            .hide(true)\n            .help(\"Set the minimum time (in seconds) to run benchmarks. Note that the number of \\\n                   benchmark runs is additionally influenced by the `--min-runs`, `--max-runs`, and \\\n                   `--runs` option.\")\n        )\n        .arg(\n            Arg::new(\"debug-mode\")\n            .long(\"debug-mode\")\n            .action(ArgAction::SetTrue)\n            .hide(true)\n            .help(\"Enable debug mode which does not actually run commands, but returns fake times when the command is 'sleep <time>'.\")\n        )\n}\n\n#[test]\nfn verify_app() {\n    build_command().debug_assert();\n}\n"
  },
  {
    "path": "src/command.rs",
    "content": "use std::collections::BTreeMap;\nuse std::fmt;\nuse std::str::FromStr;\n\nuse crate::parameter::tokenize::tokenize;\nuse crate::parameter::ParameterValue;\nuse crate::{\n    error::{OptionsError, ParameterScanError},\n    parameter::{\n        range_step::{Numeric, RangeStep},\n        ParameterNameAndValue,\n    },\n};\n\nuse clap::{parser::ValuesRef, ArgMatches};\n\nuse anyhow::{bail, Context, Result};\nuse rust_decimal::Decimal;\n\n/// A command that should be benchmarked.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub struct Command<'a> {\n    /// The command name (without parameter substitution)\n    name: Option<&'a str>,\n\n    /// The command that should be executed (without parameter substitution)\n    expression: &'a str,\n\n    /// Zero or more parameter values.\n    parameters: Vec<ParameterNameAndValue<'a>>,\n}\n\nimpl<'a> Command<'a> {\n    pub fn new(name: Option<&'a str>, expression: &'a str) -> Command<'a> {\n        Command {\n            name,\n            expression,\n            parameters: Vec::new(),\n        }\n    }\n\n    pub fn new_parametrized(\n        name: Option<&'a str>,\n        expression: &'a str,\n        parameters: impl IntoIterator<Item = ParameterNameAndValue<'a>>,\n    ) -> Command<'a> {\n        Command {\n            name,\n            expression,\n            parameters: parameters.into_iter().collect(),\n        }\n    }\n\n    pub fn get_name(&self) -> String {\n        self.name.map_or_else(\n            || self.get_command_line(),\n            |name| self.replace_parameters_in(name),\n        )\n    }\n\n    pub fn get_name_with_unused_parameters(&self) -> String {\n        let parameters = self\n            .get_unused_parameters()\n            .fold(String::new(), |output, (parameter, value)| {\n                output + &format!(\"{parameter} = {value}, \")\n            });\n        let parameters = parameters.trim_end_matches(\", \");\n        let parameters = if parameters.is_empty() {\n            \"\".into()\n        } else {\n            format!(\" ({parameters})\")\n        };\n\n        format!(\"{}{}\", self.get_name(), parameters)\n    }\n\n    pub fn get_command_line(&self) -> String {\n        self.replace_parameters_in(self.expression)\n    }\n\n    pub fn get_command(&self) -> Result<std::process::Command> {\n        let command_line = self.get_command_line();\n        let mut tokens = shell_words::split(&command_line)\n            .with_context(|| format!(\"Failed to parse command '{command_line}'\"))?\n            .into_iter();\n\n        if let Some(program_name) = tokens.next() {\n            let mut command_builder = std::process::Command::new(program_name);\n            command_builder.args(tokens);\n            Ok(command_builder)\n        } else {\n            bail!(\"Can not execute empty command\")\n        }\n    }\n\n    pub fn get_parameters(&self) -> &[(&'a str, ParameterValue)] {\n        &self.parameters\n    }\n\n    pub fn get_unused_parameters(&self) -> impl Iterator<Item = &(&'a str, ParameterValue)> {\n        self.parameters\n            .iter()\n            .filter(move |(parameter, _)| !self.expression.contains(&format!(\"{{{parameter}}}\")))\n    }\n\n    fn replace_parameters_in(&self, original: &str) -> String {\n        let mut result = String::new();\n        let mut replacements = BTreeMap::<String, String>::new();\n        for (param_name, param_value) in &self.parameters {\n            replacements.insert(format!(\"{{{param_name}}}\"), param_value.to_string());\n        }\n        let mut remaining = original;\n        // Manually replace consecutive occurrences to avoid double-replacing: e.g.,\n        //\n        //     hyperfine -L foo 'a,{bar}' -L bar 'baz,quux' 'echo {foo} {bar}'\n        //\n        // should not ever run 'echo baz baz'. See `test_get_command_line_nonoverlapping`.\n        'outer: while let Some(head) = remaining.chars().next() {\n            for (k, v) in &replacements {\n                if remaining.starts_with(k.as_str()) {\n                    result.push_str(v);\n                    remaining = &remaining[k.len()..];\n                    continue 'outer;\n                }\n            }\n            result.push(head);\n            remaining = &remaining[head.len_utf8()..];\n        }\n        result\n    }\n}\n\n/// A collection of commands that should be benchmarked\npub struct Commands<'a>(Vec<Command<'a>>);\n\nimpl<'a> Commands<'a> {\n    pub fn from_cli_arguments(matches: &'a ArgMatches) -> Result<Commands<'a>> {\n        let command_names = matches.get_many::<String>(\"command-name\");\n        let command_strings = matches\n            .get_many::<String>(\"command\")\n            .unwrap_or_default()\n            .map(|v| v.as_str())\n            .collect::<Vec<_>>();\n\n        if let Some(args) = matches.get_many::<String>(\"parameter-scan\") {\n            let step_size = matches\n                .get_one::<String>(\"parameter-step-size\")\n                .map(|s| s.as_str());\n            Ok(Self(Self::get_parameter_scan_commands(\n                command_names,\n                command_strings,\n                args,\n                step_size,\n            )?))\n        } else if let Some(args) = matches.get_many::<String>(\"parameter-list\") {\n            let command_names = command_names.map_or(vec![], |names| {\n                names.map(|v| v.as_str()).collect::<Vec<_>>()\n            });\n            let args: Vec<_> = args.map(|v| v.as_str()).collect::<Vec<_>>();\n            let param_names_and_values: Vec<(&str, Vec<String>)> = args\n                .chunks_exact(2)\n                .map(|pair| {\n                    let name = pair[0];\n                    let list_str = pair[1];\n                    (name, tokenize(list_str))\n                })\n                .collect();\n            {\n                let duplicates =\n                    Self::find_duplicates(param_names_and_values.iter().map(|(name, _)| *name));\n                if !duplicates.is_empty() {\n                    bail!(\"Duplicate parameter names: {}\", &duplicates.join(\", \"));\n                }\n            }\n\n            let dimensions: Vec<usize> = std::iter::once(command_strings.len())\n                .chain(\n                    param_names_and_values\n                        .iter()\n                        .map(|(_, values)| values.len()),\n                )\n                .collect();\n            let param_space_size = dimensions.iter().product();\n            if param_space_size == 0 {\n                return Ok(Self(Vec::new()));\n            }\n\n            // `--command-name` should appear exactly once or exactly B times,\n            // where B is the total number of benchmarks.\n            let command_name_count = command_names.len();\n            if command_name_count > 1 && command_name_count != param_space_size {\n                return Err(OptionsError::UnexpectedCommandNameCount(\n                    command_name_count,\n                    param_space_size,\n                )\n                .into());\n            }\n\n            let mut i = 0;\n            let mut commands = Vec::with_capacity(param_space_size);\n            let mut index = vec![0usize; dimensions.len()];\n            'outer: loop {\n                let name = command_names\n                    .get(i)\n                    .or_else(|| command_names.first())\n                    .copied();\n                i += 1;\n\n                let (command_index, params_indices) = index.split_first().unwrap();\n                let parameters: Vec<_> = param_names_and_values\n                    .iter()\n                    .zip(params_indices)\n                    .map(|((name, values), i)| (*name, ParameterValue::Text(values[*i].clone())))\n                    .collect();\n                commands.push(Command::new_parametrized(\n                    name,\n                    command_strings[*command_index],\n                    parameters,\n                ));\n\n                // Increment index, exiting loop on overflow.\n                for (i, n) in index.iter_mut().zip(dimensions.iter()) {\n                    *i += 1;\n                    if *i < *n {\n                        continue 'outer;\n                    } else {\n                        *i = 0;\n                    }\n                }\n                break 'outer;\n            }\n\n            Ok(Self(commands))\n        } else {\n            let command_names = command_names.map_or(vec![], |names| {\n                names.map(|v| v.as_str()).collect::<Vec<_>>()\n            });\n            if command_names.len() > command_strings.len() {\n                return Err(OptionsError::TooManyCommandNames(command_strings.len()).into());\n            }\n\n            let mut commands = Vec::with_capacity(command_strings.len());\n            for (i, s) in command_strings.iter().enumerate() {\n                commands.push(Command::new(command_names.get(i).copied(), s));\n            }\n            Ok(Self(commands))\n        }\n    }\n\n    pub fn iter(&self) -> impl Iterator<Item = &Command<'a>> {\n        self.0.iter()\n    }\n\n    pub fn num_commands(&self, has_reference_command: bool) -> usize {\n        self.0.len() + if has_reference_command { 1 } else { 0 }\n    }\n\n    /// Finds all the strings that appear multiple times in the input iterator, returning them in\n    /// sorted order. If no string appears more than once, the result is an empty vector.\n    fn find_duplicates<'b, I: IntoIterator<Item = &'b str>>(i: I) -> Vec<&'b str> {\n        let mut counts = BTreeMap::<&'b str, usize>::new();\n        for s in i {\n            *counts.entry(s).or_default() += 1;\n        }\n        counts\n            .into_iter()\n            .filter_map(|(k, n)| if n > 1 { Some(k) } else { None })\n            .collect()\n    }\n\n    fn build_parameter_scan_commands<'b, T: Numeric>(\n        param_name: &'b str,\n        param_min: T,\n        param_max: T,\n        step: T,\n        command_names: Vec<&'b str>,\n        command_strings: Vec<&'b str>,\n    ) -> Result<Vec<Command<'b>>, ParameterScanError> {\n        let param_range = RangeStep::new(param_min, param_max, step)?;\n        let command_name_count = command_names.len();\n\n        let mut i = 0;\n        let mut commands = vec![];\n        for value in param_range {\n            for cmd in &command_strings {\n                let name = command_names\n                    .get(i)\n                    .or_else(|| command_names.first())\n                    .copied();\n                commands.push(Command::new_parametrized(\n                    name,\n                    cmd,\n                    vec![(param_name, ParameterValue::Numeric(value.into()))],\n                ));\n                i += 1;\n            }\n        }\n\n        // `--command-name` should appear exactly once or exactly B times,\n        // where B is the total number of benchmarks.\n        let command_count = commands.len();\n        if command_name_count > 1 && command_name_count != command_count {\n            return Err(ParameterScanError::UnexpectedCommandNameCount(\n                command_name_count,\n                command_count,\n            ));\n        }\n\n        Ok(commands)\n    }\n\n    fn get_parameter_scan_commands<'b>(\n        command_names: Option<ValuesRef<'b, String>>,\n        command_strings: Vec<&'b str>,\n        mut vals: ValuesRef<'b, String>,\n        step: Option<&str>,\n    ) -> Result<Vec<Command<'b>>, ParameterScanError> {\n        let command_names = command_names.map_or(vec![], |names| {\n            names.map(|v| v.as_str()).collect::<Vec<_>>()\n        });\n        let param_name = vals.next().unwrap().as_str();\n        let param_min = vals.next().unwrap().as_str();\n        let param_max = vals.next().unwrap().as_str();\n\n        // attempt to parse as integers\n        if let (Ok(param_min), Ok(param_max), Ok(step)) = (\n            param_min.parse::<i32>(),\n            param_max.parse::<i32>(),\n            step.unwrap_or(\"1\").parse::<i32>(),\n        ) {\n            return Self::build_parameter_scan_commands(\n                param_name,\n                param_min,\n                param_max,\n                step,\n                command_names,\n                command_strings,\n            );\n        }\n\n        // try parsing them as decimals\n        let param_min = Decimal::from_str(param_min)?;\n        let param_max = Decimal::from_str(param_max)?;\n\n        if step.is_none() {\n            return Err(ParameterScanError::StepRequired);\n        }\n\n        let step = Decimal::from_str(step.unwrap())?;\n        Self::build_parameter_scan_commands(\n            param_name,\n            param_min,\n            param_max,\n            step,\n            command_names,\n            command_strings,\n        )\n    }\n}\n\n#[test]\nfn test_get_command_line_nonoverlapping() {\n    let cmd = Command::new_parametrized(\n        None,\n        \"echo {foo} {bar}\",\n        vec![\n            (\"foo\", ParameterValue::Text(\"{bar} baz\".into())),\n            (\"bar\", ParameterValue::Text(\"quux\".into())),\n        ],\n    );\n    assert_eq!(cmd.get_command_line(), \"echo {bar} baz quux\");\n}\n\n#[test]\nfn test_get_parameterized_command_name() {\n    let cmd = Command::new_parametrized(\n        Some(\"name-{bar}-{foo}\"),\n        \"echo {foo} {bar}\",\n        vec![\n            (\"foo\", ParameterValue::Text(\"baz\".into())),\n            (\"bar\", ParameterValue::Text(\"quux\".into())),\n        ],\n    );\n    assert_eq!(cmd.get_name(), \"name-quux-baz\");\n}\n\nimpl fmt::Display for Command<'_> {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        write!(f, \"{}\", self.get_command_line())\n    }\n}\n\n#[test]\nfn test_build_commands_cross_product() {\n    use crate::cli::get_cli_arguments;\n\n    let matches = get_cli_arguments(vec![\n        \"hyperfine\",\n        \"-L\",\n        \"par1\",\n        \"a,b\",\n        \"-L\",\n        \"par2\",\n        \"z,y\",\n        \"echo {par1} {par2}\",\n        \"printf '%s\\n' {par1} {par2}\",\n    ]);\n    let result = Commands::from_cli_arguments(&matches).unwrap().0;\n\n    // Iteration order: command list first, then parameters in listed order (here, \"par1\" before\n    // \"par2\", which is distinct from their sorted order), with parameter values in listed order.\n    let pv = |s: &str| ParameterValue::Text(s.to_string());\n    let cmd = |cmd: usize, par1: &str, par2: &str| {\n        let expression = [\"echo {par1} {par2}\", \"printf '%s\\n' {par1} {par2}\"][cmd];\n        let params = vec![(\"par1\", pv(par1)), (\"par2\", pv(par2))];\n        Command::new_parametrized(None, expression, params)\n    };\n    let expected = vec![\n        cmd(0, \"a\", \"z\"),\n        cmd(1, \"a\", \"z\"),\n        cmd(0, \"b\", \"z\"),\n        cmd(1, \"b\", \"z\"),\n        cmd(0, \"a\", \"y\"),\n        cmd(1, \"a\", \"y\"),\n        cmd(0, \"b\", \"y\"),\n        cmd(1, \"b\", \"y\"),\n    ];\n    assert_eq!(result, expected);\n}\n\n#[test]\nfn test_build_parameter_list_commands() {\n    use crate::cli::get_cli_arguments;\n\n    let matches = get_cli_arguments(vec![\n        \"hyperfine\",\n        \"echo {foo}\",\n        \"--parameter-list\",\n        \"foo\",\n        \"1,2\",\n        \"--command-name\",\n        \"name-{foo}\",\n    ]);\n    let commands = Commands::from_cli_arguments(&matches).unwrap().0;\n    assert_eq!(commands.len(), 2);\n    assert_eq!(commands[0].get_name(), \"name-1\");\n    assert_eq!(commands[1].get_name(), \"name-2\");\n    assert_eq!(commands[0].get_command_line(), \"echo 1\");\n    assert_eq!(commands[1].get_command_line(), \"echo 2\");\n}\n\n#[test]\nfn test_build_parameter_scan_commands() {\n    use crate::cli::get_cli_arguments;\n    let matches = get_cli_arguments(vec![\n        \"hyperfine\",\n        \"echo {val}\",\n        \"--parameter-scan\",\n        \"val\",\n        \"1\",\n        \"2\",\n        \"--parameter-step-size\",\n        \"1\",\n        \"--command-name\",\n        \"name-{val}\",\n    ]);\n    let commands = Commands::from_cli_arguments(&matches).unwrap().0;\n    assert_eq!(commands.len(), 2);\n    assert_eq!(commands[0].get_name(), \"name-1\");\n    assert_eq!(commands[1].get_name(), \"name-2\");\n    assert_eq!(commands[0].get_command_line(), \"echo 1\");\n    assert_eq!(commands[1].get_command_line(), \"echo 2\");\n}\n\n#[test]\nfn test_build_parameter_scan_commands_named() {\n    use crate::cli::get_cli_arguments;\n    let matches = get_cli_arguments(vec![\n        \"hyperfine\",\n        \"echo {val}\",\n        \"sleep {val}\",\n        \"--parameter-scan\",\n        \"val\",\n        \"1\",\n        \"2\",\n        \"--parameter-step-size\",\n        \"1\",\n        \"--command-name\",\n        \"echo-1\",\n        \"--command-name\",\n        \"sleep-1\",\n        \"--command-name\",\n        \"echo-2\",\n        \"--command-name\",\n        \"sleep-2\",\n    ]);\n    let commands = Commands::from_cli_arguments(&matches).unwrap().0;\n    assert_eq!(commands.len(), 4);\n    assert_eq!(commands[0].get_name(), \"echo-1\");\n    assert_eq!(commands[0].get_command_line(), \"echo 1\");\n    assert_eq!(commands[1].get_name(), \"sleep-1\");\n    assert_eq!(commands[1].get_command_line(), \"sleep 1\");\n    assert_eq!(commands[2].get_name(), \"echo-2\");\n    assert_eq!(commands[2].get_command_line(), \"echo 2\");\n    assert_eq!(commands[3].get_name(), \"sleep-2\");\n    assert_eq!(commands[3].get_command_line(), \"sleep 2\");\n}\n\n#[test]\nfn test_parameter_scan_commands_int() {\n    let commands = Commands::build_parameter_scan_commands(\n        \"val\",\n        1i32,\n        7i32,\n        3i32,\n        vec![],\n        vec![\"echo {val}\"],\n    )\n    .unwrap();\n    assert_eq!(commands.len(), 3);\n    assert_eq!(commands[2].get_name(), \"echo 7\");\n    assert_eq!(commands[2].get_command_line(), \"echo 7\");\n}\n\n#[test]\nfn test_parameter_scan_commands_decimal() {\n    let param_min = Decimal::from_str(\"0\").unwrap();\n    let param_max = Decimal::from_str(\"1\").unwrap();\n    let step = Decimal::from_str(\"0.33\").unwrap();\n\n    let commands = Commands::build_parameter_scan_commands(\n        \"val\",\n        param_min,\n        param_max,\n        step,\n        vec![],\n        vec![\"echo {val}\"],\n    )\n    .unwrap();\n    assert_eq!(commands.len(), 4);\n    assert_eq!(commands[3].get_name(), \"echo 0.99\");\n    assert_eq!(commands[3].get_command_line(), \"echo 0.99\");\n}\n\n#[test]\nfn test_parameter_scan_commands_names() {\n    let commands = Commands::build_parameter_scan_commands(\n        \"val\",\n        1i32,\n        3i32,\n        1i32,\n        vec![\"name-{val}\"],\n        vec![\"echo {val}\"],\n    )\n    .unwrap();\n    assert_eq!(commands.len(), 3);\n    let command_names = commands\n        .iter()\n        .map(|c| c.get_name())\n        .collect::<Vec<String>>();\n    assert_eq!(command_names, vec![\"name-1\", \"name-2\", \"name-3\"]);\n}\n\n#[test]\nfn test_get_specified_command_names() {\n    let commands = Commands::build_parameter_scan_commands(\n        \"val\",\n        1i32,\n        3i32,\n        1i32,\n        vec![\"name-a\", \"name-b\", \"name-c\"],\n        vec![\"echo {val}\"],\n    )\n    .unwrap();\n    assert_eq!(commands.len(), 3);\n    let command_names = commands\n        .iter()\n        .map(|c| c.get_name())\n        .collect::<Vec<String>>();\n    assert_eq!(command_names, vec![\"name-a\", \"name-b\", \"name-c\"]);\n}\n\n#[test]\nfn test_different_command_name_count_with_parameters() {\n    let result = Commands::build_parameter_scan_commands(\n        \"val\",\n        1i32,\n        3i32,\n        1i32,\n        vec![\"name-1\", \"name-2\"],\n        vec![\"echo {val}\"],\n    );\n    assert!(matches!(\n        result.unwrap_err(),\n        ParameterScanError::UnexpectedCommandNameCount(2, 3)\n    ));\n}\n"
  },
  {
    "path": "src/error.rs",
    "content": "use std::num::{self, ParseFloatError, ParseIntError};\n\nuse rust_decimal::Error as DecimalError;\nuse thiserror::Error;\n\n#[derive(Debug, Error)]\npub enum ParameterScanError {\n    #[error(\"Error while parsing parameter scan arguments ({0})\")]\n    ParseIntError(num::ParseIntError),\n    #[error(\"Error while parsing parameter scan arguments ({0})\")]\n    ParseDecimalError(DecimalError),\n    #[error(\"Empty parameter range\")]\n    EmptyRange,\n    #[error(\"Parameter range is too large\")]\n    TooLarge,\n    #[error(\"Zero is not a valid parameter step\")]\n    ZeroStep,\n    #[error(\"A step size is required when the range bounds are floating point numbers. The step size can be specified with the '-D/--parameter-step-size <DELTA>' parameter\")]\n    StepRequired,\n    #[error(\"'--command-name' has been specified {0} times. It has to appear exactly once, or exactly {1} times (number of benchmarks)\")]\n    UnexpectedCommandNameCount(usize, usize),\n}\n\nimpl From<num::ParseIntError> for ParameterScanError {\n    fn from(e: num::ParseIntError) -> ParameterScanError {\n        ParameterScanError::ParseIntError(e)\n    }\n}\n\nimpl From<DecimalError> for ParameterScanError {\n    fn from(e: DecimalError) -> ParameterScanError {\n        ParameterScanError::ParseDecimalError(e)\n    }\n}\n\n#[derive(Debug, Error)]\npub enum OptionsError<'a> {\n    #[error(\n        \"Conflicting requirements for the number of runs (empty range, min is larger than max)\"\n    )]\n    EmptyRunsRange,\n    #[error(\"Too many --command-name options: Expected {0} at most\")]\n    TooManyCommandNames(usize),\n    #[error(\"'--command-name' has been specified {0} times. It has to appear exactly once, or exactly {1} times (number of benchmarks)\")]\n    UnexpectedCommandNameCount(usize, usize),\n    #[error(\"Could not read numeric integer argument to '--{0}': {1}\")]\n    IntParsingError(&'a str, ParseIntError),\n    #[error(\"Could not read numeric floating point argument to '--{0}': {1}\")]\n    FloatParsingError(&'a str, ParseFloatError),\n    #[error(\"An empty command has been specified for the '--shell <command>' option\")]\n    EmptyShell,\n    #[error(\"Failed to parse '--shell <command>' expression as command line: {0}\")]\n    ShellParseError(shell_words::ParseError),\n    #[error(\"Unknown output policy '{0}'. Use './{0}' to output to a file named '{0}'.\")]\n    UnknownOutputPolicy(String),\n    #[error(\"The file '{0}' specified as '--input' does not exist\")]\n    StdinDataFileDoesNotExist(String),\n}\n"
  },
  {
    "path": "src/export/asciidoc.rs",
    "content": "use super::markup::Alignment;\nuse crate::export::markup::MarkupExporter;\n\n#[derive(Default)]\npub struct AsciidocExporter {}\n\nimpl MarkupExporter for AsciidocExporter {\n    fn table_header(&self, cell_aligmnents: &[Alignment]) -> String {\n        format!(\n            \"[cols=\\\"{}\\\"]\\n|===\",\n            cell_aligmnents\n                .iter()\n                .map(|a| match a {\n                    Alignment::Left => \"<\",\n                    Alignment::Right => \">\",\n                })\n                .collect::<Vec<&str>>()\n                .join(\",\")\n        )\n    }\n\n    fn table_footer(&self, _cell_aligmnents: &[Alignment]) -> String {\n        \"|===\\n\".to_string()\n    }\n\n    fn table_row(&self, cells: &[&str]) -> String {\n        format!(\"\\n| {} \\n\", cells.join(\" \\n| \"))\n    }\n\n    fn table_divider(&self, _cell_aligmnents: &[Alignment]) -> String {\n        \"\".to_string()\n    }\n\n    fn command(&self, cmd: &str) -> String {\n        format!(\"`{cmd}`\")\n    }\n}\n\n/// Check Asciidoc-based data row formatting\n#[test]\nfn test_asciidoc_exporter_table_data() {\n    let exporter = AsciidocExporter::default();\n    let data = vec![\"a\", \"b\", \"c\"];\n\n    let actual = exporter.table_row(&data);\n    let expect = \"\\n| a \\n| b \\n| c \\n\";\n\n    assert_eq!(expect, actual);\n}\n\n/// Check Asciidoc-based table header formatting\n#[test]\nfn test_asciidoc_exporter_table_header() {\n    let exporter = AsciidocExporter::default();\n    let cells_alignment = [\n        Alignment::Left,\n        Alignment::Right,\n        Alignment::Right,\n        Alignment::Right,\n        Alignment::Right,\n    ];\n\n    let actual = exporter.table_header(&cells_alignment);\n    let expect = \"[cols=\\\"<,>,>,>,>\\\"]\\n|===\";\n\n    assert_eq!(expect, actual);\n}\n"
  },
  {
    "path": "src/export/csv.rs",
    "content": "use std::borrow::Cow;\n\nuse csv::WriterBuilder;\n\nuse super::Exporter;\nuse crate::benchmark::benchmark_result::BenchmarkResult;\nuse crate::options::SortOrder;\nuse crate::util::units::Unit;\n\nuse anyhow::Result;\n\n#[derive(Default)]\npub struct CsvExporter {}\n\nimpl Exporter for CsvExporter {\n    fn serialize(\n        &self,\n        results: &[BenchmarkResult],\n        _unit: Option<Unit>,\n        _sort_order: SortOrder,\n    ) -> Result<Vec<u8>> {\n        let mut writer = WriterBuilder::new().from_writer(vec![]);\n\n        {\n            let mut headers: Vec<Cow<[u8]>> = [\n                // The list of times and exit codes cannot be exported to the CSV file - omit them.\n                \"command\", \"mean\", \"stddev\", \"median\", \"user\", \"system\", \"min\", \"max\",\n            ]\n            .iter()\n            .map(|x| Cow::Borrowed(x.as_bytes()))\n            .collect();\n            if let Some(res) = results.first() {\n                for param_name in res.parameters.keys() {\n                    headers.push(Cow::Owned(format!(\"parameter_{param_name}\").into_bytes()));\n                }\n            }\n            writer.write_record(headers)?;\n        }\n\n        for res in results {\n            let mut fields = vec![Cow::Borrowed(res.command.as_bytes())];\n            for f in &[\n                res.mean,\n                res.stddev.unwrap_or(0.0),\n                res.median,\n                res.user,\n                res.system,\n                res.min,\n                res.max,\n            ] {\n                fields.push(Cow::Owned(f.to_string().into_bytes()))\n            }\n            for v in res.parameters.values() {\n                fields.push(Cow::Borrowed(v.as_bytes()))\n            }\n            writer.write_record(fields)?;\n        }\n\n        Ok(writer.into_inner()?)\n    }\n}\n\n#[test]\nfn test_csv() {\n    use std::collections::BTreeMap;\n    let exporter = CsvExporter::default();\n\n    let results = vec![\n        BenchmarkResult {\n            command: String::from(\"command_a\"),\n            command_with_unused_parameters: String::from(\"command_a\"),\n            mean: 1.0,\n            stddev: Some(2.0),\n            median: 1.0,\n            user: 3.0,\n            system: 4.0,\n            min: 5.0,\n            max: 6.0,\n            times: Some(vec![7.0, 8.0, 9.0]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: {\n                let mut params = BTreeMap::new();\n                params.insert(\"foo\".into(), \"one\".into());\n                params.insert(\"bar\".into(), \"two\".into());\n                params\n            },\n        },\n        BenchmarkResult {\n            command: String::from(\"command_b\"),\n            command_with_unused_parameters: String::from(\"command_b\"),\n            mean: 11.0,\n            stddev: Some(12.0),\n            median: 11.0,\n            user: 13.0,\n            system: 14.0,\n            min: 15.0,\n            max: 16.5,\n            times: Some(vec![17.0, 18.0, 19.0]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: {\n                let mut params = BTreeMap::new();\n                params.insert(\"foo\".into(), \"one\".into());\n                params.insert(\"bar\".into(), \"seven\".into());\n                params\n            },\n        },\n    ];\n\n    let actual = String::from_utf8(\n        exporter\n            .serialize(&results, Some(Unit::Second), SortOrder::Command)\n            .unwrap(),\n    )\n    .unwrap();\n\n    insta::assert_snapshot!(actual, @r#\"\n    command,mean,stddev,median,user,system,min,max,parameter_bar,parameter_foo\n    command_a,1,2,1,3,4,5,6,two,one\n    command_b,11,12,11,13,14,15,16.5,seven,one\n    \"#);\n}\n"
  },
  {
    "path": "src/export/json.rs",
    "content": "use serde::*;\nuse serde_json::to_vec_pretty;\n\nuse super::Exporter;\nuse crate::benchmark::benchmark_result::BenchmarkResult;\nuse crate::options::SortOrder;\nuse crate::util::units::Unit;\n\nuse anyhow::Result;\n\n#[derive(Serialize, Debug)]\nstruct HyperfineSummary<'a> {\n    results: &'a [BenchmarkResult],\n}\n\n#[derive(Default)]\npub struct JsonExporter {}\n\nimpl Exporter for JsonExporter {\n    fn serialize(\n        &self,\n        results: &[BenchmarkResult],\n        _unit: Option<Unit>,\n        _sort_order: SortOrder,\n    ) -> Result<Vec<u8>> {\n        let mut output = to_vec_pretty(&HyperfineSummary { results });\n        if let Ok(ref mut content) = output {\n            content.push(b'\\n');\n        }\n\n        Ok(output?)\n    }\n}\n"
  },
  {
    "path": "src/export/markdown.rs",
    "content": "use crate::export::markup::MarkupExporter;\n\nuse super::markup::Alignment;\n\n#[derive(Default)]\npub struct MarkdownExporter {}\n\nimpl MarkupExporter for MarkdownExporter {\n    fn table_row(&self, cells: &[&str]) -> String {\n        format!(\"| {} |\\n\", cells.join(\" | \"))\n    }\n\n    fn table_divider(&self, cell_aligmnents: &[Alignment]) -> String {\n        format!(\n            \"|{}\\n\",\n            cell_aligmnents\n                .iter()\n                .map(|a| match a {\n                    Alignment::Left => \":---|\",\n                    Alignment::Right => \"---:|\",\n                })\n                .collect::<String>()\n        )\n    }\n\n    fn command(&self, cmd: &str) -> String {\n        format!(\"`{cmd}`\")\n    }\n}\n\n/// Check Markdown-based data row formatting\n#[test]\nfn test_markdown_formatter_table_data() {\n    let formatter = MarkdownExporter::default();\n\n    assert_eq!(formatter.table_row(&[\"a\", \"b\", \"c\"]), \"| a | b | c |\\n\");\n}\n\n/// Check Markdown-based horizontal line formatting\n#[test]\nfn test_markdown_formatter_table_divider() {\n    let formatter = MarkdownExporter::default();\n\n    let divider = formatter.table_divider(&[Alignment::Left, Alignment::Right, Alignment::Left]);\n    assert_eq!(divider, \"|:---|---:|:---|\\n\");\n}\n"
  },
  {
    "path": "src/export/markup.rs",
    "content": "use crate::benchmark::relative_speed::BenchmarkResultWithRelativeSpeed;\nuse crate::benchmark::{benchmark_result::BenchmarkResult, relative_speed};\nuse crate::options::SortOrder;\nuse crate::output::format::format_duration_value;\nuse crate::util::units::Unit;\n\nuse super::Exporter;\nuse anyhow::Result;\n\npub enum Alignment {\n    Left,\n    Right,\n}\n\npub trait MarkupExporter {\n    fn table_results(&self, entries: &[BenchmarkResultWithRelativeSpeed], unit: Unit) -> String {\n        // prepare table header strings\n        let notation = format!(\"[{}]\", unit.short_name());\n\n        // prepare table cells alignment\n        let cells_alignment = [\n            Alignment::Left,\n            Alignment::Right,\n            Alignment::Right,\n            Alignment::Right,\n            Alignment::Right,\n        ];\n\n        // emit table header format\n        let mut table = self.table_header(&cells_alignment);\n\n        // emit table header data\n        table.push_str(&self.table_row(&[\n            \"Command\",\n            &format!(\"Mean {notation}\"),\n            &format!(\"Min {notation}\"),\n            &format!(\"Max {notation}\"),\n            \"Relative\",\n        ]));\n\n        // emit horizontal line\n        table.push_str(&self.table_divider(&cells_alignment));\n\n        for entry in entries {\n            let measurement = &entry.result;\n            // prepare data row strings\n            let cmd_str = measurement\n                .command_with_unused_parameters\n                .replace('|', \"\\\\|\");\n            let mean_str = format_duration_value(measurement.mean, Some(unit)).0;\n            let stddev_str = if let Some(stddev) = measurement.stddev {\n                format!(\" ± {}\", format_duration_value(stddev, Some(unit)).0)\n            } else {\n                \"\".into()\n            };\n            let min_str = format_duration_value(measurement.min, Some(unit)).0;\n            let max_str = format_duration_value(measurement.max, Some(unit)).0;\n            let rel_str = format!(\"{:.2}\", entry.relative_speed);\n            let rel_stddev_str = if entry.is_reference {\n                \"\".into()\n            } else if let Some(stddev) = entry.relative_speed_stddev {\n                format!(\" ± {stddev:.2}\")\n            } else {\n                \"\".into()\n            };\n\n            // prepare table row entries\n            table.push_str(&self.table_row(&[\n                &self.command(&cmd_str),\n                &format!(\"{mean_str}{stddev_str}\"),\n                &min_str,\n                &max_str,\n                &format!(\"{rel_str}{rel_stddev_str}\"),\n            ]))\n        }\n\n        // emit table footer format\n        table.push_str(&self.table_footer(&cells_alignment));\n\n        table\n    }\n\n    fn table_row(&self, cells: &[&str]) -> String;\n\n    fn table_divider(&self, cell_aligmnents: &[Alignment]) -> String;\n\n    fn table_header(&self, _cell_aligmnents: &[Alignment]) -> String {\n        \"\".to_string()\n    }\n\n    fn table_footer(&self, _cell_aligmnents: &[Alignment]) -> String {\n        \"\".to_string()\n    }\n\n    fn command(&self, size: &str) -> String;\n}\n\nfn determine_unit_from_results(results: &[BenchmarkResult]) -> Unit {\n    if let Some(first_result) = results.first() {\n        // Use the first BenchmarkResult entry to determine the unit for all entries.\n        format_duration_value(first_result.mean, None).1\n    } else {\n        // Default to `Second`.\n        Unit::Second\n    }\n}\n\nimpl<T: MarkupExporter> Exporter for T {\n    fn serialize(\n        &self,\n        results: &[BenchmarkResult],\n        unit: Option<Unit>,\n        sort_order: SortOrder,\n    ) -> Result<Vec<u8>> {\n        let unit = unit.unwrap_or_else(|| determine_unit_from_results(results));\n        let entries = relative_speed::compute(results, sort_order);\n\n        let table = self.table_results(&entries, unit);\n        Ok(table.as_bytes().to_vec())\n    }\n}\n"
  },
  {
    "path": "src/export/mod.rs",
    "content": "use std::fs::{File, OpenOptions};\nuse std::io::Write;\n\nmod asciidoc;\nmod csv;\nmod json;\nmod markdown;\nmod markup;\nmod orgmode;\n#[cfg(test)]\nmod tests;\n\nuse self::asciidoc::AsciidocExporter;\nuse self::csv::CsvExporter;\nuse self::json::JsonExporter;\nuse self::markdown::MarkdownExporter;\nuse self::orgmode::OrgmodeExporter;\n\nuse crate::benchmark::benchmark_result::BenchmarkResult;\nuse crate::options::SortOrder;\nuse crate::util::units::Unit;\n\nuse anyhow::{Context, Result};\nuse clap::ArgMatches;\n\n/// The desired form of exporter to use for a given file.\n#[derive(Clone)]\npub enum ExportType {\n    /// Asciidoc Table\n    Asciidoc,\n\n    /// CSV (comma separated values) format\n    Csv,\n\n    /// JSON format\n    Json,\n\n    /// Markdown table\n    Markdown,\n\n    /// Emacs org-mode tables\n    Orgmode,\n}\n\n/// Interface for different exporters.\ntrait Exporter {\n    /// Export the given entries in the serialized form.\n    fn serialize(\n        &self,\n        results: &[BenchmarkResult],\n        unit: Option<Unit>,\n        sort_order: SortOrder,\n    ) -> Result<Vec<u8>>;\n}\n\npub enum ExportTarget {\n    File(String),\n    Stdout,\n}\n\nstruct ExporterWithTarget {\n    exporter: Box<dyn Exporter>,\n    target: ExportTarget,\n}\n\n/// Handles the management of multiple file exporters.\npub struct ExportManager {\n    exporters: Vec<ExporterWithTarget>,\n    time_unit: Option<Unit>,\n    sort_order: SortOrder,\n}\n\nimpl ExportManager {\n    /// Build the ExportManager that will export the results specified\n    /// in the given ArgMatches\n    pub fn from_cli_arguments(\n        matches: &ArgMatches,\n        time_unit: Option<Unit>,\n        sort_order: SortOrder,\n    ) -> Result<Self> {\n        let mut export_manager = Self {\n            exporters: vec![],\n            time_unit,\n            sort_order,\n        };\n        {\n            let mut add_exporter = |flag, exporttype| -> Result<()> {\n                if let Some(filename) = matches.get_one::<String>(flag) {\n                    export_manager.add_exporter(exporttype, filename)?;\n                }\n                Ok(())\n            };\n            add_exporter(\"export-asciidoc\", ExportType::Asciidoc)?;\n            add_exporter(\"export-json\", ExportType::Json)?;\n            add_exporter(\"export-csv\", ExportType::Csv)?;\n            add_exporter(\"export-markdown\", ExportType::Markdown)?;\n            add_exporter(\"export-orgmode\", ExportType::Orgmode)?;\n        }\n        Ok(export_manager)\n    }\n\n    /// Add an additional exporter to the ExportManager\n    pub fn add_exporter(&mut self, export_type: ExportType, filename: &str) -> Result<()> {\n        let exporter: Box<dyn Exporter> = match export_type {\n            ExportType::Asciidoc => Box::<AsciidocExporter>::default(),\n            ExportType::Csv => Box::<CsvExporter>::default(),\n            ExportType::Json => Box::<JsonExporter>::default(),\n            ExportType::Markdown => Box::<MarkdownExporter>::default(),\n            ExportType::Orgmode => Box::<OrgmodeExporter>::default(),\n        };\n\n        self.exporters.push(ExporterWithTarget {\n            exporter,\n            target: if filename == \"-\" {\n                ExportTarget::Stdout\n            } else {\n                let _ = File::create(filename)\n                    .with_context(|| format!(\"Could not create export file '{filename}'\"))?;\n                ExportTarget::File(filename.to_string())\n            },\n        });\n\n        Ok(())\n    }\n\n    /// Write the given results to all Exporters. The 'intermediate' flag specifies\n    /// whether this is being called while still performing benchmarks, or if this\n    /// is the final call after all benchmarks have been finished. In the former case,\n    /// results are written to all file targets (to always have them up to date, even\n    /// if a benchmark fails). In the latter case, we only print to stdout targets (in\n    /// order not to clutter the output of hyperfine with intermediate results).\n    pub fn write_results(&self, results: &[BenchmarkResult], intermediate: bool) -> Result<()> {\n        for e in &self.exporters {\n            let content = || {\n                e.exporter\n                    .serialize(results, self.time_unit, self.sort_order)\n            };\n\n            match e.target {\n                ExportTarget::File(ref filename) => {\n                    if intermediate {\n                        write_to_file(filename, &content()?)?\n                    }\n                }\n                ExportTarget::Stdout => {\n                    if !intermediate {\n                        println!();\n                        println!(\"{}\", String::from_utf8(content()?).unwrap());\n                    }\n                }\n            }\n        }\n        Ok(())\n    }\n}\n\n/// Write the given content to a file with the specified name\nfn write_to_file(filename: &str, content: &[u8]) -> Result<()> {\n    let mut file = OpenOptions::new().write(true).open(filename)?;\n    file.write_all(content)\n        .with_context(|| format!(\"Failed to export results to '{filename}'\"))\n}\n"
  },
  {
    "path": "src/export/orgmode.rs",
    "content": "use super::markup::Alignment;\nuse crate::export::markup::MarkupExporter;\n\n#[derive(Default)]\npub struct OrgmodeExporter {}\n\nimpl MarkupExporter for OrgmodeExporter {\n    fn table_row(&self, cells: &[&str]) -> String {\n        format!(\n            \"| {}  |  {} |\\n\",\n            cells.first().unwrap(),\n            &cells[1..].join(\" |  \")\n        )\n    }\n\n    fn table_divider(&self, cell_aligmnents: &[Alignment]) -> String {\n        format!(\"|{}--|\\n\", \"--+\".repeat(cell_aligmnents.len() - 1))\n    }\n\n    fn command(&self, cmd: &str) -> String {\n        format!(\"={cmd}=\")\n    }\n}\n\n/// Check Emacs org-mode data row formatting\n#[test]\nfn test_orgmode_formatter_table_data() {\n    let exporter = OrgmodeExporter::default();\n\n    let actual = exporter.table_row(&[\"a\", \"b\", \"c\"]);\n    let expect = \"| a  |  b |  c |\\n\";\n\n    assert_eq!(expect, actual);\n}\n\n/// Check Emacs org-mode horizontal line formatting\n#[test]\nfn test_orgmode_formatter_table_line() {\n    let exporter = OrgmodeExporter::default();\n\n    let actual = exporter.table_divider(&[\n        Alignment::Left,\n        Alignment::Left,\n        Alignment::Left,\n        Alignment::Left,\n        Alignment::Left,\n    ]);\n    let expect = \"|--+--+--+--+--|\\n\";\n\n    assert_eq!(expect, actual);\n}\n"
  },
  {
    "path": "src/export/tests.rs",
    "content": "use super::Exporter;\nuse crate::benchmark::benchmark_result::BenchmarkResult;\nuse crate::export::asciidoc::AsciidocExporter;\nuse crate::export::orgmode::OrgmodeExporter;\nuse crate::util::units::Unit;\nuse crate::{export::markdown::MarkdownExporter, options::SortOrder};\nuse std::collections::BTreeMap;\n\nfn get_output<E: Exporter + Default>(\n    results: &[BenchmarkResult],\n    unit: Option<Unit>,\n    sort_order: SortOrder,\n) -> String {\n    let exporter = E::default();\n    String::from_utf8(exporter.serialize(results, unit, sort_order).unwrap()).unwrap()\n}\n\n/// Ensure the makrup output includes the table header and the multiple\n/// benchmark results as a table. The list of actual times is not included\n/// in the output.\n///\n/// This also demonstrates that the first entry's units (ms) are used to set\n/// the units for all entries when the time unit is not specified.\n#[test]\nfn test_markup_export_auto_ms() {\n    let results = [\n        BenchmarkResult {\n            command: String::from(\"sleep 0.1\"),\n            command_with_unused_parameters: String::from(\"sleep 0.1\"),\n            mean: 0.1057,\n            stddev: Some(0.0016),\n            median: 0.1057,\n            user: 0.0009,\n            system: 0.0011,\n            min: 0.1023,\n            max: 0.1080,\n            times: Some(vec![0.1, 0.1, 0.1]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: BTreeMap::new(),\n        },\n        BenchmarkResult {\n            command: String::from(\"sleep 2\"),\n            command_with_unused_parameters: String::from(\"sleep 2\"),\n            mean: 2.0050,\n            stddev: Some(0.0020),\n            median: 2.0050,\n            user: 0.0009,\n            system: 0.0012,\n            min: 2.0020,\n            max: 2.0080,\n            times: Some(vec![2.0, 2.0, 2.0]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: BTreeMap::new(),\n        },\n    ];\n\n    insta::assert_snapshot!(get_output::<MarkdownExporter>(&results, None, SortOrder::Command), @r#\"\n    | Command | Mean [ms] | Min [ms] | Max [ms] | Relative |\n    |:---|---:|---:|---:|---:|\n    | `sleep 0.1` | 105.7 ± 1.6 | 102.3 | 108.0 | 1.00 |\n    | `sleep 2` | 2005.0 ± 2.0 | 2002.0 | 2008.0 | 18.97 ± 0.29 |\n    \"#);\n\n    insta::assert_snapshot!(get_output::<AsciidocExporter>(&results, None, SortOrder::Command), @r#\"\n    [cols=\"<,>,>,>,>\"]\n    |===\n    | Command \n    | Mean [ms] \n    | Min [ms] \n    | Max [ms] \n    | Relative \n\n    | `sleep 0.1` \n    | 105.7 ± 1.6 \n    | 102.3 \n    | 108.0 \n    | 1.00 \n\n    | `sleep 2` \n    | 2005.0 ± 2.0 \n    | 2002.0 \n    | 2008.0 \n    | 18.97 ± 0.29 \n    |===\n    \"#);\n\n    insta::assert_snapshot!(get_output::<OrgmodeExporter>(&results, None, SortOrder::Command), @r#\"\n    | Command  |  Mean [ms] |  Min [ms] |  Max [ms] |  Relative |\n    |--+--+--+--+--|\n    | =sleep 0.1=  |  105.7 ± 1.6 |  102.3 |  108.0 |  1.00 |\n    | =sleep 2=  |  2005.0 ± 2.0 |  2002.0 |  2008.0 |  18.97 ± 0.29 |\n    \"#);\n}\n\n/// This (again) demonstrates that the first entry's units (s) are used to set\n/// the units for all entries when the time unit is not given.\n#[test]\nfn test_markup_export_auto_s() {\n    let results = [\n        BenchmarkResult {\n            command: String::from(\"sleep 2\"),\n            command_with_unused_parameters: String::from(\"sleep 2\"),\n            mean: 2.0050,\n            stddev: Some(0.0020),\n            median: 2.0050,\n            user: 0.0009,\n            system: 0.0012,\n            min: 2.0020,\n            max: 2.0080,\n            times: Some(vec![2.0, 2.0, 2.0]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: BTreeMap::new(),\n        },\n        BenchmarkResult {\n            command: String::from(\"sleep 0.1\"),\n            command_with_unused_parameters: String::from(\"sleep 0.1\"),\n            mean: 0.1057,\n            stddev: Some(0.0016),\n            median: 0.1057,\n            user: 0.0009,\n            system: 0.0011,\n            min: 0.1023,\n            max: 0.1080,\n            times: Some(vec![0.1, 0.1, 0.1]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: BTreeMap::new(),\n        },\n    ];\n\n    insta::assert_snapshot!(get_output::<MarkdownExporter>(&results, None, SortOrder::Command), @r#\"\n    | Command | Mean [s] | Min [s] | Max [s] | Relative |\n    |:---|---:|---:|---:|---:|\n    | `sleep 2` | 2.005 ± 0.002 | 2.002 | 2.008 | 18.97 ± 0.29 |\n    | `sleep 0.1` | 0.106 ± 0.002 | 0.102 | 0.108 | 1.00 |\n    \"#);\n\n    insta::assert_snapshot!(get_output::<AsciidocExporter>(&results, None, SortOrder::Command), @r#\"\n    [cols=\"<,>,>,>,>\"]\n    |===\n    | Command \n    | Mean [s] \n    | Min [s] \n    | Max [s] \n    | Relative \n\n    | `sleep 2` \n    | 2.005 ± 0.002 \n    | 2.002 \n    | 2.008 \n    | 18.97 ± 0.29 \n\n    | `sleep 0.1` \n    | 0.106 ± 0.002 \n    | 0.102 \n    | 0.108 \n    | 1.00 \n    |===\n    \"#);\n\n    insta::assert_snapshot!(get_output::<OrgmodeExporter>(&results, None, SortOrder::Command), @r#\"\n    | Command  |  Mean [s] |  Min [s] |  Max [s] |  Relative |\n    |--+--+--+--+--|\n    | =sleep 2=  |  2.005 ± 0.002 |  2.002 |  2.008 |  18.97 ± 0.29 |\n    | =sleep 0.1=  |  0.106 ± 0.002 |  0.102 |  0.108 |  1.00 |\n    \"#);\n}\n\n/// This (again) demonstrates that the given time unit (ms) is used to set\n/// the units for all entries.\n#[test]\nfn test_markup_export_manual_ms() {\n    let timing_results = [\n        BenchmarkResult {\n            command: String::from(\"sleep 2\"),\n            command_with_unused_parameters: String::from(\"sleep 2\"),\n            mean: 2.0050,\n            stddev: Some(0.0020),\n            median: 2.0050,\n            user: 0.0009,\n            system: 0.0012,\n            min: 2.0020,\n            max: 2.0080,\n            times: Some(vec![2.0, 2.0, 2.0]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: BTreeMap::new(),\n        },\n        BenchmarkResult {\n            command: String::from(\"sleep 0.1\"),\n            command_with_unused_parameters: String::from(\"sleep 0.1\"),\n            mean: 0.1057,\n            stddev: Some(0.0016),\n            median: 0.1057,\n            user: 0.0009,\n            system: 0.0011,\n            min: 0.1023,\n            max: 0.1080,\n            times: Some(vec![0.1, 0.1, 0.1]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: BTreeMap::new(),\n        },\n    ];\n\n    insta::assert_snapshot!(get_output::<MarkdownExporter>(&timing_results, Some(Unit::MilliSecond), SortOrder::Command), @r#\"\n    | Command | Mean [ms] | Min [ms] | Max [ms] | Relative |\n    |:---|---:|---:|---:|---:|\n    | `sleep 2` | 2005.0 ± 2.0 | 2002.0 | 2008.0 | 18.97 ± 0.29 |\n    | `sleep 0.1` | 105.7 ± 1.6 | 102.3 | 108.0 | 1.00 |\n    \"#);\n\n    insta::assert_snapshot!(get_output::<AsciidocExporter>(&timing_results, Some(Unit::MilliSecond), SortOrder::Command), @r#\"\n    [cols=\"<,>,>,>,>\"]\n    |===\n    | Command \n    | Mean [ms] \n    | Min [ms] \n    | Max [ms] \n    | Relative \n\n    | `sleep 2` \n    | 2005.0 ± 2.0 \n    | 2002.0 \n    | 2008.0 \n    | 18.97 ± 0.29 \n\n    | `sleep 0.1` \n    | 105.7 ± 1.6 \n    | 102.3 \n    | 108.0 \n    | 1.00 \n    |===\n    \"#);\n\n    insta::assert_snapshot!(get_output::<OrgmodeExporter>(&timing_results, Some(Unit::MilliSecond), SortOrder::Command), @r#\"\n    | Command  |  Mean [ms] |  Min [ms] |  Max [ms] |  Relative |\n    |--+--+--+--+--|\n    | =sleep 2=  |  2005.0 ± 2.0 |  2002.0 |  2008.0 |  18.97 ± 0.29 |\n    | =sleep 0.1=  |  105.7 ± 1.6 |  102.3 |  108.0 |  1.00 |\n    \"#);\n}\n\n/// The given time unit (s) is used to set the units for all entries.\n#[test]\nfn test_markup_export_manual_s() {\n    let results = [\n        BenchmarkResult {\n            command: String::from(\"sleep 2\"),\n            command_with_unused_parameters: String::from(\"sleep 2\"),\n            mean: 2.0050,\n            stddev: Some(0.0020),\n            median: 2.0050,\n            user: 0.0009,\n            system: 0.0012,\n            min: 2.0020,\n            max: 2.0080,\n            times: Some(vec![2.0, 2.0, 2.0]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: BTreeMap::new(),\n        },\n        BenchmarkResult {\n            command: String::from(\"sleep 0.1\"),\n            command_with_unused_parameters: String::from(\"sleep 0.1\"),\n            mean: 0.1057,\n            stddev: Some(0.0016),\n            median: 0.1057,\n            user: 0.0009,\n            system: 0.0011,\n            min: 0.1023,\n            max: 0.1080,\n            times: Some(vec![0.1, 0.1, 0.1]),\n            memory_usage_byte: None,\n            exit_codes: vec![Some(0), Some(0), Some(0)],\n            parameters: BTreeMap::new(),\n        },\n    ];\n\n    insta::assert_snapshot!(get_output::<MarkdownExporter>(&results, Some(Unit::Second), SortOrder::Command), @r#\"\n        | Command | Mean [s] | Min [s] | Max [s] | Relative |\n        |:---|---:|---:|---:|---:|\n        | `sleep 2` | 2.005 ± 0.002 | 2.002 | 2.008 | 18.97 ± 0.29 |\n        | `sleep 0.1` | 0.106 ± 0.002 | 0.102 | 0.108 | 1.00 |\n        \"#);\n\n    insta::assert_snapshot!(get_output::<MarkdownExporter>(&results, Some(Unit::Second), SortOrder::MeanTime), @r#\"\n        | Command | Mean [s] | Min [s] | Max [s] | Relative |\n        |:---|---:|---:|---:|---:|\n        | `sleep 0.1` | 0.106 ± 0.002 | 0.102 | 0.108 | 1.00 |\n        | `sleep 2` | 2.005 ± 0.002 | 2.002 | 2.008 | 18.97 ± 0.29 |\n        \"#);\n\n    insta::assert_snapshot!(get_output::<AsciidocExporter>(&results, Some(Unit::Second), SortOrder::Command), @r#\"\n    [cols=\"<,>,>,>,>\"]\n    |===\n    | Command \n    | Mean [s] \n    | Min [s] \n    | Max [s] \n    | Relative \n\n    | `sleep 2` \n    | 2.005 ± 0.002 \n    | 2.002 \n    | 2.008 \n    | 18.97 ± 0.29 \n\n    | `sleep 0.1` \n    | 0.106 ± 0.002 \n    | 0.102 \n    | 0.108 \n    | 1.00 \n    |===\n    \"#);\n}\n"
  },
  {
    "path": "src/main.rs",
    "content": "#![cfg_attr(\n    all(windows, feature = \"windows_process_extensions_main_thread_handle\"),\n    feature(windows_process_extensions_main_thread_handle)\n)]\n\nuse std::env;\n\nuse benchmark::scheduler::Scheduler;\nuse cli::get_cli_arguments;\nuse command::Commands;\nuse export::ExportManager;\nuse options::Options;\n\nuse anyhow::Result;\nuse colored::*;\n\npub mod benchmark;\npub mod cli;\npub mod command;\npub mod error;\npub mod export;\npub mod options;\npub mod outlier_detection;\npub mod output;\npub mod parameter;\npub mod timer;\npub mod util;\n\nfn run() -> Result<()> {\n    // Enabled ANSI colors on Windows 10\n    #[cfg(windows)]\n    colored::control::set_virtual_terminal(true).unwrap();\n\n    let cli_arguments = get_cli_arguments(env::args_os());\n    let mut options = Options::from_cli_arguments(&cli_arguments)?;\n    let commands = Commands::from_cli_arguments(&cli_arguments)?;\n    let export_manager = ExportManager::from_cli_arguments(\n        &cli_arguments,\n        options.time_unit,\n        options.sort_order_exports,\n    )?;\n\n    options.validate_against_command_list(&commands)?;\n\n    let mut scheduler = Scheduler::new(&commands, &options, &export_manager);\n    scheduler.run_benchmarks()?;\n    scheduler.print_relative_speed_comparison();\n    scheduler.final_export()?;\n\n    Ok(())\n}\n\nfn main() {\n    match run() {\n        Ok(_) => {}\n        Err(e) => {\n            eprintln!(\"{} {:#}\", \"Error:\".red(), e);\n            std::process::exit(1);\n        }\n    }\n}\n"
  },
  {
    "path": "src/options.rs",
    "content": "use std::fs::File;\nuse std::io::IsTerminal;\nuse std::path::PathBuf;\nuse std::process::{Command, Stdio};\nuse std::{cmp, env, fmt, io};\n\nuse anyhow::ensure;\nuse clap::ArgMatches;\n\nuse crate::command::Commands;\nuse crate::error::OptionsError;\nuse crate::util::units::{Second, Unit};\n\nuse anyhow::Result;\n\n#[cfg(not(windows))]\npub const DEFAULT_SHELL: &str = \"sh\";\n\n#[cfg(windows)]\npub const DEFAULT_SHELL: &str = \"cmd.exe\";\n\n/// Shell to use for executing benchmarked commands\n#[derive(Debug, PartialEq)]\npub enum Shell {\n    /// Default shell command\n    Default(&'static str),\n\n    /// Custom shell command specified via --shell\n    Custom(Vec<String>),\n}\n\nimpl Default for Shell {\n    fn default() -> Self {\n        Shell::Default(DEFAULT_SHELL)\n    }\n}\n\nimpl fmt::Display for Shell {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match self {\n            Shell::Default(cmd) => write!(f, \"{cmd}\"),\n            Shell::Custom(cmdline) => write!(f, \"{}\", shell_words::join(cmdline)),\n        }\n    }\n}\n\nimpl Shell {\n    /// Parse given string as shell command line\n    pub fn parse_from_str<'a>(s: &str) -> Result<Self, OptionsError<'a>> {\n        let v = shell_words::split(s).map_err(OptionsError::ShellParseError)?;\n        if v.is_empty() || v[0].is_empty() {\n            return Err(OptionsError::EmptyShell);\n        }\n        Ok(Shell::Custom(v))\n    }\n\n    pub fn command(&self) -> Command {\n        match self {\n            Shell::Default(cmd) => Command::new(cmd),\n            Shell::Custom(cmdline) => {\n                let mut c = Command::new(&cmdline[0]);\n                c.args(&cmdline[1..]);\n                c\n            }\n        }\n    }\n}\n\n/// Action to take when an executed command fails.\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum CmdFailureAction {\n    /// Exit with an error message\n    RaiseError,\n\n    /// Ignore all non-zero exit codes\n    IgnoreAllFailures,\n\n    /// Ignore specific exit codes\n    IgnoreSpecificFailures(Vec<i32>),\n}\n\n/// Output style type option\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum OutputStyleOption {\n    /// Do not output with colors or any special formatting\n    Basic,\n\n    /// Output with full color and formatting\n    Full,\n\n    /// Keep elements such as progress bar, but use no coloring\n    NoColor,\n\n    /// Keep coloring, but use no progress bar\n    Color,\n\n    /// Disable all the output\n    Disabled,\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum SortOrder {\n    Command,\n    MeanTime,\n}\n\n/// Bounds for the number of benchmark runs\npub struct RunBounds {\n    /// Minimum number of benchmark runs\n    pub min: u64,\n\n    /// Maximum number of benchmark runs\n    pub max: Option<u64>,\n}\n\nimpl Default for RunBounds {\n    fn default() -> Self {\n        RunBounds { min: 10, max: None }\n    }\n}\n\n#[derive(Debug, Default, Clone, PartialEq)]\npub enum CommandInputPolicy {\n    /// Read from the null device\n    #[default]\n    Null,\n\n    /// Read input from a file\n    File(PathBuf),\n}\n\nimpl CommandInputPolicy {\n    pub fn get_stdin(&self) -> io::Result<Stdio> {\n        let stream: Stdio = match self {\n            CommandInputPolicy::Null => Stdio::null(),\n\n            CommandInputPolicy::File(path) => {\n                let file: File = File::open(path)?;\n                Stdio::from(file)\n            }\n        };\n\n        Ok(stream)\n    }\n}\n\n/// How to handle the output of benchmarked commands\n#[derive(Debug, Clone, PartialEq, Eq, Default)]\npub enum CommandOutputPolicy {\n    /// Redirect output to the null device\n    #[default]\n    Null,\n\n    /// Feed output through a pipe before discarding it\n    Pipe,\n\n    /// Redirect output to a file\n    File(PathBuf),\n\n    /// Show command output on the terminal\n    Inherit,\n}\n\nimpl CommandOutputPolicy {\n    pub fn get_stdout_stderr(&self) -> io::Result<(Stdio, Stdio)> {\n        let streams = match self {\n            CommandOutputPolicy::Null => (Stdio::null(), Stdio::null()),\n\n            // Typically only stdout is performance-relevant, so just pipe that\n            CommandOutputPolicy::Pipe => (Stdio::piped(), Stdio::null()),\n\n            CommandOutputPolicy::File(path) => {\n                let file = File::create(path)?;\n                (file.into(), Stdio::null())\n            }\n\n            CommandOutputPolicy::Inherit => (Stdio::inherit(), Stdio::inherit()),\n        };\n\n        Ok(streams)\n    }\n}\n\n#[derive(Debug, PartialEq)]\npub enum ExecutorKind {\n    Raw,\n    Shell(Shell),\n    Mock(Option<String>),\n}\n\nimpl Default for ExecutorKind {\n    fn default() -> Self {\n        ExecutorKind::Shell(Shell::default())\n    }\n}\n\n/// The main settings for a hyperfine benchmark session\npub struct Options {\n    /// Upper and lower bound for the number of benchmark runs\n    pub run_bounds: RunBounds,\n\n    /// Number of warmup runs\n    pub warmup_count: u64,\n\n    /// Minimum benchmarking time\n    pub min_benchmarking_time: Second,\n\n    /// Whether or not to ignore non-zero exit codes\n    pub command_failure_action: CmdFailureAction,\n\n    // Command to use as a reference for relative speed comparison\n    pub reference_command: Option<String>,\n\n    // Name of the reference command\n    pub reference_name: Option<String>,\n\n    /// Command(s) to run before each timing run\n    pub preparation_command: Option<Vec<String>>,\n\n    /// Command(s) to run after each timing run\n    pub conclusion_command: Option<Vec<String>>,\n\n    /// Command to run before each *batch* of timing runs, i.e. before each individual benchmark\n    pub setup_command: Option<String>,\n\n    /// Command to run after each *batch* of timing runs, i.e. after each individual benchmark\n    pub cleanup_command: Option<String>,\n\n    /// What color mode to use for the terminal output\n    pub output_style: OutputStyleOption,\n\n    /// How to order benchmarks in the relative speed comparison\n    pub sort_order_speed_comparison: SortOrder,\n\n    /// How to order benchmarks in the markup format exports\n    pub sort_order_exports: SortOrder,\n\n    /// Determines how we run commands\n    pub executor_kind: ExecutorKind,\n\n    /// Where input to the benchmarked command comes from\n    pub command_input_policy: CommandInputPolicy,\n\n    /// What to do with the output of the benchmarked commands\n    pub command_output_policies: Vec<CommandOutputPolicy>,\n\n    /// Which time unit to use when displaying results\n    pub time_unit: Option<Unit>,\n}\n\nimpl Default for Options {\n    fn default() -> Options {\n        Options {\n            run_bounds: RunBounds::default(),\n            warmup_count: 0,\n            min_benchmarking_time: 3.0,\n            command_failure_action: CmdFailureAction::RaiseError,\n            reference_command: None,\n            reference_name: None,\n            preparation_command: None,\n            conclusion_command: None,\n            setup_command: None,\n            cleanup_command: None,\n            output_style: OutputStyleOption::Full,\n            sort_order_speed_comparison: SortOrder::MeanTime,\n            sort_order_exports: SortOrder::Command,\n            executor_kind: ExecutorKind::default(),\n            command_output_policies: vec![CommandOutputPolicy::Null],\n            time_unit: None,\n            command_input_policy: CommandInputPolicy::Null,\n        }\n    }\n}\n\nimpl Options {\n    pub fn from_cli_arguments<'a>(matches: &ArgMatches) -> Result<Self, OptionsError<'a>> {\n        let mut options = Self::default();\n        let param_to_u64 = |param| {\n            matches\n                .get_one::<String>(param)\n                .map(|n| {\n                    n.parse::<u64>()\n                        .map_err(|e| OptionsError::IntParsingError(param, e))\n                })\n                .transpose()\n        };\n\n        options.warmup_count = param_to_u64(\"warmup\")?.unwrap_or(options.warmup_count);\n\n        let mut min_runs = param_to_u64(\"min-runs\")?;\n        let mut max_runs = param_to_u64(\"max-runs\")?;\n\n        if let Some(runs) = param_to_u64(\"runs\")? {\n            min_runs = Some(runs);\n            max_runs = Some(runs);\n        }\n\n        match (min_runs, max_runs) {\n            (Some(min), None) => {\n                options.run_bounds.min = min;\n            }\n            (None, Some(max)) => {\n                // Since the minimum was not explicit we lower it if max is below the default min.\n                options.run_bounds.min = cmp::min(options.run_bounds.min, max);\n                options.run_bounds.max = Some(max);\n            }\n            (Some(min), Some(max)) if min > max => {\n                return Err(OptionsError::EmptyRunsRange);\n            }\n            (Some(min), Some(max)) => {\n                options.run_bounds.min = min;\n                options.run_bounds.max = Some(max);\n            }\n            (None, None) => {}\n        };\n\n        options.setup_command = matches.get_one::<String>(\"setup\").map(String::from);\n\n        options.reference_command = matches.get_one::<String>(\"reference\").map(String::from);\n        options.reference_name = matches\n            .get_one::<String>(\"reference-name\")\n            .map(String::from);\n\n        options.preparation_command = matches\n            .get_many::<String>(\"prepare\")\n            .map(|values| values.map(String::from).collect::<Vec<String>>());\n\n        options.conclusion_command = matches\n            .get_many::<String>(\"conclude\")\n            .map(|values| values.map(String::from).collect::<Vec<String>>());\n\n        options.cleanup_command = matches.get_one::<String>(\"cleanup\").map(String::from);\n\n        options.command_output_policies = if matches.get_flag(\"show-output\") {\n            vec![CommandOutputPolicy::Inherit]\n        } else if let Some(output_values) = matches.get_many::<String>(\"output\") {\n            let mut policies = vec![];\n            for value in output_values {\n                let policy = match value.as_str() {\n                    \"null\" => CommandOutputPolicy::Null,\n                    \"pipe\" => CommandOutputPolicy::Pipe,\n                    \"inherit\" => CommandOutputPolicy::Inherit,\n                    arg => {\n                        let path = PathBuf::from(arg);\n                        if path.components().count() <= 1 {\n                            return Err(OptionsError::UnknownOutputPolicy(arg.to_string()));\n                        }\n                        CommandOutputPolicy::File(path)\n                    }\n                };\n                policies.push(policy);\n            }\n            policies\n        } else {\n            vec![CommandOutputPolicy::Null]\n        };\n\n        options.output_style = match matches.get_one::<String>(\"style\").map(|s| s.as_str()) {\n            Some(\"full\") => OutputStyleOption::Full,\n            Some(\"basic\") => OutputStyleOption::Basic,\n            Some(\"nocolor\") => OutputStyleOption::NoColor,\n            Some(\"color\") => OutputStyleOption::Color,\n            Some(\"none\") => OutputStyleOption::Disabled,\n            _ => {\n                if options\n                    .command_output_policies\n                    .contains(&CommandOutputPolicy::Inherit)\n                    || !io::stdout().is_terminal()\n                {\n                    OutputStyleOption::Basic\n                } else if env::var_os(\"TERM\")\n                    .map(|t| t == \"unknown\" || t == \"dumb\")\n                    .unwrap_or(!cfg!(target_os = \"windows\"))\n                    || env::var_os(\"NO_COLOR\")\n                        .map(|t| !t.is_empty())\n                        .unwrap_or(false)\n                {\n                    OutputStyleOption::NoColor\n                } else {\n                    OutputStyleOption::Full\n                }\n            }\n        };\n\n        match options.output_style {\n            OutputStyleOption::Basic | OutputStyleOption::NoColor => {\n                colored::control::set_override(false)\n            }\n            OutputStyleOption::Full | OutputStyleOption::Color => {\n                colored::control::set_override(true)\n            }\n            OutputStyleOption::Disabled => {}\n        };\n\n        (\n            options.sort_order_speed_comparison,\n            options.sort_order_exports,\n        ) = match matches.get_one::<String>(\"sort\").map(|s| s.as_str()) {\n            None | Some(\"auto\") => (SortOrder::MeanTime, SortOrder::Command),\n            Some(\"command\") => (SortOrder::Command, SortOrder::Command),\n            Some(\"mean-time\") => (SortOrder::MeanTime, SortOrder::MeanTime),\n            Some(_) => unreachable!(\"Unknown sort order\"),\n        };\n\n        options.executor_kind = if matches.get_flag(\"no-shell\") {\n            ExecutorKind::Raw\n        } else {\n            match (\n                matches.get_flag(\"debug-mode\"),\n                matches.get_one::<String>(\"shell\"),\n            ) {\n                (false, Some(shell)) if shell == \"default\" => ExecutorKind::Shell(Shell::default()),\n                (false, Some(shell)) if shell == \"none\" => ExecutorKind::Raw,\n                (false, Some(shell)) => ExecutorKind::Shell(Shell::parse_from_str(shell)?),\n                (false, None) => ExecutorKind::Shell(Shell::default()),\n                (true, Some(shell)) => ExecutorKind::Mock(Some(shell.into())),\n                (true, None) => ExecutorKind::Mock(None),\n            }\n        };\n\n        if let Some(mode) = matches.get_one::<String>(\"ignore-failure\") {\n            options.command_failure_action = match mode.as_str() {\n                \"all-non-zero\" | \"\" => CmdFailureAction::IgnoreAllFailures,\n                codes => {\n                    let exit_codes: Result<Vec<i32>, _> = codes\n                        .split(',')\n                        .map(|s| {\n                            s.trim()\n                                .parse::<i32>()\n                                .map_err(|e| OptionsError::IntParsingError(\"ignore-failure\", e))\n                        })\n                        .collect();\n                    CmdFailureAction::IgnoreSpecificFailures(exit_codes?)\n                }\n            };\n        }\n\n        options.time_unit = match matches.get_one::<String>(\"time-unit\").map(|s| s.as_str()) {\n            Some(\"microsecond\") => Some(Unit::MicroSecond),\n            Some(\"millisecond\") => Some(Unit::MilliSecond),\n            Some(\"second\") => Some(Unit::Second),\n            _ => None,\n        };\n\n        if let Some(time) = matches.get_one::<String>(\"min-benchmarking-time\") {\n            options.min_benchmarking_time = time\n                .parse::<f64>()\n                .map_err(|e| OptionsError::FloatParsingError(\"min-benchmarking-time\", e))?;\n        }\n\n        options.command_input_policy = if let Some(path_str) = matches.get_one::<String>(\"input\") {\n            if path_str == \"null\" {\n                CommandInputPolicy::Null\n            } else {\n                let path = PathBuf::from(path_str);\n                if !path.exists() {\n                    return Err(OptionsError::StdinDataFileDoesNotExist(\n                        path_str.to_string(),\n                    ));\n                }\n                CommandInputPolicy::File(path)\n            }\n        } else {\n            CommandInputPolicy::Null\n        };\n\n        Ok(options)\n    }\n\n    pub fn validate_against_command_list(&mut self, commands: &Commands) -> Result<()> {\n        let has_reference_command = self.reference_command.is_some();\n        let num_commands = commands.num_commands(has_reference_command);\n\n        if let Some(preparation_command) = &self.preparation_command {\n            ensure!(\n                preparation_command.len() <= 1 || num_commands == preparation_command.len(),\n                \"The '--prepare' option has to be provided just once or N times, where N={num_commands} is the \\\n                 number of benchmark commands (including a potential reference).\"\n            );\n        }\n\n        if let Some(conclusion_command) = &self.conclusion_command {\n            ensure!(\n                conclusion_command.len() <= 1 || num_commands == conclusion_command.len(),\n                \"The '--conclude' option has to be provided just once or N times, where N={num_commands} is the \\\n                 number of benchmark commands (including a potential reference).\"\n            );\n        }\n\n        if self.command_output_policies.len() == 1 {\n            self.command_output_policies =\n                vec![self.command_output_policies[0].clone(); num_commands];\n        } else {\n            ensure!(\n                self.command_output_policies.len() == num_commands,\n                \"The '--output' option has to be provided just once or N times, where N={num_commands} is the \\\n                 number of benchmark commands (including a potential reference).\"\n            );\n        }\n\n        Ok(())\n    }\n}\n\n#[test]\nfn test_default_shell() {\n    let shell = Shell::default();\n\n    let s = format!(\"{shell}\");\n    assert_eq!(&s, DEFAULT_SHELL);\n\n    let cmd = shell.command();\n    assert_eq!(cmd.get_program(), DEFAULT_SHELL);\n}\n\n#[test]\nfn test_can_parse_shell_command_line_from_str() {\n    let shell = Shell::parse_from_str(\"shell -x 'aaa bbb'\").unwrap();\n\n    let s = format!(\"{shell}\");\n    assert_eq!(&s, \"shell -x 'aaa bbb'\");\n\n    let cmd = shell.command();\n    assert_eq!(cmd.get_program().to_string_lossy(), \"shell\");\n    assert_eq!(\n        cmd.get_args()\n            .map(|a| a.to_string_lossy())\n            .collect::<Vec<_>>(),\n        vec![\"-x\", \"aaa bbb\"]\n    );\n\n    // Error cases\n    assert!(matches!(\n        Shell::parse_from_str(\"shell 'foo\").unwrap_err(),\n        OptionsError::ShellParseError(_)\n    ));\n\n    assert!(matches!(\n        Shell::parse_from_str(\"\").unwrap_err(),\n        OptionsError::EmptyShell\n    ));\n\n    assert!(matches!(\n        Shell::parse_from_str(\"''\").unwrap_err(),\n        OptionsError::EmptyShell\n    ));\n}\n"
  },
  {
    "path": "src/outlier_detection.rs",
    "content": "//! A module for statistical outlier detection.\n//!\n//! References:\n//! - Boris Iglewicz and David Hoaglin (1993), \"Volume 16: How to Detect and Handle Outliers\",\n//!   The ASQC Basic References in Quality Control: Statistical Techniques, Edward F. Mykytka,\n//!   Ph.D., Editor.\n\nuse statistical::median;\n\n/// Minimum modified Z-score for a datapoint to be an outlier. Here, 1.4826 is a factor that\n/// converts the MAD to an estimator for the standard deviation. The second factor is the number\n/// of standard deviations.\npub const OUTLIER_THRESHOLD: f64 = 1.4826 * 10.0;\n\n/// Compute modifized Z-scores for a given sample. A (unmodified) Z-score is defined by\n/// `(x_i - x_mean)/x_stddev` whereas the modified Z-score is defined by `(x_i - x_median)/MAD`\n/// where MAD is the median absolute deviation.\n///\n/// References:\n/// - <https://en.wikipedia.org/wiki/Median_absolute_deviation>\npub fn modified_zscores(xs: &[f64]) -> Vec<f64> {\n    assert!(!xs.is_empty());\n\n    // Compute sample median:\n    let x_median = median(xs);\n\n    // Compute the absolute deviations from the median:\n    let deviations: Vec<f64> = xs.iter().map(|x| (x - x_median).abs()).collect();\n\n    // Compute median absolute deviation:\n    let mad = median(&deviations);\n\n    // Handle MAD == 0 case\n    let mad = if mad > 0.0 { mad } else { f64::EPSILON };\n\n    // Compute modified Z-scores (x_i - x_median) / MAD\n    xs.iter().map(|&x| (x - x_median) / mad).collect()\n}\n\n/// Return the number of outliers in a given sample. Outliers are defined as data points with a\n/// modified Z-score that is larger than `OUTLIER_THRESHOLD`.\n#[cfg(test)]\npub fn num_outliers(xs: &[f64]) -> usize {\n    if xs.is_empty() {\n        return 0;\n    }\n\n    let scores = modified_zscores(xs);\n    scores\n        .iter()\n        .filter(|&&s| s.abs() > OUTLIER_THRESHOLD)\n        .count()\n}\n\n#[test]\nfn test_detect_outliers() {\n    // Should not detect outliers in small samples\n    assert_eq!(0, num_outliers(&[]));\n    assert_eq!(0, num_outliers(&[50.0]));\n    assert_eq!(0, num_outliers(&[1000.0, 0.0]));\n\n    // Should not detect outliers in low-variance samples\n    let xs = [-0.2, 0.0, 0.2];\n    assert_eq!(0, num_outliers(&xs));\n\n    // Should detect a single outlier\n    let xs = [-0.2, 0.0, 0.2, 4.0];\n    assert_eq!(1, num_outliers(&xs));\n\n    // Should detect a single outlier\n    let xs = [0.5, 0.30, 0.29, 0.31, 0.30];\n    assert_eq!(1, num_outliers(&xs));\n\n    // Should detect no outliers in sample drawn from normal distribution\n    let xs = [\n        2.33269488,\n        1.42195907,\n        -0.57527698,\n        -0.31293437,\n        2.2948158,\n        0.75813273,\n        -1.0712388,\n        -0.96394741,\n        -1.15897446,\n        1.10976285,\n    ];\n    assert_eq!(0, num_outliers(&xs));\n\n    // Should detect two outliers that were manually added\n    let xs = [\n        2.33269488,\n        1.42195907,\n        -0.57527698,\n        -0.31293437,\n        2.2948158,\n        0.75813273,\n        -1.0712388,\n        -0.96394741,\n        -1.15897446,\n        1.10976285,\n        20.0,\n        -500.0,\n    ];\n    assert_eq!(2, num_outliers(&xs));\n}\n\n#[test]\nfn test_detect_outliers_if_mad_becomes_0() {\n    // See https://stats.stackexchange.com/q/339932\n    let xs = [10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 100.0];\n    assert_eq!(1, num_outliers(&xs));\n\n    let xs = [10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 100.0, 100.0];\n    assert_eq!(2, num_outliers(&xs));\n}\n"
  },
  {
    "path": "src/output/format.rs",
    "content": "use crate::util::units::{Second, Unit};\n\n/// Format the given duration as a string. The output-unit can be enforced by setting `unit` to\n/// `Some(target_unit)`. If `unit` is `None`, it will be determined automatically.\npub fn format_duration(duration: Second, unit: Option<Unit>) -> String {\n    let (duration_fmt, _) = format_duration_unit(duration, unit);\n    duration_fmt\n}\n\n/// Like `format_duration`, but returns the target unit as well.\npub fn format_duration_unit(duration: Second, unit: Option<Unit>) -> (String, Unit) {\n    let (out_str, out_unit) = format_duration_value(duration, unit);\n\n    (format!(\"{} {}\", out_str, out_unit.short_name()), out_unit)\n}\n\n/// Like `format_duration`, but returns the target unit as well.\npub fn format_duration_value(duration: Second, unit: Option<Unit>) -> (String, Unit) {\n    if (duration < 0.001 && unit.is_none()) || unit == Some(Unit::MicroSecond) {\n        (Unit::MicroSecond.format(duration), Unit::MicroSecond)\n    } else if (duration < 1.0 && unit.is_none()) || unit == Some(Unit::MilliSecond) {\n        (Unit::MilliSecond.format(duration), Unit::MilliSecond)\n    } else {\n        (Unit::Second.format(duration), Unit::Second)\n    }\n}\n\n#[test]\nfn test_format_duration_unit_basic() {\n    let (out_str, out_unit) = format_duration_unit(1.3, None);\n\n    assert_eq!(\"1.300 s\", out_str);\n    assert_eq!(Unit::Second, out_unit);\n\n    let (out_str, out_unit) = format_duration_unit(1.0, None);\n\n    assert_eq!(\"1.000 s\", out_str);\n    assert_eq!(Unit::Second, out_unit);\n\n    let (out_str, out_unit) = format_duration_unit(0.999, None);\n\n    assert_eq!(\"999.0 ms\", out_str);\n    assert_eq!(Unit::MilliSecond, out_unit);\n\n    let (out_str, out_unit) = format_duration_unit(0.0005, None);\n\n    assert_eq!(\"500.0 µs\", out_str);\n    assert_eq!(Unit::MicroSecond, out_unit);\n\n    let (out_str, out_unit) = format_duration_unit(0.0, None);\n\n    assert_eq!(\"0.0 µs\", out_str);\n    assert_eq!(Unit::MicroSecond, out_unit);\n\n    let (out_str, out_unit) = format_duration_unit(1000.0, None);\n\n    assert_eq!(\"1000.000 s\", out_str);\n    assert_eq!(Unit::Second, out_unit);\n}\n\n#[test]\nfn test_format_duration_unit_with_unit() {\n    let (out_str, out_unit) = format_duration_unit(1.3, Some(Unit::Second));\n\n    assert_eq!(\"1.300 s\", out_str);\n    assert_eq!(Unit::Second, out_unit);\n\n    let (out_str, out_unit) = format_duration_unit(1.3, Some(Unit::MilliSecond));\n\n    assert_eq!(\"1300.0 ms\", out_str);\n    assert_eq!(Unit::MilliSecond, out_unit);\n\n    let (out_str, out_unit) = format_duration_unit(1.3, Some(Unit::MicroSecond));\n\n    assert_eq!(\"1300000.0 µs\", out_str);\n    assert_eq!(Unit::MicroSecond, out_unit);\n}\n"
  },
  {
    "path": "src/output/mod.rs",
    "content": "pub mod format;\npub mod progress_bar;\npub mod warnings;\n"
  },
  {
    "path": "src/output/progress_bar.rs",
    "content": "use indicatif::{ProgressBar, ProgressStyle};\nuse std::time::Duration;\n\nuse crate::options::OutputStyleOption;\n\n#[cfg(not(windows))]\nconst TICK_SETTINGS: (&str, u64) = (\"⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ \", 80);\n\n#[cfg(windows)]\nconst TICK_SETTINGS: (&str, u64) = (r\"+-x| \", 200);\n\n/// Return a pre-configured progress bar\npub fn get_progress_bar(length: u64, msg: &str, option: OutputStyleOption) -> ProgressBar {\n    let progressbar_style = match option {\n        OutputStyleOption::Basic | OutputStyleOption::Color => ProgressStyle::default_bar(),\n        _ => ProgressStyle::default_spinner()\n            .tick_chars(TICK_SETTINGS.0)\n            .template(\" {spinner} {msg:<30} {wide_bar} ETA {eta_precise} \")\n            .expect(\"no template error\"),\n    };\n\n    let progress_bar = match option {\n        OutputStyleOption::Basic | OutputStyleOption::Color => ProgressBar::hidden(),\n        _ => ProgressBar::new(length),\n    };\n    progress_bar.set_style(progressbar_style);\n    progress_bar.enable_steady_tick(Duration::from_millis(TICK_SETTINGS.1));\n    progress_bar.set_message(msg.to_owned());\n\n    progress_bar\n}\n"
  },
  {
    "path": "src/output/warnings.rs",
    "content": "use std::fmt;\n\nuse crate::benchmark::MIN_EXECUTION_TIME;\nuse crate::output::format::format_duration;\nuse crate::util::units::Second;\n\npub struct OutlierWarningOptions {\n    pub warmup_in_use: bool,\n    pub prepare_in_use: bool,\n}\n\n/// A list of all possible warnings\npub enum Warnings {\n    FastExecutionTime,\n    NonZeroExitCode,\n    SlowInitialRun(Second, OutlierWarningOptions),\n    OutliersDetected(OutlierWarningOptions),\n}\n\nimpl fmt::Display for Warnings {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match *self {\n            Warnings::FastExecutionTime => write!(\n                f,\n                \"Command took less than {:.0} ms to complete. Note that the results might be \\\n                inaccurate because hyperfine can not calibrate the shell startup time much \\\n                more precise than this limit. You can try to use the `-N`/`--shell=none` \\\n                option to disable the shell completely.\",\n                MIN_EXECUTION_TIME * 1e3\n            ),\n            Warnings::NonZeroExitCode => write!(f, \"Ignoring non-zero exit code.\"),\n            Warnings::SlowInitialRun(time_first_run, ref options) => write!(\n                f,\n                \"The first benchmarking run for this command was significantly slower than the \\\n                 rest ({time}). This could be caused by (filesystem) caches that were not filled until \\\n                 after the first run. {hints}\",\n                time=format_duration(time_first_run, None),\n                hints=match (options.warmup_in_use, options.prepare_in_use) {\n                    (true, true) => \"You are already using both the '--warmup' option as well \\\n                    as the '--prepare' option. Consider re-running the benchmark on a quiet system. \\\n                    Maybe it was a random outlier. Alternatively, consider increasing the warmup \\\n                    count.\",\n                    (true, false) => \"You are already using the '--warmup' option which helps \\\n                    to fill these caches before the actual benchmark. You can either try to \\\n                    increase the warmup count further or re-run this benchmark on a quiet system \\\n                    in case it was a random outlier. Alternatively, consider using the '--prepare' \\\n                    option to clear the caches before each timing run.\",\n                    (false, true) => \"You are already using the '--prepare' option which can \\\n                    be used to clear caches. If you did not use a cache-clearing command with \\\n                    '--prepare', you can either try that or consider using the '--warmup' option \\\n                    to fill those caches before the actual benchmark.\",\n                    (false, false) => \"You should consider using the '--warmup' option to fill \\\n                    those caches before the actual benchmark. Alternatively, use the '--prepare' \\\n                    option to clear the caches before each timing run.\"\n                }\n            ),\n            Warnings::OutliersDetected(ref options) => write!(\n                f,\n                \"Statistical outliers were detected. Consider re-running this benchmark on a quiet \\\n                 system without any interferences from other programs.{hint}\",\n                hint=if options.warmup_in_use && options.prepare_in_use {\n                    \"\"\n                } else {\n                    \" It might help to use the '--warmup' or '--prepare' options.\"\n                }\n            ),\n        }\n    }\n}\n"
  },
  {
    "path": "src/parameter/mod.rs",
    "content": "use crate::util::number::Number;\nuse std::fmt::Display;\n\npub mod range_step;\npub mod tokenize;\n\n#[derive(Debug, Clone, PartialEq, Eq)]\npub enum ParameterValue {\n    Text(String),\n    Numeric(Number),\n}\n\nimpl Display for ParameterValue {\n    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {\n        let str = match self {\n            ParameterValue::Text(ref value) => value.clone(),\n            ParameterValue::Numeric(value) => value.to_string(),\n        };\n        write!(f, \"{str}\")\n    }\n}\n\npub type ParameterNameAndValue<'a> = (&'a str, ParameterValue);\n"
  },
  {
    "path": "src/parameter/range_step.rs",
    "content": "use std::convert::TryInto;\nuse std::ops::{Add, AddAssign, Div, Sub};\n\nuse crate::error::ParameterScanError;\nuse crate::util::number::Number;\n\npub trait Numeric:\n    Add<Output = Self>\n    + Sub<Output = Self>\n    + Div<Output = Self>\n    + AddAssign\n    + PartialOrd\n    + Copy\n    + Clone\n    + From<i32>\n    + Into<Number>\n{\n}\nimpl<\n        T: Add<Output = Self>\n            + Sub<Output = Self>\n            + Div<Output = Self>\n            + AddAssign\n            + PartialOrd\n            + Copy\n            + Clone\n            + From<i32>\n            + Into<Number>,\n    > Numeric for T\n{\n}\n\n#[derive(Debug)]\npub struct RangeStep<T> {\n    state: T,\n    end: T,\n    step: T,\n}\n\nimpl<T: Numeric> RangeStep<T> {\n    pub fn new(start: T, end: T, step: T) -> Result<Self, ParameterScanError> {\n        if end < start {\n            return Err(ParameterScanError::EmptyRange);\n        }\n\n        if step == T::from(0) {\n            return Err(ParameterScanError::ZeroStep);\n        }\n\n        const MAX_PARAMETERS: usize = 100_000;\n        match range_step_size_hint(start, end, step) {\n            (_, Some(size)) if size <= MAX_PARAMETERS => Ok(Self {\n                state: start,\n                end,\n                step,\n            }),\n            _ => Err(ParameterScanError::TooLarge),\n        }\n    }\n}\n\nimpl<T: Numeric> Iterator for RangeStep<T> {\n    type Item = T;\n\n    fn next(&mut self) -> Option<Self::Item> {\n        if self.state > self.end {\n            return None;\n        }\n        let return_val = self.state;\n        self.state += self.step;\n\n        Some(return_val)\n    }\n\n    fn size_hint(&self) -> (usize, Option<usize>) {\n        range_step_size_hint(self.state, self.end, self.step)\n    }\n}\n\nfn range_step_size_hint<T: Numeric>(start: T, end: T, step: T) -> (usize, Option<usize>) {\n    if step == T::from(0) {\n        return (usize::MAX, None);\n    }\n\n    let steps = (end - start + T::from(1)) / step;\n    steps\n        .into()\n        .try_into()\n        .map_or((usize::MAX, None), |u| (u, Some(u)))\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    use rust_decimal::Decimal;\n    use std::str::FromStr;\n\n    #[test]\n    fn test_integer_range() {\n        let param_range: Vec<i32> = RangeStep::new(0, 10, 3).unwrap().collect();\n\n        assert_eq!(param_range.len(), 4);\n        assert_eq!(param_range[0], 0);\n        assert_eq!(param_range[3], 9);\n    }\n\n    #[test]\n    fn test_decimal_range() {\n        let param_min = Decimal::from(0);\n        let param_max = Decimal::from(1);\n        let step = Decimal::from_str(\"0.1\").unwrap();\n\n        let param_range: Vec<Decimal> = RangeStep::new(param_min, param_max, step)\n            .unwrap()\n            .collect();\n\n        assert_eq!(param_range.len(), 11);\n        assert_eq!(param_range[0], Decimal::from(0));\n        assert_eq!(param_range[10], Decimal::from(1));\n    }\n\n    #[test]\n    fn test_range_step_validate() {\n        let result = RangeStep::new(0, 10, 3);\n        assert!(result.is_ok());\n\n        let result = RangeStep::new(\n            Decimal::from(0),\n            Decimal::from(1),\n            Decimal::from_str(\"0.1\").unwrap(),\n        );\n        assert!(result.is_ok());\n\n        let result = RangeStep::new(11, 10, 1);\n        assert_eq!(format!(\"{}\", result.unwrap_err()), \"Empty parameter range\");\n\n        let result = RangeStep::new(0, 10, 0);\n        assert_eq!(\n            format!(\"{}\", result.unwrap_err()),\n            \"Zero is not a valid parameter step\"\n        );\n\n        let result = RangeStep::new(0, 100_001, 1);\n        assert_eq!(\n            format!(\"{}\", result.unwrap_err()),\n            \"Parameter range is too large\"\n        );\n    }\n}\n"
  },
  {
    "path": "src/parameter/tokenize.rs",
    "content": "pub fn tokenize(values: &str) -> Vec<String> {\n    let mut tokens = vec![];\n    let mut buf = String::new();\n\n    let mut iter = values.chars();\n    while let Some(c) = iter.next() {\n        match c {\n            '\\\\' => match iter.next() {\n                Some(c2 @ ',') | Some(c2 @ '\\\\') => {\n                    buf.push(c2);\n                }\n                Some(c2) => {\n                    buf.push('\\\\');\n                    buf.push(c2);\n                }\n                None => buf.push('\\\\'),\n            },\n            ',' => {\n                tokens.push(buf);\n                buf = String::new();\n            }\n            _ => {\n                buf.push(c);\n            }\n        };\n    }\n\n    tokens.push(buf);\n\n    tokens\n}\n\n#[test]\nfn test_tokenize_single_value() {\n    assert_eq!(tokenize(r\"\"), vec![\"\"]);\n    assert_eq!(tokenize(r\"foo\"), vec![\"foo\"]);\n    assert_eq!(tokenize(r\" \"), vec![\" \"]);\n    assert_eq!(tokenize(r\"hello\\, world!\"), vec![\"hello, world!\"]);\n    assert_eq!(tokenize(r\"\\,\"), vec![\",\"]);\n    assert_eq!(tokenize(r\"\\,\\,\\,\"), vec![\",,,\"]);\n    assert_eq!(tokenize(r\"\\n\"), vec![r\"\\n\"]);\n    assert_eq!(tokenize(r\"\\\\\"), vec![r\"\\\"]);\n    assert_eq!(tokenize(r\"\\\\\\,\"), vec![r\"\\,\"]);\n}\n\n#[test]\nfn test_tokenize_multiple_values() {\n    assert_eq!(tokenize(r\"foo,bar,baz\"), vec![\"foo\", \"bar\", \"baz\"]);\n    assert_eq!(tokenize(r\"hello world,foo\"), vec![\"hello world\", \"foo\"]);\n\n    assert_eq!(tokenize(r\"hello\\,world!,baz\"), vec![\"hello,world!\", \"baz\"]);\n}\n\n#[test]\nfn test_tokenize_empty_values() {\n    assert_eq!(tokenize(r\"foo,,bar\"), vec![\"foo\", \"\", \"bar\"]);\n    assert_eq!(tokenize(r\",bar\"), vec![\"\", \"bar\"]);\n    assert_eq!(tokenize(r\"bar,\"), vec![\"bar\", \"\"]);\n    assert_eq!(tokenize(r\",,\"), vec![\"\", \"\", \"\"]);\n}\n"
  },
  {
    "path": "src/timer/mod.rs",
    "content": "mod wall_clock_timer;\n\n#[cfg(windows)]\nmod windows_timer;\n\n#[cfg(not(windows))]\nmod unix_timer;\n\n#[cfg(target_os = \"linux\")]\nuse nix::fcntl::{splice, SpliceFFlags};\n#[cfg(target_os = \"linux\")]\nuse std::fs::File;\n#[cfg(target_os = \"linux\")]\nuse std::os::fd::AsFd;\n\n#[cfg(target_os = \"windows\")]\nuse windows_sys::Win32::System::Threading::CREATE_SUSPENDED;\n\nuse crate::util::units::Second;\nuse wall_clock_timer::WallClockTimer;\n\nuse std::io::Read;\nuse std::process::{ChildStdout, Command, ExitStatus};\n\nuse anyhow::Result;\n\n#[cfg(not(windows))]\n#[derive(Debug, Copy, Clone)]\nstruct CPUTimes {\n    /// Total amount of time spent executing in user mode\n    pub user_usec: i64,\n\n    /// Total amount of time spent executing in kernel mode\n    pub system_usec: i64,\n\n    /// Maximum amount of memory used by the process, in bytes\n    pub memory_usage_byte: u64,\n}\n\n/// Used to indicate the result of running a command\n#[derive(Debug, Copy, Clone)]\npub struct TimerResult {\n    pub time_real: Second,\n    pub time_user: Second,\n    pub time_system: Second,\n    pub memory_usage_byte: u64,\n    /// The exit status of the process\n    pub status: ExitStatus,\n}\n\n/// Discard the output of a child process.\nfn discard(output: ChildStdout) {\n    const CHUNK_SIZE: usize = 64 << 10;\n\n    #[cfg(target_os = \"linux\")]\n    {\n        if let Ok(file) = File::create(\"/dev/null\") {\n            while let Ok(bytes) = splice(\n                output.as_fd(),\n                None,\n                file.as_fd(),\n                None,\n                CHUNK_SIZE,\n                SpliceFFlags::empty(),\n            ) {\n                if bytes == 0 {\n                    break;\n                }\n            }\n        }\n    }\n\n    let mut output = output;\n    let mut buf = [0; CHUNK_SIZE];\n    while let Ok(bytes) = output.read(&mut buf) {\n        if bytes == 0 {\n            break;\n        }\n    }\n}\n\n/// Execute the given command and return a timing summary\npub fn execute_and_measure(mut command: Command) -> Result<TimerResult> {\n    #[cfg(not(windows))]\n    let cpu_timer = self::unix_timer::CPUTimer::start();\n\n    #[cfg(windows)]\n    {\n        use std::os::windows::process::CommandExt;\n\n        // Create the process in a suspended state so that we don't miss any cpu time between process creation and `CPUTimer` start.\n        command.creation_flags(CREATE_SUSPENDED);\n    }\n\n    let wallclock_timer = WallClockTimer::start();\n    let mut child = command.spawn()?;\n\n    #[cfg(windows)]\n    let cpu_timer = {\n        // SAFETY: We created a suspended process\n        unsafe { self::windows_timer::CPUTimer::start_suspended_process(&child) }\n    };\n\n    if let Some(output) = child.stdout.take() {\n        // Handle CommandOutputPolicy::Pipe\n        discard(output);\n    }\n\n    let status = child.wait()?;\n\n    let time_real = wallclock_timer.stop();\n    let (time_user, time_system, memory_usage_byte) = cpu_timer.stop();\n\n    Ok(TimerResult {\n        time_real,\n        time_user,\n        time_system,\n        memory_usage_byte,\n        status,\n    })\n}\n"
  },
  {
    "path": "src/timer/unix_timer.rs",
    "content": "#![cfg(not(windows))]\n\nuse std::convert::TryFrom;\nuse std::mem;\n\nuse crate::timer::CPUTimes;\nuse crate::util::units::Second;\n\n#[derive(Debug, Copy, Clone)]\npub struct CPUInterval {\n    /// Total amount of time spent executing in user mode\n    pub user: Second,\n\n    /// Total amount of time spent executing in kernel mode\n    pub system: Second,\n}\n\npub struct CPUTimer {\n    start_cpu: CPUTimes,\n}\n\nimpl CPUTimer {\n    pub fn start() -> Self {\n        CPUTimer {\n            start_cpu: get_cpu_times(),\n        }\n    }\n\n    pub fn stop(&self) -> (Second, Second, u64) {\n        let end_cpu = get_cpu_times();\n        let cpu_interval = cpu_time_interval(&self.start_cpu, &end_cpu);\n        (\n            cpu_interval.user,\n            cpu_interval.system,\n            end_cpu.memory_usage_byte,\n        )\n    }\n}\n\n/// Read CPU execution times ('user' and 'system')\nfn get_cpu_times() -> CPUTimes {\n    use libc::{getrusage, rusage, RUSAGE_CHILDREN};\n\n    let result: rusage = unsafe {\n        let mut buf = mem::zeroed();\n        let success = getrusage(RUSAGE_CHILDREN, &mut buf);\n        assert_eq!(0, success);\n        buf\n    };\n\n    const MICROSEC_PER_SEC: i64 = 1000 * 1000;\n\n    // Linux and *BSD return the value in KibiBytes, Darwin flavors in bytes\n    let max_rss_byte = if cfg!(target_os = \"macos\") || cfg!(target_os = \"ios\") {\n        result.ru_maxrss\n    } else {\n        result.ru_maxrss * 1024\n    };\n\n    #[allow(clippy::useless_conversion)]\n    CPUTimes {\n        user_usec: i64::from(result.ru_utime.tv_sec) * MICROSEC_PER_SEC\n            + i64::from(result.ru_utime.tv_usec),\n        system_usec: i64::from(result.ru_stime.tv_sec) * MICROSEC_PER_SEC\n            + i64::from(result.ru_stime.tv_usec),\n        memory_usage_byte: u64::try_from(max_rss_byte).unwrap_or(0),\n    }\n}\n\n/// Compute the time intervals in between two `CPUTimes` snapshots\nfn cpu_time_interval(start: &CPUTimes, end: &CPUTimes) -> CPUInterval {\n    CPUInterval {\n        user: ((end.user_usec - start.user_usec) as f64) * 1e-6,\n        system: ((end.system_usec - start.system_usec) as f64) * 1e-6,\n    }\n}\n\n#[cfg(test)]\nuse approx::assert_relative_eq;\n\n#[test]\nfn test_cpu_time_interval() {\n    let t_a = CPUTimes {\n        user_usec: 12345,\n        system_usec: 54321,\n        memory_usage_byte: 0,\n    };\n\n    let t_b = CPUTimes {\n        user_usec: 20000,\n        system_usec: 70000,\n        memory_usage_byte: 0,\n    };\n\n    let t_zero = cpu_time_interval(&t_a, &t_a);\n    assert!(t_zero.user.abs() < f64::EPSILON);\n    assert!(t_zero.system.abs() < f64::EPSILON);\n\n    let t_ab = cpu_time_interval(&t_a, &t_b);\n    assert_relative_eq!(0.007655, t_ab.user);\n    assert_relative_eq!(0.015679, t_ab.system);\n\n    let t_ba = cpu_time_interval(&t_b, &t_a);\n    assert_relative_eq!(-0.007655, t_ba.user);\n    assert_relative_eq!(-0.015679, t_ba.system);\n}\n"
  },
  {
    "path": "src/timer/wall_clock_timer.rs",
    "content": "use std::time::Instant;\n\nuse crate::util::units::Second;\n\npub struct WallClockTimer {\n    start: Instant,\n}\n\nimpl WallClockTimer {\n    pub fn start() -> WallClockTimer {\n        WallClockTimer {\n            start: Instant::now(),\n        }\n    }\n\n    pub fn stop(&self) -> Second {\n        let duration = self.start.elapsed();\n        duration.as_secs() as f64 + f64::from(duration.subsec_nanos()) * 1e-9\n    }\n}\n"
  },
  {
    "path": "src/timer/windows_timer.rs",
    "content": "#![cfg(windows)]\n#![warn(unsafe_op_in_unsafe_fn)]\n\nuse std::{mem, os::windows::io::AsRawHandle, process, ptr};\n\nuse windows_sys::Win32::{\n    Foundation::{CloseHandle, HANDLE},\n    System::JobObjects::{\n        AssignProcessToJobObject, CreateJobObjectW, JobObjectBasicAccountingInformation,\n        QueryInformationJobObject, JOBOBJECT_BASIC_ACCOUNTING_INFORMATION,\n    },\n};\n\n#[cfg(feature = \"windows_process_extensions_main_thread_handle\")]\nuse std::os::windows::process::ChildExt;\n#[cfg(feature = \"windows_process_extensions_main_thread_handle\")]\nuse windows_sys::Win32::System::Threading::ResumeThread;\n\n#[cfg(not(feature = \"windows_process_extensions_main_thread_handle\"))]\nuse once_cell::sync::Lazy;\n#[cfg(not(feature = \"windows_process_extensions_main_thread_handle\"))]\nuse windows_sys::{\n    s, w,\n    Win32::{\n        Foundation::{NTSTATUS, STATUS_SUCCESS},\n        System::LibraryLoader::{GetModuleHandleW, GetProcAddress},\n    },\n};\n\nuse crate::util::units::Second;\n\nconst HUNDRED_NS_PER_MS: i64 = 10;\n\n#[cfg(not(feature = \"windows_process_extensions_main_thread_handle\"))]\n#[allow(non_upper_case_globals)]\nstatic NtResumeProcess: Lazy<unsafe extern \"system\" fn(ProcessHandle: HANDLE) -> NTSTATUS> =\n    Lazy::new(|| {\n        // SAFETY: Getting the module handle for ntdll.dll is safe\n        let ntdll = unsafe { GetModuleHandleW(w!(\"ntdll.dll\")) };\n        assert!(ntdll != std::ptr::null_mut(), \"GetModuleHandleW failed\");\n\n        // SAFETY: The ntdll handle is valid\n        let nt_resume_process = unsafe { GetProcAddress(ntdll, s!(\"NtResumeProcess\")) };\n\n        // SAFETY: We transmute to the correct function signature\n        unsafe { mem::transmute(nt_resume_process.unwrap()) }\n    });\n\npub struct CPUTimer {\n    job_object: HANDLE,\n}\n\nimpl CPUTimer {\n    pub unsafe fn start_suspended_process(child: &process::Child) -> Self {\n        let child_handle = child.as_raw_handle() as HANDLE;\n\n        // SAFETY: Creating a new job object is safe\n        let job_object = unsafe { CreateJobObjectW(ptr::null_mut(), ptr::null_mut()) };\n        assert!(\n            job_object != std::ptr::null_mut(),\n            \"CreateJobObjectW failed\"\n        );\n\n        // SAFETY: The job object handle is valid\n        let ret = unsafe { AssignProcessToJobObject(job_object, child_handle) };\n        assert!(ret != 0, \"AssignProcessToJobObject failed\");\n\n        #[cfg(feature = \"windows_process_extensions_main_thread_handle\")]\n        {\n            // SAFETY: The main thread handle is valid\n            let ret = unsafe { ResumeThread(child.main_thread_handle().as_raw_handle() as HANDLE) };\n            assert!(ret != u32::MAX, \"ResumeThread failed\");\n        }\n\n        #[cfg(not(feature = \"windows_process_extensions_main_thread_handle\"))]\n        {\n            // Since we can't get the main thread handle on stable rust, we use\n            // the undocumented but widely known `NtResumeProcess` function to\n            // resume a process by it's handle.\n\n            // SAFETY: The process handle is valid\n            let ret = unsafe { NtResumeProcess(child_handle) };\n            assert!(ret == STATUS_SUCCESS, \"NtResumeProcess failed\");\n        }\n\n        Self { job_object }\n    }\n\n    pub fn stop(&self) -> (Second, Second, u64) {\n        let mut job_object_info =\n            mem::MaybeUninit::<JOBOBJECT_BASIC_ACCOUNTING_INFORMATION>::uninit();\n\n        // SAFETY: A valid job object got created in `start_suspended_process`\n        let res = unsafe {\n            QueryInformationJobObject(\n                self.job_object,\n                JobObjectBasicAccountingInformation,\n                job_object_info.as_mut_ptr().cast(),\n                mem::size_of::<JOBOBJECT_BASIC_ACCOUNTING_INFORMATION>() as u32,\n                ptr::null_mut(),\n            )\n        };\n\n        if res != 0 {\n            // SAFETY: The job object info got correctly initialized\n            let job_object_info = unsafe { job_object_info.assume_init() };\n\n            // The `TotalUserTime` is \"The total amount of user-mode execution time for\n            // all active processes associated with the job, as well as all terminated processes no\n            // longer associated with the job, in 100-nanosecond ticks.\"\n            let user: i64 = job_object_info.TotalUserTime / HUNDRED_NS_PER_MS;\n\n            // The `TotalKernelTime` is \"The total amount of kernel-mode execution time\n            // for all active processes associated with the job, as well as all terminated\n            // processes no longer associated with the job, in 100-nanosecond ticks.\"\n            let kernel: i64 = job_object_info.TotalKernelTime / HUNDRED_NS_PER_MS;\n            (user as f64 * 1e-6, kernel as f64 * 1e-6, 0)\n        } else {\n            (0.0, 0.0, 0)\n        }\n    }\n}\n\nimpl Drop for CPUTimer {\n    fn drop(&mut self) {\n        // SAFETY: A valid job object got created in `start_suspended_process`\n        unsafe { CloseHandle(self.job_object) };\n    }\n}\n"
  },
  {
    "path": "src/util/exit_code.rs",
    "content": "use std::process::ExitStatus;\n\n#[cfg(unix)]\npub fn extract_exit_code(status: ExitStatus) -> Option<i32> {\n    use std::os::unix::process::ExitStatusExt;\n\n    // From the ExitStatus::code documentation:\n    //\n    //   \"On Unix, this will return None if the process was terminated by a signal.\"\n    //\n    // In that case, ExitStatusExt::signal should never return None.\n    //\n    // To differentiate between \"normal\" exit codes and signals, we are using a technique\n    // similar to bash (https://tldp.org/LDP/abs/html/exitcodes.html) and add 128 to the\n    // signal value.\n    status.code().or_else(|| status.signal().map(|s| s + 128))\n}\n\n#[cfg(not(unix))]\npub fn extract_exit_code(status: ExitStatus) -> Option<i32> {\n    status.code()\n}\n"
  },
  {
    "path": "src/util/min_max.rs",
    "content": "/// A max function for f64's without NaNs\npub fn max(vals: &[f64]) -> f64 {\n    *vals\n        .iter()\n        .max_by(|a, b| a.partial_cmp(b).unwrap())\n        .unwrap()\n}\n\n/// A min function for f64's without NaNs\npub fn min(vals: &[f64]) -> f64 {\n    *vals\n        .iter()\n        .min_by(|a, b| a.partial_cmp(b).unwrap())\n        .unwrap()\n}\n\n#[test]\nfn test_max() {\n    let assert_float_eq = |a: f64, b: f64| {\n        assert!((a - b).abs() < f64::EPSILON);\n    };\n\n    assert_float_eq(1.0, max(&[1.0]));\n    assert_float_eq(-1.0, max(&[-1.0]));\n    assert_float_eq(-1.0, max(&[-2.0, -1.0]));\n    assert_float_eq(1.0, max(&[-1.0, 1.0]));\n    assert_float_eq(1.0, max(&[-1.0, 1.0, 0.0]));\n}\n"
  },
  {
    "path": "src/util/mod.rs",
    "content": "pub mod exit_code;\npub mod min_max;\npub mod number;\npub mod randomized_environment_offset;\npub mod units;\n"
  },
  {
    "path": "src/util/number.rs",
    "content": "use std::convert::TryFrom;\nuse std::fmt;\n\nuse rust_decimal::prelude::ToPrimitive;\nuse rust_decimal::Decimal;\nuse serde::Serialize;\n\n#[derive(Debug, Clone, Serialize, Copy, PartialEq, Eq)]\n#[serde(untagged)]\npub enum Number {\n    Int(i32),\n    Decimal(Decimal),\n}\n\nimpl fmt::Display for Number {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        match *self {\n            Number::Int(i) => fmt::Display::fmt(&i, f),\n            Number::Decimal(i) => fmt::Display::fmt(&i, f),\n        }\n    }\n}\n\nimpl From<i32> for Number {\n    fn from(x: i32) -> Number {\n        Number::Int(x)\n    }\n}\n\nimpl From<Decimal> for Number {\n    fn from(x: Decimal) -> Number {\n        Number::Decimal(x)\n    }\n}\n\nimpl TryFrom<Number> for usize {\n    type Error = ();\n\n    fn try_from(numeric: Number) -> Result<Self, Self::Error> {\n        match numeric {\n            Number::Int(i) => usize::try_from(i).map_err(|_| ()),\n            Number::Decimal(d) => match d.to_u64() {\n                Some(u) => usize::try_from(u).map_err(|_| ()),\n                None => Err(()),\n            },\n        }\n    }\n}\n"
  },
  {
    "path": "src/util/randomized_environment_offset.rs",
    "content": "/// Returns a string with a random length. This value will be set as an environment\n/// variable to account for offset effects. See [1] for more details.\n///\n/// [1] Mytkowicz, 2009. Producing Wrong Data Without Doing Anything Obviously Wrong!.\n///     Sigplan Notices - SIGPLAN. 44. 265-276. 10.1145/1508284.1508275.\npub fn value() -> String {\n    \"X\".repeat(rand::random::<usize>() % 4096usize)\n}\n"
  },
  {
    "path": "src/util/units.rs",
    "content": "//! This module contains common units.\n\npub type Scalar = f64;\n\n/// Type alias for unit of time\npub type Second = Scalar;\n\n/// Supported time units\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\npub enum Unit {\n    Second,\n    MilliSecond,\n    MicroSecond,\n}\n\nimpl Unit {\n    /// The abbreviation of the Unit.\n    pub fn short_name(self) -> String {\n        match self {\n            Unit::Second => String::from(\"s\"),\n            Unit::MilliSecond => String::from(\"ms\"),\n            Unit::MicroSecond => String::from(\"µs\"),\n        }\n    }\n\n    /// Returns the Second value formatted for the Unit.\n    pub fn format(self, value: Second) -> String {\n        match self {\n            Unit::Second => format!(\"{value:.3}\"),\n            Unit::MilliSecond => format!(\"{:.1}\", value * 1e3),\n            Unit::MicroSecond => format!(\"{:.1}\", value * 1e6),\n        }\n    }\n}\n\n#[test]\nfn test_unit_short_name() {\n    assert_eq!(\"s\", Unit::Second.short_name());\n    assert_eq!(\"ms\", Unit::MilliSecond.short_name());\n    assert_eq!(\"µs\", Unit::MicroSecond.short_name());\n}\n\n// Note - the values are rounded when formatted.\n#[test]\nfn test_unit_format() {\n    let value: Second = 123.456789;\n    assert_eq!(\"123.457\", Unit::Second.format(value));\n    assert_eq!(\"123456.8\", Unit::MilliSecond.format(value));\n\n    assert_eq!(\"1234.6\", Unit::MicroSecond.format(0.00123456));\n}\n"
  },
  {
    "path": "tests/common.rs",
    "content": "use std::process::Command;\n\nuse assert_cmd::cargo::CommandCargoExt;\n\npub fn hyperfine_raw_command() -> Command {\n    let mut cmd = Command::cargo_bin(\"hyperfine\").unwrap();\n    cmd.current_dir(\"tests/\");\n    cmd\n}\n\npub fn hyperfine() -> assert_cmd::Command {\n    assert_cmd::Command::from_std(hyperfine_raw_command())\n}\n"
  },
  {
    "path": "tests/example_input_file.txt",
    "content": "This text is part of a file\n"
  },
  {
    "path": "tests/execution_order_tests.rs",
    "content": "use std::{fs::File, io::Read, path::PathBuf};\n\nuse tempfile::{tempdir, TempDir};\n\nmod common;\nuse common::hyperfine;\n\nstruct ExecutionOrderTest {\n    cmd: assert_cmd::Command,\n    expected_content: String,\n    logfile_path: PathBuf,\n    #[allow(dead_code)]\n    tempdir: TempDir,\n}\n\nimpl ExecutionOrderTest {\n    fn new() -> Self {\n        let tempdir = tempdir().unwrap();\n        let logfile_path = tempdir.path().join(\"output.log\");\n\n        ExecutionOrderTest {\n            cmd: hyperfine(),\n            expected_content: String::new(),\n            logfile_path,\n            tempdir,\n        }\n    }\n\n    fn arg<S: AsRef<str>>(&mut self, arg: S) -> &mut Self {\n        self.cmd.arg(arg.as_ref());\n        self\n    }\n\n    fn get_command(&self, output: &str) -> String {\n        format!(\n            \"echo {output} >> {path}\",\n            output = output,\n            path = self.logfile_path.to_string_lossy()\n        )\n    }\n\n    fn command(&mut self, output: &str) -> &mut Self {\n        self.arg(self.get_command(output));\n        self\n    }\n\n    fn setup(&mut self, output: &str) -> &mut Self {\n        self.arg(\"--setup\");\n        self.command(output)\n    }\n\n    fn prepare(&mut self, output: &str) -> &mut Self {\n        self.arg(\"--prepare\");\n        self.command(output)\n    }\n\n    fn reference(&mut self, output: &str) -> &mut Self {\n        self.arg(\"--reference\");\n        self.command(output)\n    }\n\n    fn conclude(&mut self, output: &str) -> &mut Self {\n        self.arg(\"--conclude\");\n        self.command(output)\n    }\n\n    fn cleanup(&mut self, output: &str) -> &mut Self {\n        self.arg(\"--cleanup\");\n        self.command(output)\n    }\n\n    fn expect_output(&mut self, output: &str) -> &mut Self {\n        self.expected_content.push_str(output);\n\n        #[cfg(windows)]\n        {\n            self.expected_content.push_str(\" \\r\");\n        }\n\n        self.expected_content.push('\\n');\n        self\n    }\n\n    fn run(&mut self) {\n        self.cmd.assert().success();\n\n        let mut f = File::open(&self.logfile_path).unwrap();\n        let mut content = String::new();\n        f.read_to_string(&mut content).unwrap();\n\n        assert_eq!(content, self.expected_content);\n    }\n}\n\nimpl Default for ExecutionOrderTest {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\n#[test]\nfn benchmarks_are_executed_sequentially_one() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=1\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 2\")\n        .run();\n}\n\n#[test]\nfn benchmarks_are_executed_sequentially() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=2\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 2\")\n        .expect_output(\"command 2\")\n        .run();\n}\n\n#[test]\nfn warmup_runs_are_executed_before_benchmarking_runs() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=2\")\n        .arg(\"--warmup=3\")\n        .command(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .run();\n}\n\n#[test]\nfn setup_commands_are_executed_before_each_series_of_timing_runs() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=2\")\n        .setup(\"setup\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        .expect_output(\"setup\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"setup\")\n        .expect_output(\"command 2\")\n        .expect_output(\"command 2\")\n        .run();\n}\n\n#[test]\nfn prepare_commands_are_executed_before_each_timing_run() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=2\")\n        .prepare(\"prepare\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command 1\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command 1\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command 2\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command 2\")\n        .run();\n}\n\n#[test]\nfn conclude_commands_are_executed_after_each_timing_run() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=2\")\n        .conclude(\"conclude\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        .expect_output(\"command 1\")\n        .expect_output(\"conclude\")\n        .expect_output(\"command 1\")\n        .expect_output(\"conclude\")\n        .expect_output(\"command 2\")\n        .expect_output(\"conclude\")\n        .expect_output(\"command 2\")\n        .expect_output(\"conclude\")\n        .run();\n}\n\n#[test]\nfn prepare_commands_are_executed_before_each_warmup() {\n    ExecutionOrderTest::new()\n        .arg(\"--warmup=2\")\n        .arg(\"--runs=1\")\n        .prepare(\"prepare\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        // warmup 1\n        .expect_output(\"prepare\")\n        .expect_output(\"command 1\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command 1\")\n        // benchmark 1\n        .expect_output(\"prepare\")\n        .expect_output(\"command 1\")\n        // warmup 2\n        .expect_output(\"prepare\")\n        .expect_output(\"command 2\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command 2\")\n        // benchmark 2\n        .expect_output(\"prepare\")\n        .expect_output(\"command 2\")\n        .run();\n}\n\n#[test]\nfn conclude_commands_are_executed_after_each_warmup() {\n    ExecutionOrderTest::new()\n        .arg(\"--warmup=2\")\n        .arg(\"--runs=1\")\n        .conclude(\"conclude\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        // warmup 1\n        .expect_output(\"command 1\")\n        .expect_output(\"conclude\")\n        .expect_output(\"command 1\")\n        .expect_output(\"conclude\")\n        // benchmark 1\n        .expect_output(\"command 1\")\n        .expect_output(\"conclude\")\n        // warmup 2\n        .expect_output(\"command 2\")\n        .expect_output(\"conclude\")\n        .expect_output(\"command 2\")\n        .expect_output(\"conclude\")\n        // benchmark 2\n        .expect_output(\"command 2\")\n        .expect_output(\"conclude\")\n        .run();\n}\n\n#[test]\nfn cleanup_commands_are_executed_once_after_each_benchmark() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=2\")\n        .cleanup(\"cleanup\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"cleanup\")\n        .expect_output(\"command 2\")\n        .expect_output(\"command 2\")\n        .expect_output(\"cleanup\")\n        .run();\n}\n\n#[test]\nfn setup_prepare_cleanup_combined() {\n    ExecutionOrderTest::new()\n        .arg(\"--warmup=1\")\n        .arg(\"--runs=2\")\n        .setup(\"setup\")\n        .prepare(\"prepare\")\n        .command(\"command1\")\n        .command(\"command2\")\n        .cleanup(\"cleanup\")\n        // 1\n        .expect_output(\"setup\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command1\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command1\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command1\")\n        .expect_output(\"cleanup\")\n        // 2\n        .expect_output(\"setup\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command2\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command2\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command2\")\n        .expect_output(\"cleanup\")\n        .run();\n}\n\n#[test]\nfn setup_prepare_conclude_cleanup_combined() {\n    ExecutionOrderTest::new()\n        .arg(\"--warmup=1\")\n        .arg(\"--runs=2\")\n        .setup(\"setup\")\n        .prepare(\"prepare\")\n        .command(\"command1\")\n        .command(\"command2\")\n        .conclude(\"conclude\")\n        .cleanup(\"cleanup\")\n        // 1\n        .expect_output(\"setup\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude\")\n        .expect_output(\"cleanup\")\n        // 2\n        .expect_output(\"setup\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude\")\n        .expect_output(\"cleanup\")\n        .run();\n}\n\n#[test]\nfn single_parameter_value() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=2\")\n        .arg(\"--parameter-list\")\n        .arg(\"number\")\n        .arg(\"1,2,3\")\n        .command(\"command {number}\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 2\")\n        .expect_output(\"command 2\")\n        .expect_output(\"command 3\")\n        .expect_output(\"command 3\")\n        .run();\n}\n\n#[test]\nfn multiple_parameter_values() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=2\")\n        .arg(\"--parameter-list\")\n        .arg(\"number\")\n        .arg(\"1,2,3\")\n        .arg(\"--parameter-list\")\n        .arg(\"letter\")\n        .arg(\"a,b\")\n        .command(\"command {number} {letter}\")\n        .expect_output(\"command 1 a\")\n        .expect_output(\"command 1 a\")\n        .expect_output(\"command 2 a\")\n        .expect_output(\"command 2 a\")\n        .expect_output(\"command 3 a\")\n        .expect_output(\"command 3 a\")\n        .expect_output(\"command 1 b\")\n        .expect_output(\"command 1 b\")\n        .expect_output(\"command 2 b\")\n        .expect_output(\"command 2 b\")\n        .expect_output(\"command 3 b\")\n        .expect_output(\"command 3 b\")\n        .run();\n}\n\n#[test]\nfn reference_is_executed_first() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=1\")\n        .reference(\"reference\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        .expect_output(\"reference\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 2\")\n        .run();\n}\n\n#[test]\nfn reference_is_executed_first_parameter_value() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=2\")\n        .reference(\"reference\")\n        .arg(\"--parameter-list\")\n        .arg(\"number\")\n        .arg(\"1,2,3\")\n        .command(\"command {number}\")\n        .expect_output(\"reference\")\n        .expect_output(\"reference\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 2\")\n        .expect_output(\"command 2\")\n        .expect_output(\"command 3\")\n        .expect_output(\"command 3\")\n        .run();\n}\n\n#[test]\nfn reference_is_executed_separately_from_commands() {\n    ExecutionOrderTest::new()\n        .arg(\"--runs=1\")\n        .reference(\"command 1\")\n        .command(\"command 1\")\n        .command(\"command 2\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 1\")\n        .expect_output(\"command 2\")\n        .run();\n}\n\n#[test]\nfn setup_prepare_reference_conclude_cleanup_combined() {\n    ExecutionOrderTest::new()\n        .arg(\"--warmup=1\")\n        .arg(\"--runs=2\")\n        .setup(\"setup\")\n        .prepare(\"prepare\")\n        .reference(\"reference\")\n        .command(\"command1\")\n        .command(\"command2\")\n        .conclude(\"conclude\")\n        .cleanup(\"cleanup\")\n        // reference\n        .expect_output(\"setup\")\n        .expect_output(\"prepare\")\n        .expect_output(\"reference\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"reference\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"reference\")\n        .expect_output(\"conclude\")\n        .expect_output(\"cleanup\")\n        // 1\n        .expect_output(\"setup\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude\")\n        .expect_output(\"cleanup\")\n        // 2\n        .expect_output(\"setup\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude\")\n        .expect_output(\"prepare\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude\")\n        .expect_output(\"cleanup\")\n        .run();\n}\n\n#[test]\nfn setup_separate_prepare_separate_conclude_cleanup_combined() {\n    ExecutionOrderTest::new()\n        .arg(\"--warmup=1\")\n        .arg(\"--runs=2\")\n        .setup(\"setup\")\n        .cleanup(\"cleanup\")\n        .prepare(\"prepare1\")\n        .command(\"command1\")\n        .conclude(\"conclude1\")\n        .prepare(\"prepare2\")\n        .command(\"command2\")\n        .conclude(\"conclude2\")\n        // 1\n        .expect_output(\"setup\")\n        .expect_output(\"prepare1\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude1\")\n        .expect_output(\"prepare1\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude1\")\n        .expect_output(\"prepare1\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude1\")\n        .expect_output(\"cleanup\")\n        // 2\n        .expect_output(\"setup\")\n        .expect_output(\"prepare2\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude2\")\n        .expect_output(\"prepare2\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude2\")\n        .expect_output(\"prepare2\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude2\")\n        .expect_output(\"cleanup\")\n        .run();\n}\n\n#[test]\nfn setup_separate_prepare_reference_separate_conclude_cleanup_combined() {\n    ExecutionOrderTest::new()\n        .arg(\"--warmup=1\")\n        .arg(\"--runs=2\")\n        .setup(\"setup\")\n        .cleanup(\"cleanup\")\n        .prepare(\"prepareref\")\n        .reference(\"reference\")\n        .conclude(\"concluderef\")\n        .prepare(\"prepare1\")\n        .command(\"command1\")\n        .conclude(\"conclude1\")\n        .prepare(\"prepare2\")\n        .command(\"command2\")\n        .conclude(\"conclude2\")\n        // reference\n        .expect_output(\"setup\")\n        .expect_output(\"prepareref\")\n        .expect_output(\"reference\")\n        .expect_output(\"concluderef\")\n        .expect_output(\"prepareref\")\n        .expect_output(\"reference\")\n        .expect_output(\"concluderef\")\n        .expect_output(\"prepareref\")\n        .expect_output(\"reference\")\n        .expect_output(\"concluderef\")\n        .expect_output(\"cleanup\")\n        // 1\n        .expect_output(\"setup\")\n        .expect_output(\"prepare1\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude1\")\n        .expect_output(\"prepare1\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude1\")\n        .expect_output(\"prepare1\")\n        .expect_output(\"command1\")\n        .expect_output(\"conclude1\")\n        .expect_output(\"cleanup\")\n        // 2\n        .expect_output(\"setup\")\n        .expect_output(\"prepare2\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude2\")\n        .expect_output(\"prepare2\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude2\")\n        .expect_output(\"prepare2\")\n        .expect_output(\"command2\")\n        .expect_output(\"conclude2\")\n        .expect_output(\"cleanup\")\n        .run();\n}\n"
  },
  {
    "path": "tests/integration_tests.rs",
    "content": "mod common;\nuse common::hyperfine;\n\nuse predicates::prelude::*;\n\n/// Platform-specific I/O utility.\n/// - On Unix-like systems, defaults to `cat`.\n/// - On Windows, uses `findstr` as an alternative.\n///   See: <https://superuser.com/questions/853580/real-windows-equivalent-to-cat-stdin>\nconst STDIN_READ_COMMAND: &str = if cfg!(windows) { \"findstr x*\" } else { \"cat\" };\n\npub fn hyperfine_debug() -> assert_cmd::Command {\n    let mut cmd = hyperfine();\n    cmd.arg(\"--debug-mode\");\n    cmd\n}\n\n#[test]\nfn runs_successfully() {\n    hyperfine()\n        .arg(\"--runs=2\")\n        .arg(\"echo dummy benchmark\")\n        .assert()\n        .success();\n}\n\n#[test]\nfn one_run_is_supported() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"echo dummy benchmark\")\n        .assert()\n        .success();\n}\n\n#[test]\nfn can_run_commands_without_a_shell() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--show-output\")\n        .arg(\"--shell=none\")\n        .arg(\"echo 'hello world' argument2\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"hello world argument2\"));\n}\n\n#[test]\nfn fails_with_wrong_number_of_command_name_arguments() {\n    hyperfine()\n        .arg(\"--command-name=a\")\n        .arg(\"--command-name=b\")\n        .arg(\"echo a\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\"Too many --command-name options\"));\n}\n\n#[test]\nfn fails_with_wrong_number_of_prepare_options() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--prepare=echo a\")\n        .arg(\"--prepare=echo b\")\n        .arg(\"echo a\")\n        .arg(\"echo b\")\n        .assert()\n        .success();\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--prepare=echo ref\")\n        .arg(\"--prepare=echo a\")\n        .arg(\"--prepare=echo b\")\n        .arg(\"--reference=echo ref\")\n        .arg(\"echo a\")\n        .arg(\"echo b\")\n        .assert()\n        .success();\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--prepare=echo a\")\n        .arg(\"--prepare=echo b\")\n        .arg(\"echo a\")\n        .arg(\"echo b\")\n        .arg(\"echo c\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"The '--prepare' option has to be provided\",\n        ));\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--prepare=echo a\")\n        .arg(\"--prepare=echo b\")\n        .arg(\"--reference=echo ref\")\n        .arg(\"echo a\")\n        .arg(\"echo b\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"The '--prepare' option has to be provided\",\n        ));\n}\n\n#[test]\nfn fails_with_wrong_number_of_conclude_options() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--conclude=echo a\")\n        .arg(\"--conclude=echo b\")\n        .arg(\"echo a\")\n        .arg(\"echo b\")\n        .assert()\n        .success();\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--conclude=echo ref\")\n        .arg(\"--conclude=echo a\")\n        .arg(\"--conclude=echo b\")\n        .arg(\"--reference=echo ref\")\n        .arg(\"echo a\")\n        .arg(\"echo b\")\n        .assert()\n        .success();\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--conclude=echo a\")\n        .arg(\"--conclude=echo b\")\n        .arg(\"echo a\")\n        .arg(\"echo b\")\n        .arg(\"echo c\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"The '--conclude' option has to be provided\",\n        ));\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--conclude=echo a\")\n        .arg(\"--conclude=echo b\")\n        .arg(\"--reference=echo ref\")\n        .arg(\"echo a\")\n        .arg(\"echo b\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"The '--conclude' option has to be provided\",\n        ));\n}\n\n#[test]\nfn fails_with_duplicate_parameter_names() {\n    hyperfine()\n        .arg(\"--parameter-list\")\n        .arg(\"x\")\n        .arg(\"1,2,3\")\n        .arg(\"--parameter-list\")\n        .arg(\"x\")\n        .arg(\"a,b,c\")\n        .arg(\"echo test\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\"Duplicate parameter names: x\"));\n}\n\n#[test]\nfn fails_for_unknown_command() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"some-nonexisting-program-b5d9574198b7e4b12a71fa4747c0a577\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"Command terminated with non-zero exit code\",\n        ));\n}\n\n#[test]\nfn fails_for_unknown_command_without_shell() {\n    hyperfine()\n        .arg(\"--shell=none\")\n        .arg(\"--runs=1\")\n        .arg(\"some-nonexisting-program-b5d9574198b7e4b12a71fa4747c0a577\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"Failed to run command 'some-nonexisting-program-b5d9574198b7e4b12a71fa4747c0a577'\",\n        ));\n}\n\n#[cfg(unix)]\n#[test]\nfn fails_for_failing_command_without_shell() {\n    hyperfine()\n        .arg(\"--shell=none\")\n        .arg(\"--runs=1\")\n        .arg(\"false\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"Command terminated with non-zero exit code\",\n        ));\n}\n\n#[test]\nfn fails_for_unknown_setup_command() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--setup=some-nonexisting-program-b5d9574198b7e4b12a71fa4747c0a577\")\n        .arg(\"echo test\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"The setup command terminated with a non-zero exit code.\",\n        ));\n}\n\n#[test]\nfn fails_for_unknown_cleanup_command() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--cleanup=some-nonexisting-program-b5d9574198b7e4b12a71fa4747c0a577\")\n        .arg(\"echo test\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"The cleanup command terminated with a non-zero exit code.\",\n        ));\n}\n\n#[test]\nfn fails_for_unknown_prepare_command() {\n    hyperfine()\n        .arg(\"--prepare=some-nonexisting-program-b5d9574198b7e4b12a71fa4747c0a577\")\n        .arg(\"echo test\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"The preparation command terminated with a non-zero exit code.\",\n        ));\n}\n\n#[test]\nfn fails_for_unknown_conclude_command() {\n    hyperfine()\n        .arg(\"--conclude=some-nonexisting-program-b5d9574198b7e4b12a71fa4747c0a577\")\n        .arg(\"echo test\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"The conclusion command terminated with a non-zero exit code.\",\n        ));\n}\n\n#[cfg(unix)]\n#[test]\nfn can_run_failing_commands_with_ignore_failure_option() {\n    hyperfine()\n        .arg(\"false\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"Command terminated with non-zero exit code\",\n        ));\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--ignore-failure\")\n        .arg(\"false\")\n        .assert()\n        .success();\n}\n\n#[cfg(unix)]\n#[test]\nfn can_ignore_specific_exit_codes() {\n    // Test that specifying exit code 1 ignores it\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--ignore-failure=1\")\n        .arg(\"exit 1\")\n        .assert()\n        .success();\n\n    // Test that other exit codes still fail\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--ignore-failure=1\")\n        .arg(\"exit 2\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"Command terminated with non-zero exit code 2\",\n        ));\n}\n\n#[cfg(unix)]\n#[test]\nfn can_ignore_multiple_exit_codes() {\n    // Test that all specified exit codes are ignored\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--ignore-failure=1,2,3\")\n        .arg(\"exit 1\")\n        .assert()\n        .success();\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--ignore-failure=1,2,3\")\n        .arg(\"exit 2\")\n        .assert()\n        .success();\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--ignore-failure=1,2,3\")\n        .arg(\"exit 3\")\n        .assert()\n        .success();\n\n    // Test that other exit codes still fail\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--ignore-failure=1,2,3\")\n        .arg(\"exit 4\")\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"Command terminated with non-zero exit code 4\",\n        ));\n}\n\n#[cfg(unix)]\n#[test]\nfn ignore_failure_with_all_non_zero() {\n    // Test explicit \"all-non-zero\" mode\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--ignore-failure=all-non-zero\")\n        .arg(\"exit 5\")\n        .assert()\n        .success();\n}\n\n#[test]\nfn shows_output_of_benchmarked_command() {\n    hyperfine()\n        .arg(\"--runs=2\")\n        .arg(\"--command-name=dummy\")\n        .arg(\"--show-output\")\n        .arg(\"echo 4fd47015\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"4fd47015\").count(2));\n}\n\n#[test]\nfn runs_commands_using_user_defined_shell() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--show-output\")\n        .arg(\"--shell\")\n        .arg(\"echo 'custom_shell' '--shell-arg'\")\n        .arg(\"echo benchmark\")\n        .assert()\n        .success()\n        .stdout(\n            predicate::str::contains(\"custom_shell --shell-arg -c echo benchmark\").or(\n                predicate::str::contains(\"custom_shell --shell-arg /C echo benchmark\"),\n            ),\n        );\n}\n\n#[test]\nfn can_pass_input_to_command_from_a_file() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--input=example_input_file.txt\")\n        .arg(\"--show-output\")\n        .arg(STDIN_READ_COMMAND)\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"This text is part of a file\"));\n}\n\n#[test]\nfn fails_if_invalid_stdin_data_file_provided() {\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--input=example_non_existent_file.txt\")\n        .arg(\"--show-output\")\n        .arg(STDIN_READ_COMMAND)\n        .assert()\n        .failure()\n        .stderr(predicate::str::contains(\n            \"The file 'example_non_existent_file.txt' specified as '--input' does not exist\",\n        ));\n}\n\n#[test]\nfn returns_mean_time_in_correct_unit() {\n    hyperfine_debug()\n        .arg(\"sleep 1.234\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"Time (mean ± σ):      1.234 s ±\"));\n\n    hyperfine_debug()\n        .arg(\"sleep 0.123\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"Time (mean ± σ):     123.0 ms ±\"));\n\n    hyperfine_debug()\n        .arg(\"--time-unit=millisecond\")\n        .arg(\"sleep 1.234\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"Time (mean ± σ):     1234.0 ms ±\"));\n\n    hyperfine_debug()\n        .arg(\"--time-unit=microsecond\")\n        .arg(\"sleep 1.234\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\n            \"Time (mean ± σ):     1234000.0 µs ±\",\n        ));\n}\n\n#[test]\nfn performs_ten_runs_for_slow_commands() {\n    hyperfine_debug()\n        .arg(\"sleep 0.5\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"10 runs\"));\n}\n\n#[test]\nfn performs_three_seconds_of_benchmarking_for_fast_commands() {\n    hyperfine_debug()\n        .arg(\"sleep 0.01\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"300 runs\"));\n}\n\n#[test]\nfn takes_shell_spawning_time_into_account_for_computing_number_of_runs() {\n    hyperfine_debug()\n        .arg(\"--shell=sleep 0.02\")\n        .arg(\"sleep 0.01\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"100 runs\"));\n}\n\n#[test]\nfn takes_preparation_command_into_account_for_computing_number_of_runs() {\n    hyperfine_debug()\n        .arg(\"--prepare=sleep 0.02\")\n        .arg(\"sleep 0.01\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"100 runs\"));\n\n    // Shell overhead needs to be added to both the prepare command and the actual command,\n    // leading to a total benchmark time of (prepare + shell + cmd + shell = 0.1 s)\n    hyperfine_debug()\n        .arg(\"--shell=sleep 0.01\")\n        .arg(\"--prepare=sleep 0.03\")\n        .arg(\"sleep 0.05\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"30 runs\"));\n}\n\n#[test]\nfn takes_conclusion_command_into_account_for_computing_number_of_runs() {\n    hyperfine_debug()\n        .arg(\"--conclude=sleep 0.02\")\n        .arg(\"sleep 0.01\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"100 runs\"));\n\n    // Shell overhead needs to be added to both the conclude command and the actual command,\n    // leading to a total benchmark time of (cmd + shell + conclude + shell = 0.1 s)\n    hyperfine_debug()\n        .arg(\"--shell=sleep 0.01\")\n        .arg(\"--conclude=sleep 0.03\")\n        .arg(\"sleep 0.05\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"30 runs\"));\n}\n\n#[test]\nfn takes_both_preparation_and_conclusion_command_into_account_for_computing_number_of_runs() {\n    hyperfine_debug()\n        .arg(\"--prepare=sleep 0.01\")\n        .arg(\"--conclude=sleep 0.01\")\n        .arg(\"sleep 0.01\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"100 runs\"));\n\n    // Shell overhead needs to be added to both the prepare, conclude and the actual command,\n    // leading to a total benchmark time of (prepare + shell + cmd + shell + conclude + shell = 0.1 s)\n    hyperfine_debug()\n        .arg(\"--shell=sleep 0.01\")\n        .arg(\"--prepare=sleep 0.01\")\n        .arg(\"--conclude=sleep 0.01\")\n        .arg(\"sleep 0.05\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"30 runs\"));\n}\n\n#[test]\nfn shows_benchmark_comparison_with_relative_times() {\n    hyperfine_debug()\n        .arg(\"sleep 1.0\")\n        .arg(\"sleep 2.0\")\n        .arg(\"sleep 3.0\")\n        .assert()\n        .success()\n        .stdout(\n            predicate::str::contains(\"2.00 ± 0.00 times faster\")\n                .and(predicate::str::contains(\"3.00 ± 0.00 times faster\")),\n        );\n}\n\n#[test]\nfn shows_benchmark_comparison_with_same_time() {\n    hyperfine_debug()\n        .arg(\"--command-name=A\")\n        .arg(\"--command-name=B\")\n        .arg(\"sleep 1.0\")\n        .arg(\"sleep 1.0\")\n        .arg(\"sleep 2.0\")\n        .arg(\"sleep 1000.0\")\n        .assert()\n        .success()\n        .stdout(\n            predicate::str::contains(\"As fast (1.00 ± 0.00) as\")\n                .and(predicate::str::contains(\"2.00 ± 0.00 times faster\"))\n                .and(predicate::str::contains(\"1000.00 ± 0.00 times faster\")),\n        );\n}\n\n#[test]\nfn shows_benchmark_comparison_relative_to_reference() {\n    hyperfine_debug()\n        .arg(\"--reference=sleep 2.0\")\n        .arg(\"sleep 1.0\")\n        .arg(\"sleep 3.0\")\n        .assert()\n        .success()\n        .stdout(\n            predicate::str::contains(\"2.00 ± 0.00 times slower\")\n                .and(predicate::str::contains(\"1.50 ± 0.00 times faster\")),\n        );\n}\n\n#[test]\nfn shows_reference_name() {\n    hyperfine_debug()\n        .arg(\"--reference=sleep 2.0\")\n        .arg(\"--reference-name=refabc123\")\n        .arg(\"sleep 1.0\")\n        .arg(\"sleep 3.0\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\"Benchmark 1: refabc123\"));\n}\n\n#[test]\nfn performs_all_benchmarks_in_parameter_scan() {\n    hyperfine_debug()\n        .arg(\"--parameter-scan\")\n        .arg(\"time\")\n        .arg(\"30\")\n        .arg(\"45\")\n        .arg(\"--parameter-step-size\")\n        .arg(\"5\")\n        .arg(\"sleep {time}\")\n        .assert()\n        .success()\n        .stdout(\n            predicate::str::contains(\"Benchmark 1: sleep 30\")\n                .and(predicate::str::contains(\"Benchmark 2: sleep 35\"))\n                .and(predicate::str::contains(\"Benchmark 3: sleep 40\"))\n                .and(predicate::str::contains(\"Benchmark 4: sleep 45\"))\n                .and(predicate::str::contains(\"Benchmark 5: sleep 50\").not()),\n        );\n}\n\n#[test]\nfn performs_reference_and_all_benchmarks_in_parameter_scan() {\n    hyperfine_debug()\n        .arg(\"--reference=sleep 25\")\n        .arg(\"--parameter-scan\")\n        .arg(\"time\")\n        .arg(\"30\")\n        .arg(\"45\")\n        .arg(\"--parameter-step-size\")\n        .arg(\"5\")\n        .arg(\"sleep {time}\")\n        .assert()\n        .success()\n        .stdout(\n            predicate::str::contains(\"Benchmark 1: sleep 25\")\n                .and(predicate::str::contains(\"Benchmark 2: sleep 30\"))\n                .and(predicate::str::contains(\"Benchmark 3: sleep 35\"))\n                .and(predicate::str::contains(\"Benchmark 4: sleep 40\"))\n                .and(predicate::str::contains(\"Benchmark 5: sleep 45\"))\n                .and(predicate::str::contains(\"Benchmark 6: sleep 50\").not()),\n        );\n}\n\n#[test]\nfn intermediate_results_are_not_exported_to_stdout() {\n    hyperfine_debug()\n        .arg(\"--style=none\") // To only see the Markdown export on stdout\n        .arg(\"--export-markdown\")\n        .arg(\"-\")\n        .arg(\"sleep 1\")\n        .arg(\"sleep 2\")\n        .assert()\n        .success()\n        .stdout(\n            (predicate::str::contains(\"sleep 1\").count(1))\n                .and(predicate::str::contains(\"sleep 2\").count(1)),\n        );\n}\n\n#[test]\n#[cfg(unix)]\nfn exports_intermediate_results_to_file() {\n    use tempfile::tempdir;\n\n    let tempdir = tempdir().unwrap();\n    let export_path = tempdir.path().join(\"results.md\");\n\n    hyperfine()\n        .arg(\"--runs=1\")\n        .arg(\"--export-markdown\")\n        .arg(&export_path)\n        .arg(\"true\")\n        .arg(\"false\")\n        .assert()\n        .failure();\n\n    let contents = std::fs::read_to_string(export_path).unwrap();\n    assert!(contents.contains(\"true\"));\n}\n\n#[test]\nfn unused_parameters_are_shown_in_benchmark_name() {\n    hyperfine()\n        .arg(\"--runs=2\")\n        .arg(\"--parameter-list\")\n        .arg(\"branch\")\n        .arg(\"master,feature\")\n        .arg(\"echo test\")\n        .assert()\n        .success()\n        .stdout(\n            predicate::str::contains(\"echo test (branch = master)\")\n                .and(predicate::str::contains(\"echo test (branch = feature)\")),\n        );\n}\n\n#[test]\nfn speed_comparison_sort_order() {\n    for sort_order in [\"auto\", \"mean-time\"] {\n        hyperfine_debug()\n            .arg(\"sleep 2\")\n            .arg(\"sleep 1\")\n            .arg(format!(\"--sort={sort_order}\"))\n            .assert()\n            .success()\n            .stdout(predicate::str::contains(\n                \"sleep 1 ran\\n    2.00 ± 0.00 times faster than sleep 2\",\n            ));\n    }\n\n    hyperfine_debug()\n        .arg(\"sleep 2\")\n        .arg(\"sleep 1\")\n        .arg(\"--sort=command\")\n        .assert()\n        .success()\n        .stdout(predicate::str::contains(\n            \"2.00 ±  0.00  sleep 2\\n        1.00          sleep 1\",\n        ));\n}\n\n#[cfg(windows)]\n#[test]\nfn windows_quote_args() {\n    hyperfine()\n        .arg(\"more \\\"example_input_file.txt\\\"\")\n        .assert()\n        .success();\n}\n\n#[cfg(windows)]\n#[test]\nfn windows_quote_before_quote_args() {\n    hyperfine()\n        .arg(\"dir \\\"..\\\\src\\\\\\\" \\\"..\\\\tests\\\\\\\"\")\n        .assert()\n        .success();\n}\n"
  }
]