[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: github-actions\n    directory: '/'\n    schedule:\n      interval: daily\n    open-pull-requests-limit: 10\n  - package-ecosystem: npm\n    directory: '/'\n    schedule:\n      interval: daily\n    open-pull-requests-limit: 10\n"
  },
  {
    "path": ".github/release-drafter.yml",
    "content": "template: |\n    ## What’s Changed\n\n    $CHANGES\n"
  },
  {
    "path": ".github/tests_checker.yml",
    "content": "comment: 'Could you please add tests to make sure this change works as expected?',\nfileExtensions: ['.php', '.ts', '.js', '.c', '.cs', '.cpp', '.rb', '.java']\ntestDir: 'test'\n"
  },
  {
    "path": ".github/workflows/nodejs.yml",
    "content": "name: Node.js CI\n\non: [ push, pull_request ]\n\n# This allows a subsequently queued workflow run to interrupt previous runs\nconcurrency:\n  group: \"${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}\"\n  cancel-in-progress: true\n\njobs:\n  test:\n    runs-on: ${{ matrix.os }}\n    permissions:\n      contents: read\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [ubuntu-latest, windows-latest, macos-latest]\n        node-version: [20, 22]\n\n    steps:\n    - uses: actions/checkout@v3\n      with:\n        persist-credentials: false\n    - name: Use Node.js ${{ matrix.node-version }}\n      uses: actions/setup-node@v3\n      with:\n        node-version: ${{ matrix.node-version }}\n    - run: node --version\n    - run: npm --version\n    - run: npm install\n    - run: npm test\n      env:\n        CI: true\n\n  automerge:\n    if: >\n        github.event_name == 'pull_request' &&\n        github.event.pull_request.user.login == 'dependabot[bot]'\n    needs: test\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n      contents: write\n    steps:\n      - uses: fastify/github-action-merge-dependabot@v3\n        with:\n          exclude: 'chalk;pretty-bytes'\n          github-token: ${{ secrets.github_token }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n\n# Diagnostic reports (https://nodejs.org/api/report.html)\nreport.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n*.lcov\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Snowpack dependency directory (https://snowpack.dev/)\nweb_modules/\n\n# TypeScript cache\n*.tsbuildinfo\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Microbundle cache\n.rpt2_cache/\n.rts2_cache_cjs/\n.rts2_cache_es/\n.rts2_cache_umd/\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n.env.test\n\n# parcel-bundler cache (https://parceljs.org/)\n.cache\n.parcel-cache\n\n# Next.js build output\n.next\nout\n\n# Nuxt.js build / generate output\n.nuxt\ndist\n\n# Gatsby files\n.cache/\n# Comment in the public line in if your project uses Gatsby and not Next.js\n# https://nextjs.org/blog/next-9-1#public-directory-support\n# public\n\n# vuepress build output\n.vuepress/dist\n\n# Serverless directories\n.serverless/\n\n# FuseBox cache\n.fusebox/\n\n# DynamoDB Local files\n.dynamodb/\n\n# TernJS port file\n.tern-port\n\n# Stores VSCode versions used for testing VSCode extensions\n.vscode-test\n\n# yarn v2\n.yarn/cache\n.yarn/unplugged\n.yarn/build-state.yml\n.yarn/install-state.gz\n.pnp.*\n\n# Vim swap files\n*.swp\n\n# macOS files\n.DS_Store\n\n# lock files\npackage-lock.json\nyarn.lock\n\n# editor files\n.vscode\n.idea\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\nprofile-*\n\n.devcontainer\n.history"
  },
  {
    "path": ".npmignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\nnode_modules\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\n\nprofile-*\n\ndemo.gif\nserver.js\n*.png\nappveyor.yml\n.travis.yml\n.github\n.nyc_output\n"
  },
  {
    "path": ".taprc",
    "content": "ts: false\njsx: false\nflow: false\ntimeout: 900\nbranches: 60\nfunctions: 60\nlines: 60\nstatements: 60\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Matteo Collina\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": "![banner](autocannon-banner.png)\n\n# autocannon\n\n![Node.js CI](https://github.com/mcollina/autocannon/workflows/Node.js%20CI/badge.svg)\n\n![demo](https://raw.githubusercontent.com/mcollina/autocannon/master/demo.gif)\n\nAn HTTP/1.1 benchmarking tool written in node, greatly inspired by [wrk][wrk]\nand [wrk2][wrk2], with support for HTTP pipelining and HTTPS.\nOn _my_ box, *autocannon* can produce more load than `wrk` and `wrk2`, see [limitations](#limitations) for more details.\n\n* [Installation](#install)\n* [Usage](#usage)\n* [API](#api)\n* [Acknowledgements](#acknowledgements)\n* [License](#license)\n\n## Install\n\n```\nnpm i autocannon -g\n```\n\nor if you want to use the [API](#api) or as a dependency:\n\n```\nnpm i autocannon --save\n```\n\n## Usage\n\n### Command Line\n\n```\nUsage: autocannon [opts] URL\n\nURL is any valid HTTP or HTTPS URL.\nIf the PORT environment variable is set, the URL can be a path. In that case 'http://localhost:$PORT/path' will be used as the URL.\n\nAvailable options:\n\n  -c/--connections NUM\n        The number of concurrent connections to use. default: 10.\n  -p/--pipelining NUM\n        The number of pipelined requests to use. default: 1.\n  -d/--duration SEC\n        The number of seconds to run the autocannon. default: 10.\n  -a/--amount NUM\n        The number of requests to make before exiting the benchmark. If set, duration is ignored.\n  -L NUM\n        The number of milliseconds to elapse between taking samples. This controls the sample interval, & therefore the total number of samples, which affects statistical analyses. default: 1.\n  -S/--socketPath\n        A path to a Unix Domain Socket or a Windows Named Pipe. A URL is still required to send the correct Host header and path.\n  -w/--workers\n        Number of worker threads to use to fire requests.\n  -W/--warmup\n       Use a warm up interval before starting sampling.\n       This enables startup processes to finish and traffic to normalize before sampling begins\n       use -c and -d sub args e.g. `--warmup [ -c 1 -d 3 ]`\n  --on-port\n        Start the command listed after -- on the command line. When it starts listening on a port,\n        start sending requests to that port. A URL is still required to send requests to\n        the correct path. The hostname can be omitted, `localhost` will be used by default.\n        If the command after -- is `node <script>`, this flag is optional and assumed to be `true`.\n  -m/--method METHOD\n        The HTTP method to use. default: 'GET'.\n  -t/--timeout NUM\n        The number of seconds before timing out and resetting a connection. default: 10\n  -T/--title TITLE\n        The title to place in the results for identification.\n  -b/--body BODY\n        The body of the request.\n        NOTE: This option needs to be used with the '-H/--headers' option in some frameworks\n  -F/--form FORM\n        Upload a form (multipart/form-data). The form options can be a JSON string like\n        '{ \"field 1\": { \"type\": \"text\", \"value\": \"a text value\"}, \"field 2\": { \"type\": \"file\", \"path\": \"path to the file\" } }'\n        or a path to a JSON file containing the form options.\n        When uploading a file the default filename value can be overridden by using the corresponding option:\n        '{ \"field name\": { \"type\": \"file\", \"path\": \"path to the file\", \"options\": { \"filename\": \"myfilename\" } } }'\n        Passing the filepath to the form can be done by using the corresponding option:\n        '{ \"field name\": { \"type\": \"file\", \"path\": \"path to the file\", \"options\": { \"filepath\": \"/some/path/myfilename\" } } }'\n  -i/--input FILE\n        The body of the request. See '-b/body' for more details.\n  -H/--headers K=V\n        The request headers.\n  --har FILE\n        When provided, Autocannon will use requests from the HAR file.\n        CAUTION: you have to specify one or more domains using URL option: only the HAR requests to the same domains will be considered.\n        NOTE: you can still add extra headers with -H/--headers but -m/--method, -F/--form, -i/--input -b/--body will be ignored.\n  -B/--bailout NUM\n        The number of failures before initiating a bailout.\n  -M/--maxConnectionRequests NUM\n        The max number of requests to make per connection to the server.\n  -O/--maxOverallRequests NUM\n        The max number of requests to make overall to the server.\n  -r/--connectionRate NUM\n        The max number of requests to make per second from an individual connection.\n  -R/--overallRate NUM\n        The max number of requests to make per second from all connections.\n        connection rate will take precedence if both are set.\n        NOTE: if using rate limiting and a very large rate is entered which cannot be met, Autocannon will do as many requests as possible per second.\n        Also, latency data will be corrected to compensate for the effects of the coordinated omission issue.\n        If you are not familiar with the coordinated omission issue, you should probably read [this article](http://highscalability.com/blog/2015/10/5/your-load-generator-is-probably-lying-to-you-take-the-red-pi.html) or watch this [Gil Tene's talk](https://www.youtube.com/watch?v=lJ8ydIuPFeU) on the topic.\n  -C/--ignoreCoordinatedOmission\n        Ignore the coordinated omission issue when requests should be sent at a fixed rate using 'connectionRate' or 'overallRate'.\n        NOTE: it is not recommended to enable this option.\n        When the request rate cannot be met because the server is too slow, many request latencies might be missing and Autocannon might report a misleading latency distribution.\n  -D/--reconnectRate NUM\n        The number of requests to make before resetting a connections connection to the\n        server.\n  -n/--no-progress\n        Don't render the progress bar. default: false.\n  -l/--latency\n        Print all the latency data. default: false.\n  -I/--idReplacement\n        Enable replacement of `[<id>]` with a randomly generated ID within the request body. e.g. `/items/[<id>]`. default: false.\n  -j/--json\n        Print the output as newline delimited JSON. This will cause the progress bar and results not to be rendered. default: false.\n  -f/--forever\n        Run the benchmark forever. Efficiently restarts the benchmark on completion. default: false.\n  -s/--servername\n        Server name for the SNI (Server Name Indication) TLS extension. Defaults to the hostname of the URL when it is not an IP address.\n  -x/--excludeErrorStats\n        Exclude error statistics (non-2xx HTTP responses) from the final latency and bytes per second averages. default: false.\n  -E/--expectBody EXPECTED\n        Ensure the body matches this value. If enabled, mismatches count towards bailout.\n        Enabling this option will slow down the load testing.\n  --renderStatusCodes\n        Print status codes and their respective statistics.\n  --cert\n        Path to cert chain in pem format\n  --key\n        Path to private key for specified cert in pem format\n  --ca\n        Path to trusted ca certificates for the test. This argument accepts both a single file as well as a list of files\n  --debug\n        Print connection errors to stderr.\n  -v/--version\n        Print the version number.\n  -V/--verbose\n        Print the table with results. default: true.\n  -h/--help\n        Print this menu.\n```\n\nautocannon outputs data in tables like this:\n\n```\nRunning 10s test @ http://localhost:3000\n10 connections\n\n┌─────────┬──────┬──────┬───────┬──────┬─────────┬─────────┬──────────┐\n│ Stat    │ 2.5% │ 50%  │ 97.5% │ 99%  │ Avg     │ Stdev   │ Max      │\n├─────────┼──────┼──────┼───────┼──────┼─────────┼─────────┼──────────┤\n│ Latency │ 0 ms │ 0 ms │ 0 ms  │ 1 ms │ 0.02 ms │ 0.16 ms │ 16.45 ms │\n└─────────┴──────┴──────┴───────┴──────┴─────────┴─────────┴──────────┘\n┌───────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┬─────────┐\n│ Stat      │ 1%      │ 2.5%    │ 50%     │ 97.5%   │ Avg     │ Stdev   │ Min     │\n├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ Req/Sec   │ 20623   │ 20623   │ 25583   │ 26271   │ 25131.2 │ 1540.94 │ 20615   │\n├───────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┼─────────┤\n│ Bytes/Sec │ 2.29 MB │ 2.29 MB │ 2.84 MB │ 2.92 MB │ 2.79 MB │ 171 kB  │ 2.29 MB │\n└───────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┴─────────┘\n\nReq/Bytes counts sampled once per second.\n\n251k requests in 10.05s, 27.9 MB read\n```\n\nThere are two tables: one for the request latency, and one for the request volume.\n\nThe latency table lists the request times at the 2.5% percentile, the fast outliers; at 50%, the median; at 97.5%, the slow outliers; at 99%, the very slowest outliers. Here, lower means faster.\n\nThe request volume table lists the number of requests sent and the number of bytes downloaded. These values are sampled once per second. Higher values mean more requests were processed. In the above example, 2.29 MB was downloaded in 1 second in the worst case (slowest 1%). Since we only ran for 10 seconds, there are just 10 samples, the Min value and the 1% and 2.5% percentiles are all the same sample. With longer durations these numbers will differ more.\n\nWhen passing the `-l` flag, a third table lists all the latency percentiles recorded by autocannon:\n\n```\n┌────────────┬──────────────┐\n│ Percentile │ Latency (ms) │\n├────────────┼──────────────┤\n│ 0.001      │ 0            │\n├────────────┼──────────────┤\n│ 0.01       │ 0            │\n├────────────┼──────────────┤\n│ 0.1        │ 0            │\n├────────────┼──────────────┤\n│ 1          │ 0            │\n├────────────┼──────────────┤\n│ 2.5        │ 0            │\n├────────────┼──────────────┤\n│ 10         │ 0            │\n├────────────┼──────────────┤\n│ 25         │ 0            │\n├────────────┼──────────────┤\n│ 50         │ 0            │\n├────────────┼──────────────┤\n│ 75         │ 0            │\n├────────────┼──────────────┤\n│ 90         │ 0            │\n├────────────┼──────────────┤\n│ 97.5       │ 0            │\n├────────────┼──────────────┤\n│ 99         │ 1            │\n├────────────┼──────────────┤\n│ 99.9       │ 1            │\n├────────────┼──────────────┤\n│ 99.99      │ 3            │\n├────────────┼──────────────┤\n│ 99.999     │ 15           │\n└────────────┴──────────────┘\n```\n\nThis can give some more insight if a lot (millions) of requests were sent.\n\n### Programmatically\n\n```js\n'use strict'\n\nconst autocannon = require('autocannon')\n\nautocannon({\n  url: 'http://localhost:3000',\n  connections: 10, //default\n  pipelining: 1, // default\n  duration: 10 // default\n}, console.log)\n\n// async/await\nasync function foo () {\n  const result = await autocannon({\n    url: 'http://localhost:3000',\n    connections: 10, //default\n    pipelining: 1, // default\n    duration: 10 // default\n  })\n  console.log(result)\n}\n\n```\n\n<a name=\"workers\"></a>\n#### Workers\n\nIn workers mode, `autocannon` uses instances of Node's [Worker](https://nodejs.org/dist/latest/docs/api/worker_threads.html#new-workerfilename-options) class to execute the load tests in multiple threads.\n\nThe `amount` and `connections` parameters are divided amongst the workers. If either parameter is not integer divisible by the number of `workers`, the per-worker value is rounded to the lowest integer, or set to `1`, whichever is the higher. All other parameters are applied per-worker as if the test were single-threaded.\n\n**NOTE:** Unlike `amount` and `connections`, the \"overall\" parameters, `maxOverallRequests` and `overallRate`, are applied **_per worker_**. For example, if you set `connections` to `4`, `workers` to `2` and `maxOverallRequests` to `10`, each worker will receive `2` connections and a `maxOverallRequests` of `10`, resulting in `20` requests being sent.\n\n```js\n'use strict'\n\nconst autocannon = require('autocannon')\n\nautocannon({\n  url: 'http://localhost:3000',\n  connections: 10, //default\n  pipelining: 1, // default\n  duration: 10, // default\n  workers: 4\n}, console.log)\n```\n\n**NOTE:** When in workers mode, you need to pass in an absolute file path to all the options that accept a `function`. This is because a function passed into the main process can not be cloned and passed to the worker. So instead, it needs a file that it can `require`. The options with this behaviour are shown in the below example\n\n```js\n'use strict'\n\nconst autocannon = require('autocannon')\n\nautocannon({\n  // ...\n  workers: 4,\n  setupClient: '/full/path/to/setup-client.js',\n  verifyBody: '/full/path/to/verify-body.js'\n  requests: [\n    {\n      // ...\n      onResponse: '/full/path/to/on-response.js'\n    },\n    {\n      // ...\n      setupRequest: '/full/path/to/setup-request.js'\n    }\n  ]\n}, console.log)\n```\n\n## API\n\n### autocannon(opts[, cb])\n\nStart autocannon against the given target.\n\n* `opts`: Configuration options for the autocannon instance. This can have the following attributes. _REQUIRED_.\n    * `url`: The given target. Can be HTTP or HTTPS. More than one URL is allowed, but it is recommended that the number of connections is an integer multiple of the URL. _REQUIRED_.\n    * `socketPath`: A path to a Unix Domain Socket or a Windows Named Pipe. A `url` is still required to send the correct Host header and path. _OPTIONAL_.\n    * `workers`: Number of worker threads to use to fire requests.\n    * `connections`: The number of concurrent connections. _OPTIONAL_ default: `10`.\n    * `duration`: The number of seconds to run the autocannon. Can be a [timestring](https://www.npmjs.com/package/timestring). _OPTIONAL_ default: `10`.\n    * `amount`: A `Number` stating the number of requests to make before ending the test. This overrides duration and takes precedence, so the test won't end until the number of requests needed to be completed is completed. _OPTIONAL_.\n    * `sampleInt`: The number of milliseconds to elapse between taking samples. This controls the sample interval, & therefore the total number of samples, which affects statistical analyses. default: 1.\n    * `timeout`: The number of seconds to wait for a response before. _OPTIONAL_ default: `10`.\n    * `pipelining`: The number of [pipelined requests](https://en.wikipedia.org/wiki/HTTP_pipelining) for each connection. Will cause the `Client` API to throw when greater than 1. _OPTIONAL_ default: `1`.\n    * `bailout`: The threshold of the number of errors when making the requests to the server before this instance bail's out. This instance will take all existing results so far and aggregate them into the results. If none passed here, the instance will ignore errors and never bail out. _OPTIONAL_ default: `undefined`.\n    * `method`: The HTTP method to use. _OPTIONAL_ `default: 'GET'`.\n    * `title`: A `String` to be added to the results for identification. _OPTIONAL_ default: `undefined`.\n    * `body`: A `String` or a `Buffer` containing the body of the request. Insert one or more randomly generated IDs into the body by including `[<id>]` where the randomly generated ID should be inserted (Must also set idReplacement to true). This can be useful in soak testing POST endpoints where one or more fields must be unique. Leave undefined for an empty body. _OPTIONAL_ default: `undefined`.\n    * `form`: A `String` or an `Object` containing the multipart/form-data options or a path to the JSON file containing them\n    * `headers`: An `Object` containing the headers of the request. _OPTIONAL_ default: `{}`.\n    * `initialContext`: An object that you'd like to initialize your context with. Check out [an example of initializing context](./samples/init-context.js). _OPTIONAL_\n    * `setupClient`: A `Function` which will be passed the `Client` object for each connection to be made. This can be used to customise each individual connection headers and body using the API shown below. The changes you make to the client in this function will take precedence over the default `body` and `headers` you pass in here. There is an example of this in the samples folder. _OPTIONAL_ default: `function noop () {}`. When using `workers`, you need to supply a file path that default exports a function instead (Check out the [workers](#workers) section for more details).\n    * `verifyBody`: A `Function` which will be passed the response body for each completed request. Each request, whose `verifyBody` function does not return a truthy value, is counted in `mismatches`. This function will take precedence over the `expectBody`. There is an example of this in the samples folder. When using `workers`, you need to supply a file path that default exports a function (Check out the [workers](#workers) section for more details).\n    * `maxConnectionRequests`: A `Number` stating the max requests to make per connection. `amount` takes precedence if both are set. _OPTIONAL_\n    * `maxOverallRequests`: A `Number` stating the max requests to make overall. Can't be less than `connections`. `maxConnectionRequests` takes precedence if both are set. _OPTIONAL_\n    * `connectionRate`: A `Number` stating the rate of requests to make per second from each individual connection. No rate limiting by default. _OPTIONAL_\n    * `overallRate`: A `Number` stating the rate of requests to make per second from all connections. `connectionRate` takes precedence if both are set. No rate limiting by default. _OPTIONAL_\n    * `ignoreCoordinatedOmission`: A `Boolean` which disables the correction of latencies to compensate for the coordinated omission issue. Does not make sense when no rate of requests has been specified (`connectionRate` or `overallRate`). _OPTIONAL_ default: `false`.\n    * `reconnectRate`: A `Number` that makes the individual connections disconnect and reconnect to the server whenever it has sent that number of requests. _OPTIONAL_\n    * `requests`: An `Array` of `Object`s which represents the sequence of requests to make while benchmarking. Can be used in conjunction with the `body`, `headers` and `method` params above. Check the samples folder for an example of how this might be used. _OPTIONAL_. Contained objects can have these attributes:\n       * `body`: When present, will override `opts.body`. _OPTIONAL_\n       * `headers`: When present, will override `opts.headers`. _OPTIONAL_\n       * `method`: When present, will override `opts.method`. _OPTIONAL_\n       * `path`: When present, will override `opts.path`. _OPTIONAL_\n       * `setupRequest`: A `Function` you may provide to mutate the raw `request` object, e.g. `request.method = 'GET'`. It takes `request` (Object) and `context` (Object) parameters, and must return the modified request. When it returns a falsey value, autocannon will restart from first request. When using `workers`, you need to supply a file path that default exports a function instead (Check out [workers](#workers) section for more details) _OPTIONAL_\n       * `onResponse`: A `Function` you may provide to process the received response. It takes `status` (Number), `body` (String) `context` (Object) parameters and `headers` (Key-Value Object). When using `workers`, you need to supply a file path that default exports a function instead (Check out [workers](#workers) section for more details) _OPTIONAL_\n    * `har`: an `Object` of parsed [HAR](https://w3c.github.io/web-performance/specs/HAR/Overview.html) content. Autocannon will extra and use `entries.request`: `requests`, `method`, `form` and `body` options will be ignored. _NOTE_: you must ensure that entries are targeting the same domain as `url` option. _OPTIONAL_\n    * `idReplacement`: A `Boolean` which enables the replacement of `[<id>]` tags within the request body with a randomly generated ID, allowing for unique fields to be sent with requests. Check out [an example of programmatic usage](./samples/using-id-replacement.js) that can be found in the samples. _OPTIONAL_ default: `false`\n    * `forever`: A `Boolean` which allows you to setup an instance of autocannon that restarts indefinitely after emitting results with the `done` event. Useful for efficiently restarting your instance. To stop running forever, you must cause a `SIGINT` or call the `.stop()` function on your instance. _OPTIONAL_ default: `false`\n    * `servername`: A `String` identifying the server name for the SNI (Server Name Indication) TLS extension. _OPTIONAL_ default: Defaults to the hostname of the URL when it is not an IP address.\n    * `excludeErrorStats`: A `Boolean` which allows you to disable tracking non-2xx code responses in latency and bytes per second calculations. _OPTIONAL_ default: `false`.\n    * `expectBody`: A `String` representing the expected response body. Each request whose response body is not equal to `expectBody`is counted in `mismatches`. If enabled, mismatches count towards bailout. _OPTIONAL_\n    * `tlsOptions`: An `Object` that is passed into `tls.connect` call ([Full list of options](https://nodejs.org/api/tls.html#tls_tls_connect_port_host_options_callback)). Note: this only applies if your URL is secure.\n    * `skipAggregateResult`: A `Boolean` which allows you to disable the aggregate result phase of an instance run. See [autocannon.aggregateResult](<#autocannon.aggregateResult(results[, opts])>)\n* `cb`: The callback which is called on completion of a benchmark. Takes the following params. _OPTIONAL_.\n    * `err`: If there was an error encountered with the run.\n    * `results`: The results of the run.\n\n**Returns** an instance/event emitter for tracking progress, etc. If `cb` is omitted, the return value can also be used as a Promise.\n\n### Customizing sent requests\n\nWhen running, autocannon will create as many `Client` objects as desired connections. They will run in parallel until the benchmark is over (duration or total number of requests).\nEach client will loop over the `requests` array, would it contain one or several requests.\n\nWhile going through available requests, the client will maintain a `context`: an object you can use in `onResponse` and `setupRequest` functions, to store and read some contextual data.\nPlease check the `request-context.js` file in samples.\n\nNote that `context` object will be reset to `initialContext` (or `{}` it is not provided) when restarting to the first available request, ensuring similar runs.\n\n### Combining connections, overallRate and amount\n\nWhen combining a fixed `amount` of requests with concurrent `connections` and an `overallRate` limit, autocannon will distribute the requests and the intended rate over all connections. If the `overallRate` is not integer divisible, autocannon will configure some connection clients with a higher and some with a lower number of requests/second rate. If now the `amount` *is* integer divisible, all connection clients get the same number of requests. This means that the clients with a higher request rate will finish earlier, than the others, leading to a drop in the perceived request rate.\n\nExample: `connections = 10, overallRate = 17, amount = 5000`\n\n\n### autocannon.track(instance[, opts])\n\nTrack the progress of your autocannon, programmatically.\n\n* `instance`: The instance of autocannon. _REQUIRED_.\n* `opts`: Configuration options for tracking. This can have the following attributes. _OPTIONAL_.\n    * `outputStream`: The stream to output to. default: `process.stderr`.\n    * `renderProgressBar`: A truthy value to enable the rendering of the progress bar. default: `true`.\n    * `renderResultsTable`: A truthy value to enable the rendering of the results table. default: `true`.\n    * `renderLatencyTable`: A truthy value to enable the rendering of the advanced latency table. default: `false`.\n    * `progressBarString`: A `string` defining the format of the progress display output. Must be valid input for the [progress bar module](http://npm.im/progress). default: `'running [:bar] :percent'`.\n\nExample that just prints the table of results on completion:\n\n```js\n'use strict'\n\nconst autocannon = require('autocannon')\n\nconst instance = autocannon({\n  url: 'http://localhost:3000'\n}, console.log)\n\n// this is used to kill the instance on CTRL-C\nprocess.once('SIGINT', () => {\n  instance.stop()\n})\n\n// just render results\nautocannon.track(instance, {renderProgressBar: false})\n```\n\nCheck out [this example](./samples/track-run.js) to see it in use, as well.\n\n### autocannon.printResult(resultObject[, opts])\n\nReturns a text string containing the result tables.\n\n* `resultObject`: The result object of autocannon. _REQUIRED_.\n* `opts`: Configuration options for generating the tables. These may include the following attributes. _OPTIONAL_.\n    * `outputStream`: The stream to which output is directed. It is primarily used to check if the terminal supports color. default: `process.stderr`.\n    * `renderResultsTable`: A truthy value to enable the creation of the results table. default: `true`.\n    * `renderLatencyTable`: A truthy value to enable the creation of the latency table. default: `false`.\n\nExample:\n\n```js\n\"use strict\";\n\nconst { stdout } = require(\"node:process\");\nconst autocannon = require(\"autocannon\");\n\nfunction print(result) {\n  stdout.write(autocannon.printResult(result));\n}\n\nautocannon({ url: \"http://localhost:3000\" }, (err, result) => print(result));\n```\n\n### autocannon.aggregateResult(results[, opts])\n\nAggregate the results of one or more autocannon instance runs, where the instances of autocannon have been run with the `skipAggregateResult` option.\n\nThis is an advanced use case, where you might be running a load test using autocannon across multiple machines and therefore need to defer aggregating the results to a later time.\n\n* `results`: An array of autocannon instance results, where the instances have been run with the `skipAggregateResult` option set to true. _REQUIRED_.\n* `opts`: This is a subset of the options you would pass to the main autocannon API, so you could use the same options object as the one used to run the instances. See [autocannon](<#autocannon(opts[, cb])>) for full descriptions of the options. _REQUIRED_.\n    * `url`: _REQUIRED_\n    * `title`: _OPTIONAL_ default: `undefined`\n    * `socketPath`: _OPTIONAL_\n    * `connections`: _OPTIONAL_ default: `10`.\n    * `sampleInt`: _OPTIONAL_ default: `1`\n    * `pipelining`: _OPTIONAL_ default: `1`\n    * `workers`: _OPTIONAL_ default: `undefined`\n\n### Autocannon events\n\nBecause an autocannon instance is an `EventEmitter`, it emits several events. these are below:\n\n* `start`: Emitted once everything has been setup in your autocannon instance and it has started. Useful for if running the instance forever.\n* `tick`: Emitted every second this autocannon is running a benchmark. Useful for displaying stats, etc. Used by the `track` function. The `tick` event propagates  an object containing the `counter` and `bytes` values, which can be used for extended reports.\n* `done`: Emitted when the autocannon finishes a benchmark. passes the `results` as an argument to the callback.\n* `response`: Emitted when the autocannons http-client gets an HTTP response from the server. This passes the following arguments to the callback:\n    * `client`: The `http-client` itself. Can be used to modify the headers and body the client will send to the server. API below.\n    * `statusCode`: The HTTP status code of the response.\n    * `resBytes`: The response byte length.\n    * `responseTime`: The time taken to get a response after initiating the request.\n* `reqError`: Emitted in the case of a request error e.g. a timeout.\n* `error`: Emitted if there is an error during the setup phase of autocannon.\n\n### Results\n\nThe results object emitted by `done` and passed to the `autocannon()` callback has these properties:\n\n* `title`: Value of the `title` option passed to `autocannon()`.\n* `url`: The URL that was targeted.\n* `socketPath`: The UNIX Domain Socket or Windows Named Pipe that was targeted, or `undefined`.\n* `requests`: A histogram object containing statistics about the number of requests that were sent per second.\n* `latency`: A histogram object containing statistics about response latency.\n* `throughput`: A histogram object containing statistics about the response data throughput per second.\n* `duration`: The amount of time the test took, **in seconds**.\n* `errors`: The number of connection errors (including timeouts) that occurred.\n* `timeouts`: The number of connection timeouts that occurred.\n* `mismatches`: The number of requests with a mismatched body.\n* `start`: A Date object representing when the test started.\n* `finish`: A Date object representing when the test ended.\n* `connections`: The amount of connections used (value of `opts.connections`).\n* `pipelining`: The number of pipelined requests used per connection (value of `opts.pipelining`).\n* `non2xx`: The number of non-2xx response status codes received.\n* `resets`: How many times the requests pipeline was reset due to `setupRequest` returning a falsey value.\n* `statusCodeStats`: Requests counter per status code (e.g. `{ \"200\": { \"count\": \"500\" } }`)\n\nThe histogram objects for `requests`, `latency` and `throughput` are [hdr-histogram-percentiles-obj](https://github.com/thekemkid/hdr-histogram-percentiles-obj) objects and have this shape:\n\n* `min`: The lowest value for this statistic.\n* `max`: The highest value for this statistic.\n* `average`: The average (mean) value.\n* `stddev`: The standard deviation.\n* `p*`: The XXth percentile value for this statistic. The percentile properties are: `p2_5`, `p50`, `p75`, `p90`, `p97_5`, `p99`, `p99_9`, `p99_99`, `p99_999`.\n\n### `Client` API\n\nThis object is passed as the first parameter of both the `setupClient` function and the `response` event from an autocannon instance. You can use this to modify the requests you are sending while benchmarking. This is also an `EventEmitter`, with the events and their params listed below.\n\n* `client.setHeaders(headers)`: Used to modify the headers of the request this client iterator is currently on. `headers` should be an `Object`, or `undefined` if you want to remove your headers.\n* `client.setBody(body)`: Used to modify the body of the request this client iterator is currently on. `body` should be a `String` or `Buffer`, or `undefined` if you want to remove the body.\n* `client.setHeadersAndBody(headers, body)`: Used to modify both the headers and body this client iterator is currently on. `headers` and `body` should take the same form as above.\n* `client.setRequest(request)`: Used to modify the entire request that this client iterator is currently on. Can have `headers`, `body`, `method`, or `path` as attributes. Defaults to the values passed into the autocannon instance when it was created. `Note: call this when modifying multiple request values for faster encoding`\n* `client.setRequests(newRequests)`: Used to overwrite the entire requests array that was passed into the instance on initiation. `Note: call this when modifying multiple requests for faster encoding`\n\n### `Client` events\n\nThe events a `Client` can emit are listed here:\n\n* `headers`: Emitted when a request sent from this client has received the headers of its reply. This received an `Object` as the parameter.\n* `body`: Emitted when a request sent from this client has received the body of a reply. This receives a `Buffer` as the parameter.\n* `response`: Emitted when the client has received a completed response for a request it made. This is passed the following arguments:\n    * `statusCode`: The HTTP status code of the response.\n    * `resBytes`: The response byte length.\n    * `responseTime`: The time taken to get a response after initiating the request.\n* `reset`: Emitted when the requests pipeline was reset due to `setupRequest` returning a falsey value.\n\nExample using the autocannon events and the client API and events:\n\n```js\n'use strict'\n\nconst autocannon = require('autocannon')\n\nconst instance = autocannon({\n  url: 'http://localhost:3000',\n  setupClient: setupClient\n}, (err, result) => handleResults(result))\n// results passed to the callback are the same as those emitted from the done events\ninstance.on('done', handleResults)\n\ninstance.on('tick', () => console.log('ticking'))\n\ninstance.on('response', handleResponse)\n\nfunction setupClient (client) {\n  client.on('body', console.log) // console.log a response body when its received\n}\n\nfunction handleResponse (client, statusCode, resBytes, responseTime) {\n  console.log(`Got response with code ${statusCode} in ${responseTime} milliseconds`)\n  console.log(`response: ${resBytes.toString()}`)\n\n  //update the body or headers\n  client.setHeaders({new: 'header'})\n  client.setBody('new body')\n  client.setHeadersAndBody({new: 'header'}, 'new body')\n}\n\nfunction handleResults(result) {\n  // ...\n}\n```\n\n<a name=\"limitations\"></a>\n## Limitations\n\nAutocannon is written in JavaScript for the Node.js runtime and it is CPU-bound.\nWe have verified that it yields comparable results with `wrk` when benchmarking Node.js\napplications using the `http` module.\nNevertheless, it uses significantly more CPU than other tools that compiles to a binary such as `wrk`.\nAutocannon can saturate the CPU, e.g. the autocannon process reaches 100%: in those cases,\nwe recommend using `wrk2`.\n\nAs an example, let's consider a run with 1000 connections on a server\nwith 4 cores with hyperthreading:\n\n* `wrk` uses 2 threads (by default) and an auxiliary one to collect the\n  metrics with a total load of the CPU of 20% + 20% + 40%.\n* `autocannon` uses a single thread at 80% CPU load.\n\nBoth saturates a Node.js process at around 41k req/sec, however,\n`autocannon` can saturate sooner because it is single-threaded.\n\nNote that `wrk` does not support HTTP/1.1 pipelining. As a result, `autocannon` can create\nmore load on the server than wrk for each open connection.\n\n<a name=\"acknowledgements\"></a>\n## Acknowledgements\n\nThis project was kindly sponsored by [nearForm](http://nearform.com).\n\nLogo and identity designed by Cosmic Fox Design: https://www.behance.net/cosmicfox.\n\n[wrk][wrk] and [wrk2][wrk2] provided great inspiration.\n\n### Chat on Gitter\n\nIf you are using autocannon or you have any questions, let us know: [Gitter](https://gitter.im/mcollina/autocannon)\n\n### Contributors\n\n- [Glen Keane](mailto:glenkeane.94@gmail.com) | [Github](https://github.com/GlenTiki)\n- [Salman Mitha](mailto:salmanmitha@gmail.com) | [Github](https://github.com/salmanm) | [NPM](https://www.npmjs.com/~salmanm)\n\n## License\n\nCopyright [Matteo Collina](https://github.com/mcollina) and other contributors, Licensed under [MIT](./LICENSE).\n\n[node-gyp]: https://github.com/nodejs/node-gyp#installation\n[wrk]: https://github.com/wg/wrk\n[wrk2]: https://github.com/giltene/wrk2\n"
  },
  {
    "path": "autocannon.js",
    "content": "#! /usr/bin/env node\n\n'use strict'\n\nconst crossArgv = require('cross-argv')\nconst fs = require('fs')\nconst os = require('os')\nconst net = require('net')\nconst path = require('path')\nconst URL = require('url').URL\nconst spawn = require('child_process').spawn\nconst managePath = require('manage-path')\nconst hasAsyncHooks = require('has-async-hooks')\nconst subarg = require('@minimistjs/subarg')\nconst printResult = require('./lib/printResult')\nconst initJob = require('./lib/init')\nconst track = require('./lib/progressTracker')\nconst generateSubArgAliases = require('./lib/subargAliases')\nconst { checkURL, ofURL } = require('./lib/url')\nconst { parseHAR } = require('./lib/parseHAR')\nconst _aggregateResult = require('./lib/aggregateResult')\nconst validateOpts = require('./lib/validate')\n\nif (typeof URL !== 'function') {\n  console.error('autocannon requires the WHATWG URL API, but it is not available. Please upgrade to Node 6.13+.')\n  process.exit(1)\n}\n\nmodule.exports = initJob\nmodule.exports.track = track\n\nmodule.exports.start = start\nmodule.exports.printResult = printResult\nmodule.exports.parseArguments = parseArguments\nmodule.exports.aggregateResult = function aggregateResult (results, opts = {}) {\n  if (!Array.isArray(results)) {\n    throw new Error('\"results\" must be an array of results')\n  }\n\n  opts = validateOpts(opts, false)\n\n  if (opts instanceof Error) {\n    throw opts\n  }\n\n  return _aggregateResult(results, opts)\n}\nconst alias = {\n  connections: 'c',\n  pipelining: 'p',\n  timeout: 't',\n  duration: 'd',\n  sampleInt: 'L',\n  amount: 'a',\n  json: 'j',\n  renderLatencyTable: ['l', 'latency'],\n  onPort: 'on-port',\n  method: 'm',\n  headers: ['H', 'header'],\n  body: 'b',\n  form: 'F',\n  servername: 's',\n  bailout: 'B',\n  input: 'i',\n  maxConnectionRequests: 'M',\n  maxOverallRequests: 'O',\n  connectionRate: 'r',\n  overallRate: 'R',\n  ignoreCoordinatedOmission: 'C',\n  reconnectRate: 'D',\n  renderProgressBar: 'progress',\n  renderStatusCodes: 'statusCodes',\n  title: 'T',\n  verbose: 'V',\n  version: 'v',\n  forever: 'f',\n  idReplacement: 'I',\n  socketPath: 'S',\n  excludeErrorStats: 'x',\n  expectBody: 'E',\n  workers: 'w',\n  warmup: 'W',\n  help: 'h'\n}\n\nconst defaults = {\n  connections: 10,\n  timeout: 10,\n  pipelining: 1,\n  duration: 10,\n  sampleInt: 1000,\n  reconnectRate: 0,\n  renderLatencyTable: false,\n  renderProgressBar: true,\n  renderStatusCodes: false,\n  json: false,\n  forever: false,\n  method: 'GET',\n  idReplacement: false,\n  excludeErrorStats: false,\n  debug: false,\n  workers: 0,\n  verbose: true\n}\n\nfunction parseArguments (argvs) {\n  let argv = subarg(argvs, {\n    boolean: ['json', 'n', 'help', 'renderLatencyTable', 'renderProgressBar', 'renderStatusCodes', 'forever', 'idReplacement', 'excludeErrorStats', 'onPort', 'debug', 'ignoreCoordinatedOmission', 'verbose'],\n    alias,\n    default: defaults,\n    '--': true\n  })\n  // subarg does not convert aliases in sub arguments\n  argv = generateSubArgAliases(argv)\n\n  argv.url = argv._.length > 1 ? argv._ : argv._[0]\n\n  // Assume onPort if `-- node` is provided\n  if (argv['--'][0] === 'node') {\n    argv.onPort = true\n  }\n\n  if (argv.onPort) {\n    argv.spawn = argv['--']\n  }\n\n  // support -n to disable the progress bar and results table\n  if (argv.n) {\n    argv.renderProgressBar = false\n    argv.renderResultsTable = false\n    argv.renderStatusCodes = false\n  }\n\n  if (argv.version) {\n    console.log('autocannon', 'v' + require('./package').version)\n    console.log('node', process.version)\n    return\n  }\n\n  if (!checkURL(argv.url) || argv.help) {\n    const help = fs.readFileSync(path.join(__dirname, 'help.txt'), 'utf8')\n    console.error(help)\n    return\n  }\n\n  // if PORT is set (like by `0x`), target `localhost:PORT/path` by default.\n  // this allows doing:\n  //     0x --on-port 'autocannon /path' -- node server.js\n  if (process.env.PORT) {\n    argv.url = ofURL(argv.url).map(url => new URL(url, `http://localhost:${process.env.PORT}`).href)\n  }\n  // Add http:// if it's not there and this is not a /path\n  argv.url = ofURL(argv.url).map(url => {\n    if (url.indexOf('http') !== 0 && url[0] !== '/') {\n      url = `http://${url}`\n    }\n    return url\n  })\n\n  // check that the URL is valid.\n  ofURL(argv.url).map(url => {\n    try {\n      // If --on-port is given, it's acceptable to not have a hostname\n      if (argv.onPort) {\n        new URL(url, 'http://localhost') // eslint-disable-line no-new\n      } else {\n        new URL(url) // eslint-disable-line no-new\n      }\n    } catch (err) {\n      console.error(err.message)\n      console.error('')\n      console.error('When targeting a path without a hostname, the PORT environment variable must be available.')\n      console.error('Use a full URL or set the PORT variable.')\n      process.exit(1)\n    }\n\n    return null // to make linter happy\n  })\n\n  if (argv.input) {\n    argv.body = fs.readFileSync(argv.input, 'utf8')\n  }\n\n  if (argv.headers) {\n    if (!Array.isArray(argv.headers)) {\n      argv.headers = [argv.headers]\n    }\n\n    argv.headers = argv.headers.reduce((obj, header) => {\n      const colonIndex = header.indexOf(':')\n      const equalIndex = header.indexOf('=')\n      const index = Math.min(colonIndex < 0 ? Infinity : colonIndex, equalIndex < 0 ? Infinity : equalIndex)\n      if (Number.isFinite(index) && index > 0) {\n        obj[header.slice(0, index)] = header.slice(index + 1)\n        return obj\n      } else throw new Error(`An HTTP header was not correctly formatted: ${header}`)\n    }, {})\n  }\n\n  if (argv.har) {\n    try {\n      argv.har = JSON.parse(fs.readFileSync(argv.har))\n      // warn users about skipped HAR requests\n      const requestsByOrigin = parseHAR(argv.har)\n      const allowed = ofURL(argv.url, true).map(url => new URL(url).origin)\n      for (const [origin] of requestsByOrigin) {\n        if (!allowed.includes(origin)) {\n          console.error(`Warning: skipping requests to '${origin}' as the target is ${allowed.join(', ')}`)\n        }\n      }\n    } catch (err) {\n      throw new Error(`Failed to load HAR file content: ${err.message}`)\n    }\n  }\n\n  argv.tlsOptions = {}\n\n  if (argv.cert) {\n    try {\n      argv.tlsOptions.cert = fs.readFileSync(argv.cert)\n    } catch (err) {\n      throw new Error(`Failed to load cert file: ${err.message}`)\n    }\n  }\n\n  if (argv.key) {\n    try {\n      argv.tlsOptions.key = fs.readFileSync(argv.key)\n    } catch (err) {\n      throw new Error(`Failed to load key file: ${err.message}`)\n    }\n  }\n\n  if (argv.ca) {\n    if (typeof argv.ca === 'string') {\n      argv.ca = [argv.ca]\n    } else if (Array.isArray(argv.ca._)) {\n      argv.ca = argv.ca._\n    }\n\n    try {\n      argv.tlsOptions.ca = argv.ca.map(caPath => fs.readFileSync(caPath))\n    } catch (err) {\n      throw new Error(`Failed to load ca file: ${err.message}`)\n    }\n  }\n\n  // This is to distinguish down the line whether it is\n  // run via command-line or programmatically\n  argv[Symbol.for('internal')] = true\n\n  return argv\n}\n\nfunction start (argv) {\n  if (!argv) {\n    // we are printing the help\n    return\n  }\n\n  if (argv.onPort) {\n    if (!hasAsyncHooks()) {\n      console.error('The --on-port flag requires the async_hooks builtin module, but it is not available. Please upgrade to Node 8.1+.')\n      process.exit(1)\n    }\n\n    const { socketPath, server } = createChannel((port) => {\n      const url = new URL(argv.url, `http://localhost:${port}`).href\n      const opts = Object.assign({}, argv, {\n        onPort: false,\n        url\n      })\n      const tracker = initJob(opts, () => {\n        proc.kill('SIGINT')\n        server.close()\n      })\n\n      process.once('SIGINT', () => {\n        tracker.stop()\n      })\n    })\n\n    // manage-path always uses the $PATH variable, but we can pretend\n    // that it is equal to $NODE_PATH\n    const alterPath = managePath({ PATH: process.env.NODE_PATH })\n    alterPath.unshift(path.join(__dirname, 'lib/preload'))\n\n    const proc = spawn(argv.spawn[0], argv.spawn.slice(1), {\n      stdio: ['ignore', 'inherit', 'inherit'],\n      env: Object.assign({}, process.env, {\n        NODE_OPTIONS: ['-r', 'autocannonDetectPort'].join(' ') +\n          (process.env.NODE_OPTIONS ? ` ${process.env.NODE_OPTIONS}` : ''),\n        NODE_PATH: alterPath.get(),\n        AUTOCANNON_SOCKET: socketPath\n      })\n    })\n  } else {\n    // if forever is true then a promise is not returned and we need to try ... catch errors\n    try {\n      const tracker = initJob(argv)\n      if (tracker.then) {\n        tracker.catch((err) => {\n          console.error(err.message)\n        })\n      }\n    } catch (err) {\n      console.error(err.message)\n    }\n  }\n}\n\nfunction createChannel (onport) {\n  const pipeName = `${process.pid}.autocannon`\n  const socketPath = process.platform === 'win32'\n    ? `\\\\\\\\?\\\\pipe\\\\${pipeName}`\n    : path.join(os.tmpdir(), pipeName)\n  const server = net.createServer((socket) => {\n    socket.once('data', (chunk) => {\n      const port = chunk.toString()\n      onport(port)\n    })\n  })\n  server.listen(socketPath)\n  server.on('close', () => {\n    try {\n      fs.unlinkSync(socketPath)\n    } catch (err) {}\n  })\n\n  return { socketPath, server }\n}\n\nif (require.main === module) {\n  const argv = crossArgv(process.argv.slice(2))\n  start(parseArguments(argv))\n}\n"
  },
  {
    "path": "cluster.js",
    "content": "'use strict'\n\nconst cluster = require('cluster')\nconst http = require('http')\nconst numCPUs = Math.floor(require('os').cpus().length / 2) || 1\n\nif (cluster.isMaster) {\n  console.log(`Master ${process.pid} is running`)\n\n  for (let i = 0; i < numCPUs; i++) {\n    cluster.fork()\n  }\n\n  cluster.on('exit', (worker, code, signal) => {\n    console.log(`worker ${worker.process.pid} died`)\n  })\n} else {\n  // Workers can share any TCP connection\n  // In this case it is an HTTP server\n  http.createServer((req, res) => {\n    res.writeHead(200)\n    res.end('hello world\\n')\n  }).listen(3000)\n\n  console.log(`Worker ${process.pid} started`)\n}\n"
  },
  {
    "path": "for-zero-x.js",
    "content": "'use strict'\n\nconst autocannon = require('.')\n\nautocannon({\n  url: 'http://localhost:3000',\n  connections: 10,\n  duration: 10\n}, console.log)\n"
  },
  {
    "path": "help.txt",
    "content": "Usage: autocannon [opts] URL\n\nURL is any valid HTTP or HTTPS URL.\nIf the PORT environment variable is set, the URL can be a path. In that case 'http://localhost:$PORT/path' will be used as the URL.\n\nAvailable options:\n\n  -c/--connections NUM\n        The number of concurrent connections to use. default: 10.\n  -p/--pipelining NUM\n        The number of pipelined requests to use. default: 1.\n  -d/--duration SEC\n        The number of seconds to run the autocannon. default: 10.\n  -a/--amount NUM\n        The number of requests to make before exiting the benchmark. If set, duration is ignored.\n  -L NUM\n        The number of milliseconds to elapse between taking samples. This controls the sample interval, & therefore the total number of samples, which affects statistical analyses. default: 1.\n  -S/--socketPath\n        A path to a Unix Domain Socket or a Windows Named Pipe. A URL is still required to send the correct Host header and path.\n  -w/--workers\n        Number of worker threads to use to fire requests.\n  -W/--warmup\n       Use a warm up interval before starting sampling.\n       This enables startup processes to finish and traffic to normalize before sampling begins\n       use -c and -d sub args e.g. `--warmup [ -c 1 -d 3 ]`\n  --on-port\n        Start the command listed after -- on the command line. When it starts listening on a port,\n        start sending requests to that port. A URL is still required to send requests to\n        the correct path. The hostname can be omitted, `localhost` will be used by default.\n  -m/--method METHOD\n        The HTTP method to use. default: 'GET'.\n  -t/--timeout NUM\n        The number of seconds before timing out and resetting a connection. default: 10\n  -T/--title TITLE\n        The title to place in the results for identification.\n  -b/--body BODY\n        The body of the request.\n        NOTE: This option needs to be used with the '-H/--headers' option in some frameworks\n  -F/--form FORM\n        Upload a form (multipart/form-data). The form options can be a JSON string like\n        '{ \"field 1\": { \"type\": \"text\", \"value\": \"a text value\"}, \"field 2\": { \"type\": \"file\", \"path\": \"path to the file\" } }'\n        or a path to a JSON file containing the form options.\n        When uploading a file the default filename value can be overridden by using the corresponding option:\n        '{ \"field name\": { \"type\": \"file\", \"path\": \"path to the file\", \"options\": { \"filename\": \"myfilename\" } } }'\n        Passing the filepath to the form can be done by using the corresponding option:\n        '{ \"field name\": { \"type\": \"file\", \"path\": \"path to the file\", \"options\": { \"filepath\": \"/some/path/myfilename\" } } }'\n  -i/--input FILE\n        The body of the request. See '-b/body' for more details.\n  -H/--headers K=V\n        The request headers.\n  --har FILE\n        When provided, Autocannon will use requests from the HAR file.\n        CAUTION: you have to specify one or more domains using URL option: only the HAR requests to the same domains will be considered.\n        NOTE: you can still add extra headers with -H/--headers but -m/--method, -F/--form, -i/--input -b/--body will be ignored.\n  -B/--bailout NUM\n        The number of failures before initiating a bailout.\n  -M/--maxConnectionRequests NUM\n        The max number of requests to make per connection to the server.\n  -O/--maxOverallRequests NUM\n        The max number of requests to make overall to the server.\n  -r/--connectionRate NUM\n        The max number of requests to make per second from an individual connection.\n  -R/--overallRate NUM\n        The max number of requests to make per second from all connections.\n        connection rate will take precedence if both are set.\n        NOTE: if using rate limiting and a very large rate is entered which cannot be met,\n              Autocannon will do as many requests as possible per second. Also, latency data will be corrected to compensate for the effects of the coordinated omission issue. If you are not familiar with the coordinated omission issue, you should probably read [this article](http://highscalability.com/blog/2015/10/5/your-load-generator-is-probably-lying-to-you-take-the-red-pi.html) or watch this [Gil Tene's talk](https://www.youtube.com/watch?v=lJ8ydIuPFeU) on the topic.\n  -C/--ignoreCoordinatedOmission\n        Ignore the coordinated omission issue when requests should be sent at a fixed rate using 'connectionRate' or 'overallRate'.\n        NOTE: it is not recommended to enable this option.\n              When the request rate cannot be met because the server is too slow, many request latencies might be missing and Autocannon might report a misleading latency distribution.\n  -D/--reconnectRate NUM\n        The number of requests to make before resetting a connections connection to the\n        server.\n  -n/--no-progress\n        Don't render the progress bar. default: false.\n  -l/--latency\n        Print all the latency data. default: false.\n  -I/--idReplacement\n        Enable replacement of [<id>] with a randomly generated ID within the request body. default: false.\n  -j/--json\n        Print the output as newline delimited JSON. This will cause the progress bar and results not to be rendered. default: false.\n  -f/--forever\n        Run the benchmark forever. Efficiently restarts the benchmark on completion. default: false.\n  -s/--servername\n        Server name for the SNI (Server Name Indication) TLS extension. Defaults to the hostname of the URL when it is not an IP address.\n  -x/--excludeErrorStats\n        Exclude error statistics (non-2xx HTTP responses) from the final latency and bytes per second averages. default: false.\n  -E/--expectBody EXPECTED\n        Ensure the body matches this value. If enabled, mismatches count towards bailout.\n        Enabling this option will slow down the load testing.\n  --renderStatusCodes\n        Print status codes and their respective statistics.\n  --cert\n        Path to cert chain in pem format\n  --key\n        Path to private key for specified cert in pem format\n  --ca\n        Path to trusted ca certificates for the test. This argument accepts both a single file as well as a list of files\n  --debug\n        Print connection errors to stderr.\n  -v/--version\n        Print the version number.\n  -h/--help\n        Print this menu.\n"
  },
  {
    "path": "lib/aggregateResult.js",
    "content": "'use strict'\n\nconst { decodeHist, getHistograms, histAsObj, addPercentiles } = require('./histUtil')\n\nfunction aggregateResult (results, opts, histograms) {\n  results = Array.isArray(results) ? results : [results]\n  histograms = getHistograms(histograms)\n\n  const aggregated = results.map(r => ({\n    ...r,\n    latencies: decodeHist(r.latencies),\n    requests: decodeHist(r.requests),\n    throughput: decodeHist(r.throughput)\n  })).reduce((acc, r) => {\n    acc.latencies.add(r.latencies)\n\n    acc.totalCompletedRequests += r.totalCompletedRequests\n    acc.totalRequests += r.totalRequests\n    acc.totalBytes += r.totalBytes\n    acc.samples += r.samples\n\n    acc.errors += r.errors\n    acc.timeouts += r.timeouts\n    acc.mismatches += r.mismatches\n    acc.non2xx += r.non2xx\n    acc.resets += r.resets\n    acc['1xx'] += r['1xx']\n    acc['2xx'] += r['2xx']\n    acc['3xx'] += r['3xx']\n    acc['4xx'] += r['4xx']\n    acc['5xx'] += r['5xx']\n\n    Object.keys(r.statusCodeStats).forEach(statusCode => {\n      if (!acc.statusCodeStats[statusCode]) {\n        acc.statusCodeStats[statusCode] = r.statusCodeStats[statusCode]\n      } else {\n        acc.statusCodeStats[statusCode].count += r.statusCodeStats[statusCode].count\n      }\n    })\n\n    return acc\n  })\n\n  const result = {\n    title: opts.title,\n    url: opts.url,\n    socketPath: opts.socketPath,\n    connections: opts.connections,\n    sampleInt: opts.sampleInt,\n    pipelining: opts.pipelining,\n    workers: opts.workers,\n\n    duration: aggregated.duration,\n    samples: aggregated.samples,\n    start: aggregated.start,\n    finish: aggregated.finish,\n    errors: aggregated.errors,\n    timeouts: aggregated.timeouts,\n    mismatches: aggregated.mismatches,\n    non2xx: aggregated.non2xx,\n    resets: aggregated.resets,\n    '1xx': aggregated['1xx'],\n    '2xx': aggregated['2xx'],\n    '3xx': aggregated['3xx'],\n    '4xx': aggregated['4xx'],\n    '5xx': aggregated['5xx'],\n    statusCodeStats: aggregated.statusCodeStats,\n\n    latency: addPercentiles(aggregated.latencies, histAsObj(aggregated.latencies)),\n    requests: addPercentiles(histograms.requests, histAsObj(histograms.requests, aggregated.totalCompletedRequests)),\n    throughput: addPercentiles(histograms.throughput, histAsObj(histograms.throughput, aggregated.totalBytes))\n  }\n\n  result.latency.totalCount = aggregated.latencies.totalCount\n  result.requests.sent = aggregated.totalRequests\n\n  if (result.requests.min >= Number.MAX_SAFE_INTEGER) result.requests.min = 0\n  if (result.throughput.min >= Number.MAX_SAFE_INTEGER) result.throughput.min = 0\n  if (result.latency.min >= Number.MAX_SAFE_INTEGER) result.latency.min = 0\n\n  return result\n}\n\nmodule.exports = aggregateResult\n"
  },
  {
    "path": "lib/defaultOptions.js",
    "content": "'use strict'\n\nconst defaultOptions = {\n  headers: {},\n  method: 'GET',\n  duration: 10,\n  connections: 10,\n  sampleInt: 1000,\n  pipelining: 1,\n  timeout: 10,\n  maxConnectionRequests: 0,\n  maxOverallRequests: 0,\n  connectionRate: 0,\n  overallRate: 0,\n  amount: 0,\n  reconnectRate: 0,\n  forever: false,\n  idReplacement: false,\n  requests: [{}],\n  servername: undefined,\n  excludeErrorStats: false\n}\n\nmodule.exports = defaultOptions\n"
  },
  {
    "path": "lib/format.js",
    "content": "'use strict'\n\nfunction format (num) {\n  if (num < 1000) {\n    return '' + num\n  } else {\n    return '' + Math.round(num / 1000) + 'k'\n  }\n}\n\nmodule.exports = format\n"
  },
  {
    "path": "lib/histUtil.js",
    "content": "const hdr = require('hdr-histogram-js')\nconst histUtil = require('hdr-histogram-percentiles-obj')\n\nhdr.initWebAssemblySync()\n\nconst getHistograms = ({\n  latencies = hdr.build({\n    useWebAssembly: true,\n    bitBucketSize: 64,\n    autoResize: true,\n    lowestDiscernibleValue: 1,\n    highestTrackableValue: 10000,\n    numberOfSignificantValueDigits: 5\n  }),\n  requests = hdr.build({\n    useWebAssembly: true,\n    bitBucketSize: 64,\n    autoResize: true,\n    lowestDiscernibleValue: 1,\n    highestTrackableValue: 1000000,\n    numberOfSignificantValueDigits: 3\n  }),\n  throughput = hdr.build({\n    useWebAssembly: true,\n    bitBucketSize: 64,\n    autoResize: true,\n    lowestDiscernibleValue: 1,\n    highestTrackableValue: 100000000000,\n    numberOfSignificantValueDigits: 3\n  })\n} = {}) => ({\n  latencies,\n  requests,\n  throughput\n})\n\nfunction encodeHist (h) {\n  if (h.__custom) return null\n\n  return hdr.encodeIntoCompressedBase64(h)\n}\n\nfunction decodeHist (str) {\n  if (!str) return null\n\n  return hdr.decodeFromCompressedBase64(str)\n}\n\nexports.getHistograms = getHistograms\nexports.encodeHist = encodeHist\nexports.decodeHist = decodeHist\nexports.histAsObj = histUtil.histAsObj\nexports.addPercentiles = histUtil.addPercentiles\nexports.percentiles = histUtil.percentiles\n"
  },
  {
    "path": "lib/httpClient.js",
    "content": "'use strict'\n\nconst inherits = require('util').inherits\nconst EE = require('events').EventEmitter\nconst net = require('net')\nconst tls = require('tls')\nconst retimer = require('retimer')\nconst HTTPParser = require('http-parser-js').HTTPParser\nconst RequestIterator = require('./requestIterator')\nconst noop = require('./noop')\nconst clone = require('lodash.clonedeep')\nconst PipelinedRequestsQueue = require('./pipelinedRequestsQueue')\n\nfunction Client (opts) {\n  if (!(this instanceof Client)) {\n    return new Client(opts)\n  }\n\n  this.opts = clone(opts)\n\n  this.opts.setupClient = this.opts.setupClient || noop\n  this.opts.pipelining = this.opts.pipelining || 1\n  this.opts.port = this.opts.port || 80\n  this.opts.expectBody = this.opts.expectBody || null\n  this.opts.tlsOptions = this.opts.tlsOptions || {}\n  this.timeout = (this.opts.timeout || 10) * 1000\n  this.ipc = !!this.opts.socketPath\n  this.secure = this.opts.protocol === 'https:'\n  this.auth = this.opts.auth || null\n\n  if (this.secure && this.opts.port === 80) {\n    this.opts.port = 443\n  }\n\n  this.parser = new HTTPParser(HTTPParser.RESPONSE)\n  this.requestIterator = new RequestIterator(this.opts)\n\n  this.reqsMade = 0\n\n  // used for request limiting\n  this.responseMax = this.opts.responseMax\n\n  // used for rate limiting\n  this.reqsMadeThisSecond = 0\n  this.rate = this.opts.rate\n\n  // used for forcing reconnects\n  this.reconnectRate = this.opts.reconnectRate\n\n  this.pipelinedRequests = new PipelinedRequestsQueue()\n  this.destroyed = false\n\n  this.opts.setupClient(this)\n\n  const handleTimeout = () => {\n    this._destroyConnection()\n\n    this.timeoutTicker.reschedule(this.timeout)\n\n    this._connect()\n\n    for (let i = 0; i < this.opts.pipelining; i++) {\n      this.emit('timeout')\n    }\n  }\n\n  if (this.rate) {\n    this.rateInterval = setInterval(() => {\n      this.reqsMadeThisSecond = 0\n      if (this.paused) this._doRequest(this.cer)\n      this.paused = false\n    }, 1000)\n  }\n\n  this.timeoutTicker = retimer(handleTimeout, this.timeout)\n  this.parser[HTTPParser.kOnHeaders] = () => {}\n  this.parser[HTTPParser.kOnHeadersComplete] = (opts) => {\n    this.emit('headers', opts)\n    this.pipelinedRequests.setHeaders(opts)\n  }\n\n  this.parser[HTTPParser.kOnBody] = (body, start, len) => {\n    this.pipelinedRequests.addBody(body.slice(start, start + len))\n    this.emit('body', body)\n  }\n\n  this.parser[HTTPParser.kOnMessageComplete] = () => {\n    const resp = this.pipelinedRequests.terminateRequest()\n\n    if (!this.destroyed && this.reconnectRate && this.reqsMade % this.reconnectRate === 0) {\n      return this._resetConnection()\n    }\n    if (!this.destroyed) {\n      this.requestIterator.recordBody(resp.req, resp.headers.statusCode, resp.body, resp.headers.headers)\n\n      this.emit('response', resp.headers.statusCode, resp.bytes, resp.duration, this.rate)\n\n      this._doRequest()\n\n      const isFn = typeof this.opts.verifyBody === 'function'\n      if (isFn && !this.opts.verifyBody(resp.body)) {\n        return this.emit('mismatch', resp.body)\n      } else if (!isFn && this.opts.expectBody && this.opts.expectBody !== resp.body) {\n        return this.emit('mismatch', resp.body)\n      }\n    }\n  }\n\n  this._connect()\n}\n\ninherits(Client, EE)\n\nClient.prototype._connect = function () {\n  if (this.secure) {\n    let servername\n    if (this.opts.servername) {\n      servername = this.opts.servername\n    } else if (!net.isIP(this.opts.hostname)) {\n      servername = this.opts.hostname\n    }\n\n    if (this.ipc) {\n      this.conn = tls.connect(this.opts.socketPath, { ...this.opts.tlsOptions, rejectUnauthorized: false })\n    } else {\n      this.conn = tls.connect(this.opts.port, this.opts.hostname, { ...this.opts.tlsOptions, rejectUnauthorized: false, servername })\n    }\n  } else {\n    if (this.ipc) {\n      this.conn = net.connect(this.opts.socketPath)\n    } else {\n      this.conn = net.connect(this.opts.port, this.opts.hostname)\n    }\n  }\n\n  this.conn.on('error', (error) => {\n    this.emit('connError', error)\n    if (!this.destroyed) this._connect()\n  })\n\n  this.conn.on('data', (chunk) => {\n    this.pipelinedRequests.addByteCount(chunk.length)\n    this.parser.execute(chunk)\n  })\n\n  this.conn.on('end', () => {\n    if (!this.destroyed) this._connect()\n  })\n\n  for (let i = 0; i < this.opts.pipelining; i++) {\n    this._doRequest()\n  }\n}\n\nClient.prototype._doRequest = function () {\n  if (!this.rate || (this.rate && this.reqsMadeThisSecond++ < this.rate)) {\n    if (!this.destroyed && this.responseMax && this.reqsMade >= this.responseMax) {\n      return this.destroy()\n    }\n    this.emit('request')\n    if (this.reqsMade > 0) {\n      this.requestIterator.nextRequest()\n      if (this.requestIterator.resetted) {\n        this.emit('reset')\n      }\n    }\n    this.pipelinedRequests.insertRequest(this.requestIterator.currentRequest)\n    this.conn.write(this.getRequestBuffer())\n    this.timeoutTicker.reschedule(this.timeout)\n    this.reqsMade++\n  } else {\n    this.paused = true\n  }\n}\n\nClient.prototype._resetConnection = function () {\n  this._destroyConnection()\n  this._connect()\n}\n\nClient.prototype._destroyConnection = function () {\n  this.conn.removeAllListeners('error')\n  this.conn.removeAllListeners('end')\n  this.conn.on('error', () => {})\n  this.conn.destroy()\n  this.pipelinedRequests.clear()\n}\n\nClient.prototype.destroy = function () {\n  if (!this.destroyed) {\n    this.destroyed = true\n    this.timeoutTicker.clear()\n    if (this.rate) clearInterval(this.rateInterval)\n    this.emit('done')\n    this._destroyConnection()\n  }\n}\n\nClient.prototype.getRequestBuffer = function () {\n  return this.requestIterator.currentRequest.requestBuffer\n}\n\nClient.prototype.setHeaders = function (newHeaders) {\n  this._okayToUpdateCheck()\n  this.requestIterator.setHeaders(newHeaders)\n}\n\nClient.prototype.setBody = function (newBody) {\n  this._okayToUpdateCheck()\n  this.requestIterator.setBody(newBody)\n}\n\nClient.prototype.setHeadersAndBody = function (newHeaders, newBody) {\n  this._okayToUpdateCheck()\n  this.requestIterator.setHeadersAndBody(newHeaders, newBody)\n}\n\nClient.prototype.setRequest = function (newRequest) {\n  this._okayToUpdateCheck()\n  this.requestIterator.setRequest(newRequest)\n}\n\nClient.prototype.setRequests = function (newRequests) {\n  this._okayToUpdateCheck()\n  this.requestIterator.setRequests(newRequests)\n}\n\nClient.prototype._okayToUpdateCheck = function () {\n  if (this.opts.pipelining > 1) {\n    throw new Error('cannot update requests when the piplining factor is greater than 1')\n  }\n}\n\nmodule.exports = Client\n"
  },
  {
    "path": "lib/httpMethods.js",
    "content": "// most http methods taken from RFC 7231\n// PATCH is defined in RFC 5789\nmodule.exports = [\n  'GET',\n  'HEAD',\n  'POST',\n  'PUT',\n  'DELETE',\n  'CONNECT',\n  'OPTIONS',\n  'TRACE',\n  'PATCH'\n]\n"
  },
  {
    "path": "lib/httpRequestBuilder.js",
    "content": "'use strict'\n\nconst methods = require('./httpMethods')\nconst { getPropertyCaseInsensitive } = require('./util')\n\n// this is a build request factory, that curries the build request function\n// and sets the default for it\nfunction requestBuilder (defaults) {\n  // these need to be defined per request builder creation, because of the way\n  // headers don't get deep copied\n  const builderDefaults = {\n    method: 'GET',\n    path: '/',\n    headers: {},\n    body: Buffer.alloc(0),\n    hostname: 'localhost',\n    setupRequest: reqData => reqData,\n    port: 80\n  }\n\n  defaults = Object.assign(builderDefaults, defaults)\n\n  // buildRequest takes an object, and turns it into a buffer representing the\n  // http request.\n  // second parameter is passed to setupRequest, when relevant\n  // will return null if setupRequest returns falsey result\n  return function buildRequest (reqData, context) {\n    // below is a hack to enable deep extending of the headers so the default\n    // headers object isn't overwritten by accident\n    reqData = reqData || {}\n    reqData.headers = Object.assign({}, defaults.headers, reqData.headers)\n\n    reqData = Object.assign({}, defaults, reqData)\n\n    reqData = reqData.setupRequest(reqData, context)\n    if (!reqData) {\n      return null\n    }\n\n    // for some reason some tests fail with method === undefined\n    // the reqData.method should be set to SOMETHING in this case\n    // cannot find reason for failure if `|| 'GET'` is taken out\n    const method = reqData.method\n    const path = reqData.path\n    const headers = reqData.headers\n    const body = reqData.body\n\n    const headersDefinedHost = getPropertyCaseInsensitive(reqData.headers, 'host')\n    let host = headersDefinedHost || reqData.host\n\n    if (!host) {\n      const hostname = reqData.hostname\n      const port = reqData.port\n      host = hostname + ':' + port\n    }\n    const baseReq = [\n      `${method} ${path} HTTP/1.1`\n    ]\n    if (!headersDefinedHost) {\n      baseReq.push(`Host: ${host}`)\n    }\n    baseReq.push('Connection: keep-alive')\n    if (reqData.auth) {\n      const encodedAuth = Buffer.from(reqData.auth).toString('base64')\n      headers.Authorization = `Basic ${encodedAuth}`\n    }\n\n    if (methods.indexOf(method) < 0) {\n      throw new Error(`${method} HTTP method is not supported`)\n    }\n\n    let bodyBuf\n\n    if (typeof body === 'string') {\n      bodyBuf = Buffer.from(body)\n    } else if (typeof body === 'number') {\n      bodyBuf = Buffer.from(body + '')\n    } else if (Buffer.isBuffer(body)) {\n      bodyBuf = body\n    } else if (body && Array.isArray(body._)) {\n      // when the request body passed on the CLI includes brackets like for\n      // a JSON array, the subarg parser will oddly provide the contents as\n      // `body._`. Work around this specific issue.\n      bodyBuf = Buffer.from(`[${body._}]`)\n    } else if (body) {\n      throw new Error('body must be either a string or a buffer')\n    }\n\n    if (bodyBuf && bodyBuf.length > 0) {\n      const idCount = reqData.idReplacement\n        ? (bodyBuf.toString().match(/\\[<id>\\]/g) || []).length\n        : 0\n      headers['Content-Length'] = `${bodyBuf.length + (idCount * 27)}`\n    }\n\n    for (const [key, header] of Object.entries(headers)) {\n      baseReq.push(`${key}: ${header}`)\n    }\n\n    let req = Buffer.from(baseReq.join('\\r\\n') + '\\r\\n\\r\\n', 'utf8')\n\n    if (bodyBuf && bodyBuf.length > 0) {\n      req = Buffer.concat([req, bodyBuf])\n    }\n\n    return req\n  }\n}\n\nmodule.exports = requestBuilder\n"
  },
  {
    "path": "lib/init.js",
    "content": "'use strict'\n\nconst EE = require('events').EventEmitter\nconst { isMainThread } = require('./worker_threads')\n\nconst initWorkers = require('./manager')\nconst validateOpts = require('./validate')\nconst noop = require('./noop')\nconst runTracker = require('./runTracker')\nconst track = require('./progressTracker')\n\nfunction init (opts, cb) {\n  const cbPassedIn = (typeof cb === 'function')\n  if (!cbPassedIn && !opts.forever) {\n    if (opts.warmup) {\n      return runWithWarmup(opts)\n    } else {\n      return run(opts)\n    }\n  } else {\n    return _init(opts, null, cb)\n  }\n}\n\nfunction run (opts) {\n  const tracker = new EE()\n  const promise = new Promise((resolve, reject) => {\n    _init(opts, tracker, (err, results) => {\n      if (err) return reject(err)\n      resolve(results)\n    })\n  })\n  tracker.then = promise.then.bind(promise)\n  tracker.catch = promise.catch.bind(promise)\n\n  return tracker\n}\n\nfunction runWithWarmup (opts) {\n  const warmupOpts = {\n    ...opts,\n    ...opts.warmup,\n    warmupRunning: true,\n    renderResultsTable: false\n  }\n  const mainTracker = new EE()\n  const warmUpTracker = new EE()\n  const promise = new Promise((resolve, reject) => {\n    _init(warmupOpts, warmUpTracker, (err, warmupResults) => {\n      if (err) return reject(err)\n      _init(opts, mainTracker, (err, results) => {\n        if (err) return reject(err)\n        results.warmup = warmupResults\n        resolve(results)\n      })\n    })\n  })\n  mainTracker.then = promise.then.bind(promise)\n  mainTracker.catch = promise.catch.bind(promise)\n  return mainTracker\n}\n\nfunction _init (opts, tracker, cb) {\n  const cbPassedIn = (typeof cb === 'function')\n  cb = cb || noop\n\n  tracker = tracker || new EE()\n\n  opts = validateOpts(opts, cbPassedIn)\n\n  function _cb (err, result) {\n    if (err) {\n      return cbPassedIn ? cb(err) : setImmediate(() => tracker.emit('error', err))\n    }\n\n    tracker.emit('done', result)\n    cb(null, result)\n\n    if (!err && opts.json) {\n      console.log(JSON.stringify(result))\n    }\n  }\n\n  if (opts instanceof Error) {\n    _cb(opts)\n    return tracker\n  }\n\n  tracker.opts = opts\n\n  if (opts.workers && isMainThread) {\n    initWorkers(opts, tracker, _cb)\n  } else {\n    runTracker(opts, tracker, _cb)\n  }\n\n  // if not running via command-line and\n  // not rendering json, or if std isn't a tty, track progress\n  if (opts[Symbol.for('internal')] && (!opts.json || !process.stdout.isTTY)) track(tracker, opts)\n\n  return tracker\n}\n\nmodule.exports = init\n"
  },
  {
    "path": "lib/manager.js",
    "content": "'use strict'\n\nconst path = require('path')\nconst EE = require('events').EventEmitter\nconst aggregateResult = require('./aggregateResult')\nconst { getHistograms } = require('./histUtil')\nconst { Worker } = require('./worker_threads')\n\nfunction initWorkers (opts, tracker, cb) {\n  tracker = tracker || new EE()\n\n  const workers = []\n  const results = []\n  const numWorkers = +opts.workers\n  const histograms = getHistograms()\n  const histData = {\n    requests: [],\n    throughput: []\n  }\n  let restart = true\n\n  function reset () {\n    workers.length = 0\n    results.length = 0\n    histograms.requests.reset()\n    histograms.throughput.reset()\n    histData.requests.length = 0\n    histData.throughput.length = 0\n  }\n\n  function startAll () {\n    for (const w of workers) {\n      w.postMessage({ cmd: 'START' })\n    }\n\n    setImmediate(() => { tracker.emit('start') })\n  }\n\n  function handleFinish () {\n    const result = aggregateResult(results, opts, histograms)\n    cb(null, result)\n    reset()\n\n    if (opts.forever && restart) {\n      setImmediate(() => {\n        init()\n        startAll()\n      })\n    }\n  }\n\n  function stopAll () {\n    for (const w of workers) {\n      w.postMessage({ cmd: 'STOP' })\n    }\n    reset()\n  }\n\n  const workerOpts = {\n    ...opts,\n    ...(opts.amount ? { amount: Math.max(Math.floor(opts.amount / numWorkers), 1) } : undefined),\n    ...(opts.connections ? { connections: Math.max(Math.floor(opts.connections / numWorkers), 1) } : undefined)\n  }\n  workerOpts.a = workerOpts.amount\n  workerOpts.c = workerOpts.connections\n\n  function init () {\n    for (let i = 0; i < numWorkers; i++) {\n      const worker = new Worker(path.resolve(__dirname, './worker.js'), { workerData: { opts: workerOpts } })\n\n      worker.on('message', (msg) => {\n        const { cmd, data, error } = msg\n\n        if (cmd === 'RESULT') {\n          results.push(data)\n\n          if (results.length === workers.length) {\n            handleFinish()\n          }\n        } else if (cmd === 'UPDATE_HIST') {\n          const { name, value } = data\n          histData[name].push(value)\n\n          if (histData[name].length === workers.length) {\n            const total = histData[name].reduce((acc, v) => acc + v, 0)\n            histData[name].length = 0\n            histograms[name].recordValue(total)\n          }\n        } else if (cmd === 'RESET_HIST') {\n          const { name } = data\n          histograms[name].reset()\n        } else if (cmd === 'ERROR') {\n          tracker.emit('error', error)\n        }\n      })\n\n      worker.on('error', (err) => {\n        console.log('Worker error:', err)\n        stopAll()\n        cb(err)\n      })\n\n      workers.push(worker)\n    }\n  }\n\n  init()\n  startAll()\n\n  tracker.stop = () => {\n    restart = false\n    stopAll()\n  }\n\n  return tracker\n}\n\nmodule.exports = initWorkers\n"
  },
  {
    "path": "lib/multipart.js",
    "content": "'use strict'\n\nconst { resolve, basename } = require('path')\nconst { readFileSync } = require('fs')\nconst FormData = require('form-data')\n\nfunction getFormData (string) {\n  try {\n    return JSON.parse(string)\n  } catch (error) {\n    try {\n      const path = resolve(string)\n      const data = readFileSync(path, 'utf8')\n      return JSON.parse(data)\n    } catch (error) {\n      throw new Error('Invalid JSON or file where to get form data')\n    }\n  }\n}\n\nmodule.exports = (options) => {\n  const obj = typeof options === 'string' ? getFormData(options) : options\n  const form = new FormData()\n  for (const key in obj) {\n    const type = obj[key] && obj[key].type\n    switch (type) {\n      case 'file': {\n        const path = obj[key] && obj[key].path\n        if (!path) {\n          throw new Error(`Missing key 'path' in form object for key '${key}'`)\n        }\n        const opts = obj[key] && obj[key].options\n        const buffer = readFileSync(path)\n        form.append(key, buffer, Object.assign({}, {\n          filename: basename(path)\n        }, opts))\n        break\n      }\n      case 'text': {\n        const value = obj[key] && obj[key].value\n        if (!value) {\n          throw new Error(`Missing key 'value' in form object for key '${key}'`)\n        }\n        form.append(key, value)\n        break\n      }\n      default:\n        throw new Error('A \\'type\\' key with value \\'text\\' or \\'file\\' should be specified')\n    }\n  }\n  return form\n}\n"
  },
  {
    "path": "lib/noop.js",
    "content": "/* istanbul ignore next */\nmodule.exports = function noop () {}\n"
  },
  {
    "path": "lib/parseHAR.js",
    "content": "'use strict'\n\n// given we support node v8\n// eslint-disable-next-line n/no-deprecated-api\nconst { parse } = require('url')\n\nfunction parseHAR (har) {\n  const requestsPerOrigin = new Map()\n  try {\n    if (!har || typeof har !== 'object' || typeof har.log !== 'object' || !Array.isArray(har.log.entries) || !har.log.entries.length) {\n      throw new Error('no entries found')\n    }\n    let i = 0\n    for (const entry of har.log.entries) {\n      i++\n      if (!entry || typeof entry !== 'object' || !entry.request || typeof entry.request !== 'object') {\n        throw new Error(`invalid request in entry #${i}`)\n      }\n      const { request: { method, url, headers: headerArray, postData } } = entry\n      // turn headers array to headers object\n      const headers = {}\n      if (!Array.isArray(headerArray)) {\n        throw new Error(`invalid headers array in entry #${i}`)\n      }\n      let j = 0\n      for (const header of headerArray) {\n        j++\n        if (!header || typeof header !== 'object' || typeof header.name !== 'string' || typeof header.value !== 'string') {\n          throw new Error(`invalid name or value in header #${j} of entry #${i}`)\n        }\n        const { name, value } = header\n        headers[name] = value\n      }\n      const { path, hash, host, protocol } = parse(url)\n      const origin = `${protocol}//${host}`\n\n      let requests = requestsPerOrigin.get(origin)\n      if (!requests) {\n        requests = []\n        requestsPerOrigin.set(origin, requests)\n      }\n      const request = {\n        origin,\n        method,\n        // only keep path & hash as our HttpClient will handle origin\n        path: `${path}${hash || ''}`,\n        headers\n      }\n      if (typeof postData === 'object' && typeof postData.text === 'string') {\n        request.body = postData.text\n      }\n      requests.push(request)\n    }\n  } catch (err) {\n    throw new Error(`Could not parse HAR content: ${err.message}`)\n  }\n  return requestsPerOrigin\n}\n\nexports.parseHAR = parseHAR\n"
  },
  {
    "path": "lib/pipelinedRequestsQueue.js",
    "content": "'use strict'\n\n/**\n * A queue (FIFO) to hold pipelined requests and link metadata to them as the response is received from the server.\n * This facilitates the handling of responses when the HTTP requests are pipelined.\n * A queue is the best structure for this because the sever reponses are provided in the same order as the requests\n * Cf. https://en.wikipedia.org/wiki/HTTP_pipelining\n *\n * /!\\ it's up to you as a user to ensure that the queue is populated if using\n * the functionality. This implementation will fail silently if e.g. you try to\n * call any function accessing the queue while it's empty.\n */\nclass PipelinedRequestsQueue {\n  constructor () {\n    this.pendingRequests = []\n  }\n\n  insertRequest (req) {\n    this.pendingRequests.push({\n      req,\n      bytes: 0,\n      body: '',\n      headers: {},\n      startTime: process.hrtime()\n    })\n  }\n\n  peek () {\n    if (this.pendingRequests.length > 0) {\n      return this.pendingRequests[0]\n    }\n  }\n\n  addByteCount (count) {\n    const req = this.peek()\n    if (req) {\n      req.bytes += count\n    }\n  }\n\n  addBody (data) {\n    const req = this.peek()\n    if (req) {\n      req.body += data\n    }\n  }\n\n  setHeaders (headers) {\n    const req = this.peek()\n    if (req) {\n      req.headers = headers\n    }\n  }\n\n  size () {\n    return this.pendingRequests.length\n  }\n\n  /** Terminates the first-in request\n   * This will calculate the request duration, remove it from the queue and return its data\n   **/\n  terminateRequest () {\n    if (this.pendingRequests.length > 0) {\n      const data = this.pendingRequests.shift()\n      const hrduration = process.hrtime(data.startTime)\n      data.duration = hrduration[0] * 1e3 + hrduration[1] / 1e6\n      return data\n    }\n  }\n\n  clear () {\n    this.pendingRequests = []\n  }\n\n  toArray () {\n    return this.pendingRequests\n  }\n}\n\nmodule.exports = PipelinedRequestsQueue\n"
  },
  {
    "path": "lib/preload/autocannonDetectPort.js",
    "content": "'use strict'\n\nconst onListen = require('on-net-listen')\nconst net = require('net')\n\nconst socket = net.connect(process.env.AUTOCANNON_SOCKET)\n\nonListen(function (addr) {\n  this.destroy()\n  const port = Buffer.from(addr.port + '')\n  socket.write(port)\n})\n\nsocket.unref()\n"
  },
  {
    "path": "lib/printResult.js",
    "content": "'use strict'\n\nconst Table = require('cli-table3')\nconst Chalk = require('chalk')\nconst testColorSupport = require('color-support')\nconst prettyBytes = require('pretty-bytes')\nconst { percentiles } = require('./histUtil')\nconst format = require('./format')\n\nconst defaults = {\n  // use stderr as its progressBar's default\n  outputStream: process.stderr,\n  renderResultsTable: true,\n  renderLatencyTable: false,\n  verbose: true\n}\n\nclass TableWithoutColor extends Table {\n  constructor (opts = {}) {\n    super({ ...opts, style: { head: [], border: [] } })\n  }\n}\n\nconst printResult = (result, opts) => {\n  opts = Object.assign({}, defaults, opts)\n  let strResult = ''\n\n  if (opts.verbose) {\n    const chalk = new Chalk.Instance(testColorSupport({ stream: opts.outputStream, alwaysReturn: true }))\n    const ColorSafeTable = chalk.level === 0 ? TableWithoutColor : Table\n\n    const shortLatency = new ColorSafeTable({\n      head: asColor(chalk.cyan, ['Stat', '2.5%', '50%', '97.5%', '99%', 'Avg', 'Stdev', 'Max'])\n    })\n    shortLatency.push(asLowRow(chalk.bold('Latency'), asMs(result.latency)))\n    logToLocalStr('\\n' + shortLatency.toString())\n\n    const requests = new ColorSafeTable({\n      head: asColor(chalk.cyan, ['Stat', '1%', '2.5%', '50%', '97.5%', 'Avg', 'Stdev', 'Min'])\n    })\n\n    requests.push(asHighRow(chalk.bold('Req/Sec'), asNumber(result.requests)))\n    requests.push(asHighRow(chalk.bold('Bytes/Sec'), asBytes(result.throughput)))\n    logToLocalStr(requests.toString())\n\n    if (opts.renderStatusCodes === true) {\n      const statusCodeStats = new ColorSafeTable({\n        head: asColor(chalk.cyan, ['Code', 'Count'])\n      })\n      Object.keys(result.statusCodeStats).forEach(statusCode => {\n        const stats = result.statusCodeStats[statusCode]\n        const colorize = colorizeByStatusCode(chalk, statusCode)\n        statusCodeStats.push([colorize(statusCode), stats.count])\n      })\n      logToLocalStr(statusCodeStats.toString())\n    }\n    logToLocalStr('')\n    if (result.sampleInt === 1000) {\n      logToLocalStr('Req/Bytes counts sampled once per second.')\n    } else {\n      logToLocalStr('Req/Bytes counts sampled every ' + result.sampleInt / 1000 + ' seconds.')\n    }\n    logToLocalStr('# of samples: ' + result.samples)\n    logToLocalStr('')\n\n    if (opts.renderLatencyTable) {\n      const latencies = new ColorSafeTable({\n        head: asColor(chalk.cyan, ['Percentile', 'Latency (ms)'])\n      })\n      percentiles.map((perc) => {\n        const key = `p${perc}`.replace('.', '_')\n        return [\n          chalk.bold('' + perc),\n          result.latency[key]\n        ]\n      }).forEach(row => {\n        latencies.push(row)\n      })\n      logToLocalStr(latencies.toString())\n      logToLocalStr('')\n    }\n  }\n\n  if (result.non2xx) {\n    logToLocalStr(`${result['2xx']} 2xx responses, ${result.non2xx} non 2xx responses`)\n  }\n  logToLocalStr(`${format(result.requests.sent)} requests in ${result.duration}s, ${prettyBytes(result.throughput.total)} read`)\n  if (result.errors) {\n    logToLocalStr(`${format(result.errors)} errors (${format(result.timeouts)} timeouts)`)\n  }\n  if (result.mismatches) {\n    logToLocalStr(`${format(result.mismatches)} requests with mismatched body`)\n  }\n  if (result.resets) {\n    logToLocalStr(`request pipeline was reset ${format(result.resets)} ${result.resets === 1 ? 'time' : 'times'}`)\n  }\n\n  function logToLocalStr (msg) {\n    strResult += msg + '\\n'\n  }\n\n  return strResult\n}\n\n// create a table row for stats where low values is better\nfunction asLowRow (name, stat) {\n  return [\n    name,\n    stat.p2_5,\n    stat.p50,\n    stat.p97_5,\n    stat.p99,\n    stat.average,\n    stat.stddev,\n    typeof stat.max === 'string' ? stat.max : Math.floor(stat.max * 100) / 100\n  ]\n}\n\n// create a table row for stats where high values is better\nfunction asHighRow (name, stat) {\n  return [\n    name,\n    stat.p1,\n    stat.p2_5,\n    stat.p50,\n    stat.p97_5,\n    stat.average,\n    stat.stddev,\n    typeof stat.min === 'string' ? stat.min : Math.floor(stat.min * 100) / 100\n  ]\n}\n\nfunction asColor (colorise, row) {\n  return row.map((entry) => colorise(entry))\n}\n\nfunction asMs (stat) {\n  const result = Object.create(null)\n  for (const k of Object.keys(stat)) {\n    result[k] = `${stat[k]} ms`\n  }\n  result.max = typeof stat.max === 'string' ? stat.max : `${Math.floor(stat.max * 100) / 100} ms`\n\n  return result\n}\n\nfunction asNumber (stat) {\n  const result = Object.create(null)\n  for (const k of Object.keys(stat)) {\n    result[k] = stat[k].toLocaleString(undefined, {\n      // to show all digits\n      maximumFractionDigits: 20\n    })\n  }\n\n  return result\n}\n\nfunction asBytes (stat) {\n  const result = Object.create(stat)\n\n  for (const p of percentiles) {\n    const key = `p${p}`.replace('.', '_')\n    result[key] = prettyBytes(stat[key])\n  }\n\n  result.average = prettyBytes(stat.average)\n  result.stddev = prettyBytes(stat.stddev)\n  result.max = prettyBytes(stat.max)\n  result.min = prettyBytes(stat.min)\n  return result\n}\n\nfunction colorizeByStatusCode (chalk, statusCode) {\n  const codeClass = Math.floor(parseInt(statusCode) / 100) - 1\n  return [chalk.cyan, chalk.cyan, chalk.cyan, chalk.redBright, chalk.redBright][codeClass]\n}\n\nmodule.exports = printResult\n"
  },
  {
    "path": "lib/progressTracker.js",
    "content": "/**\n * Prints out the test result details. It doesn't have not much business logic.\n * We skip test coverage for this file\n */\n/* istanbul ignore file */\n'use strict'\n\nconst ProgressBar = require('progress')\nconst Chalk = require('chalk')\nconst testColorSupport = require('color-support')\nconst charSpinner = require('char-spinner')\nconst printResult = require('./printResult')\nconst { isMainThread } = require('./worker_threads')\nconst defaults = {\n  // use stderr as its progressBar's default\n  outputStream: process.stderr,\n  renderProgressBar: true,\n  renderResultsTable: true,\n  renderLatencyTable: false\n}\n\nfunction track (instance, opts) {\n  if (!instance) {\n    throw new Error('instance required for tracking')\n  }\n\n  opts = Object.assign({}, defaults, opts)\n\n  const chalk = new Chalk.Instance(testColorSupport({ stream: opts.outputStream, alwaysReturn: true }))\n  // this default needs to be set after chalk is setup, because chalk is now local to this func\n  const runningDescription = opts.warmupRunning ? 'warmup' : 'running'\n  opts.progressBarString = opts.progressBarString || `${chalk.green(runningDescription)} [:bar] :percent`\n\n  const iOpts = instance.opts\n  let durationProgressBar\n  let amountProgressBar\n  let addedListeners = false\n  let spinner\n\n  instance.on('start', () => {\n    if (opts.renderProgressBar && isMainThread) {\n      const socketPath = iOpts.socketPath ? ` (${iOpts.socketPath})` : ''\n      let msg = `${iOpts.connections} connections`\n\n      if (iOpts.pipelining > 1) {\n        msg += ` with ${iOpts.pipelining} pipelining factor`\n      }\n\n      if (iOpts.workers) {\n        msg += `\\n${iOpts.workers} workers`\n      }\n\n      const runningType = opts.warmupRunning ? 'warmup' : 'test'\n      const message = iOpts.amount\n        ? `Running ${iOpts.amount} requests ${runningType} @ ${iOpts.url}${socketPath}\\n${msg}\\n`\n        : `Running ${iOpts.duration}s ${runningType} @ ${iOpts.url}${socketPath}\\n${msg}\\n`\n\n      logToStream(message)\n\n      if (iOpts.workers) {\n        showSpinner()\n      } else {\n        if (iOpts.amount) {\n          amountProgressBar = trackAmount(instance, opts, iOpts)\n        } else {\n          durationProgressBar = trackDuration(instance, opts, iOpts)\n        }\n      }\n\n      addListener()\n    }\n  })\n\n  instance.on('done', (result) => {\n    // the code below this `if` just renders the results table...\n    // if the user doesn't want to render the table, we can just return early\n    if (opts.renderResultsTable === false) return\n\n    const tableStrResult = printResult(result, opts)\n    opts.outputStream.write(tableStrResult)\n  })\n\n  function showSpinner () {\n    spinner = charSpinner()\n  }\n\n  function hideSpinner () {\n    if (spinner) {\n      clearInterval(spinner)\n      spinner = null\n    }\n  }\n\n  function addListener () {\n    // add listeners for progress bar to instance here so they aren't\n    // added on restarting, causing listener leaks\n    if (addedListeners) {\n      return\n    }\n\n    addedListeners = true\n\n    // TODO: think about if workers can report progress every second, that\n    // way we could have a progress bar per worker.\n    if (iOpts.workers) {\n      // using `prependOnceListener` to make sure that we hide the spinner\n      // before writing anything else.\n      // if we print anything else to the output, the stale spinner text\n      // is left uncleared and clutters the output.\n      instance.prependOnceListener('done', hideSpinner)\n      return\n    }\n\n    // note: Attempted to curry the functions below, but that breaks the functionality\n    // as they use the scope/closure of the progress bar variables to allow them to be reset\n    if (opts.outputStream.isTTY) {\n      if (!iOpts.amount) { // duration progress bar\n        instance.on('tick', () => { durationProgressBar.tick() })\n        instance.on('done', () => { durationProgressBar.tick(iOpts.duration - 1) })\n        process.once('SIGINT', () => { durationProgressBar.tick(iOpts.duration - 1) })\n      } else { // amount progress bar\n        instance.on('response', () => { amountProgressBar.tick() })\n        instance.on('reqError', () => { amountProgressBar.tick() })\n        instance.on('done', () => { amountProgressBar.tick(iOpts.amount - 1) })\n        process.once('SIGINT', () => { amountProgressBar.tick(iOpts.amount - 1) })\n      }\n    }\n  }\n\n  function logToStream (msg) {\n    if (!isMainThread) return\n\n    opts.outputStream.write(msg + '\\n')\n  }\n}\n\nfunction trackDuration (instance, opts, iOpts) {\n  // if switch needed needed to avoid\n  // https://github.com/mcollina/autocannon/issues/60\n  if (!opts.outputStream.isTTY) return\n\n  const progressBar = new ProgressBar(opts.progressBarString, {\n    width: 20,\n    incomplete: ' ',\n    total: iOpts.duration,\n    clear: true,\n    stream: opts.outputStream\n  })\n\n  progressBar.tick(0)\n  return progressBar\n}\n\nfunction trackAmount (instance, opts, iOpts) {\n  // if switch needed needed to avoid\n  // https://github.com/mcollina/autocannon/issues/60\n  if (!opts.outputStream.isTTY) return\n\n  const progressBar = new ProgressBar(opts.progressBarString, {\n    width: 20,\n    incomplete: ' ',\n    total: iOpts.amount,\n    clear: true,\n    stream: opts.outputStream\n  })\n\n  progressBar.tick(0)\n  return progressBar\n}\n\nmodule.exports = track\n"
  },
  {
    "path": "lib/requestIterator.js",
    "content": "'use strict'\n\nconst hyperid = require('hyperid')\nconst inherits = require('util').inherits\nconst requestBuilder = require('./httpRequestBuilder')\nconst clone = require('lodash.clonedeep')\nconst chunk = require('lodash.chunk')\nconst flatten = require('lodash.flatten')\n\nconst toHeaderKeyValue = (rawHeaders) => {\n  const tupleHeaders = chunk(rawHeaders, 2)\n  const headers = {}\n  tupleHeaders.forEach((val) => {\n    const currentValue = headers[val[0]]\n    if (!currentValue) {\n      headers[val[0]] = val[1]\n    } else {\n      headers[val[0]] = flatten([currentValue, val[1]])\n    }\n  })\n  return headers\n}\n\nfunction RequestIterator (opts) {\n  if (!(this instanceof RequestIterator)) {\n    return new RequestIterator(opts)\n  }\n\n  this.hyperid = hyperid({ urlSafe: true })\n  this.resetted = false\n  this.headers = {}\n  this.initialContext = opts.initialContext || {}\n  this.resetContext()\n  this.reqDefaults = opts\n  this.requestBuilder = requestBuilder(opts)\n  this.setRequests(opts.requests)\n}\n\ninherits(RequestIterator, Object)\n\nRequestIterator.prototype.resetContext = function () {\n  this.context = clone(this.initialContext)\n}\n\nRequestIterator.prototype.nextRequest = function () {\n  this.resetted = false\n  ++this.currentRequestIndex\n  // when looping over available request, clear context for a fresh start\n  if (this.currentRequestIndex === this.requests.length) {\n    this.resetContext()\n    this.currentRequestIndex = 0\n  }\n  this.currentRequest = this.requests[this.currentRequestIndex]\n  // only builds if it has dynamic setup\n  if (this.reqDefaults.idReplacement || typeof this.currentRequest.setupRequest === 'function') {\n    this.rebuildRequest()\n  }\n  return this.currentRequest\n}\n\nRequestIterator.prototype.nextRequestBuffer = function () {\n  // get next request\n  this.nextRequest()\n  return this.currentRequest.requestBuffer\n}\n\nRequestIterator.prototype.setRequests = function (newRequests) {\n  this.resetted = false\n  this.requests = newRequests || [{}]\n  this.currentRequestIndex = 0\n  // build all request which don't have dynamic setup, except if it's the first one\n  this.requests.forEach((request, i) => {\n    this.currentRequest = request\n    if (i === 0 || typeof request.setupRequest !== 'function') {\n      this.rebuildRequest()\n    }\n  })\n  this.currentRequest = this.requests[0]\n}\n\nRequestIterator.prototype.setHeaders = function (newHeaders) {\n  this.headers = newHeaders || {}\n  this.currentRequest.headers = this.headers\n  this.rebuildRequest()\n}\n\nRequestIterator.prototype.setBody = function (newBody) {\n  this.currentRequest.body = newBody || Buffer.alloc(0)\n  this.rebuildRequest()\n}\n\nRequestIterator.prototype.setHeadersAndBody = function (newHeaders, newBody) {\n  this.headers = newHeaders || {}\n  this.currentRequest.headers = this.headers\n  this.currentRequest.body = newBody || Buffer.alloc(0)\n  this.rebuildRequest()\n}\n\nRequestIterator.prototype.setRequest = function (newRequest) {\n  this.currentRequest = newRequest || {}\n  this.rebuildRequest()\n}\n\nRequestIterator.prototype.rebuildRequest = function () {\n  let data\n  this.resetted = false\n  if (this.currentRequest) {\n    this.currentRequest.headers = this.currentRequest.headers || this.headers\n    data = this.requestBuilder(this.currentRequest, this.context)\n    if (data) {\n      const hyperid = this.hyperid()\n      this.currentRequest.requestBuffer = this.reqDefaults.idReplacement\n        ? Buffer.from(data.toString()\n          .replace(/\\[<id>\\]/g, hyperid)\n          // in the first line only (the url), replace encoded id placeholders\n          .replace(/^.+/, m => m.replace(/\\[%3Cid%3E]/g, hyperid)))\n        : data\n    } else if (this.currentRequestIndex === 0) {\n      // when first request fails to build, we can not reset pipeline, or it'll never end\n      throw new Error('First setupRequest() failed did not returned valid request. Stopping')\n    } else {\n      this.currentRequestIndex = this.requests.length - 1\n      this.nextRequest()\n      this.resetted = true\n    }\n  }\n  return data\n}\n\nRequestIterator.prototype.recordBody = function (request, status, body, headers) {\n  if (request && typeof request.onResponse === 'function') {\n    request.onResponse(status, body, this.context, toHeaderKeyValue(headers || []))\n  }\n}\n\nmodule.exports = RequestIterator\n"
  },
  {
    "path": "lib/run.js",
    "content": "'use strict'\n\nconst URL = require('url')\nconst reInterval = require('reinterval')\nconst EE = require('events').EventEmitter\nconst Client = require('./httpClient')\nconst { isMainThread } = require('./worker_threads')\nconst { ofURL } = require('./url')\nconst aggregateResult = require('./aggregateResult')\nconst { getHistograms, encodeHist } = require('./histUtil')\n\nconst defaults = {\n  harRequests: new Map()\n}\n\nfunction run (opts, tracker, cb) {\n  opts = Object.assign({}, defaults, opts)\n  tracker = tracker || new EE()\n\n  const histograms = getHistograms(opts.histograms)\n  const { latencies, requests, throughput } = histograms\n\n  const statusCodes = [\n    0, // 1xx\n    0, // 2xx\n    0, // 3xx\n    0, // 4xx\n    0 // 5xx\n  ]\n\n  const statusCodeStats = {}\n\n  if (opts.overallRate && (opts.overallRate < opts.connections)) opts.connections = opts.overallRate\n\n  let counter = 0\n  let bytes = 0\n  let errors = 0\n  let timeouts = 0\n  let mismatches = 0\n  let totalBytes = 0\n  let totalRequests = 0\n  let totalCompletedRequests = 0\n  let samples = 0\n  let resets = 0\n  const amount = opts.amount\n  let stop = false\n  let restart = true\n  let numRunning = opts.connections\n  let startTime = Date.now()\n  const includeErrorStats = !opts.excludeErrorStats\n\n  opts.url = ofURL(opts.url).map((url) => {\n    if (url.indexOf('http') !== 0) return 'http://' + url\n    return url\n  })\n\n  const urls = ofURL(opts.url, true).map(url => {\n    if (url.indexOf('http') !== 0) url = 'http://' + url\n    url = URL.parse(url) // eslint-disable-line n/no-deprecated-api\n\n    // copy over fields so that the client\n    // performs the right HTTP requests\n    url.pipelining = opts.pipelining\n    url.method = opts.method\n    url.body = opts.form ? opts.form.getBuffer() : opts.body\n    url.headers = opts.form ? Object.assign({}, opts.headers, opts.form.getHeaders()) : opts.headers\n    url.setupClient = opts.setupClient\n    url.verifyBody = opts.verifyBody\n    url.timeout = opts.timeout\n    url.origin = `${url.protocol}//${url.host}`\n    // only keep requests for that origin, or default to requests from options\n    url.requests = opts.harRequests.get(url.origin) || opts.requests\n    url.reconnectRate = opts.reconnectRate\n    url.responseMax = amount || opts.maxConnectionRequests || opts.maxOverallRequests\n    url.rate = opts.connectionRate || opts.overallRate\n    url.idReplacement = opts.idReplacement\n    url.socketPath = opts.socketPath\n    url.servername = opts.servername\n    url.expectBody = opts.expectBody\n\n    return url\n  })\n\n  let stopTimer\n  let clients = []\n  initialiseClients(clients)\n\n  if (!amount) {\n    stopTimer = setTimeout(() => {\n      stop = true\n    }, opts.duration * 1000)\n  }\n\n  const second = reInterval(tickProgressBar, 1000)\n  const interval = reInterval(tickInterval, opts.sampleInt)\n\n  // put the start emit in a setImmediate so trackers can be added, etc.\n  setImmediate(() => { tracker.emit('start') })\n\n  function tickProgressBar () {\n    tracker.emit('tick', { counter, bytes })\n  }\n\n  function tickInterval () {\n    totalBytes += bytes\n    totalCompletedRequests += counter\n    samples += 1\n    requests.recordValue(counter)\n    throughput.recordValue(bytes)\n    counter = 0\n    bytes = 0\n\n    if (stop) {\n      if (stopTimer) clearTimeout(stopTimer)\n      tracker.emit('tick', { counter, bytes })\n      second.clear()\n      interval.clear()\n      clients.forEach((client) => client.destroy())\n      const result = {\n        latencies: encodeHist(latencies),\n        requests: encodeHist(requests),\n        throughput: encodeHist(throughput),\n        totalCompletedRequests,\n        totalRequests,\n        totalBytes,\n        samples,\n        errors,\n        timeouts,\n        mismatches,\n        non2xx: statusCodes[0] + statusCodes[2] + statusCodes[3] + statusCodes[4],\n        statusCodeStats,\n        resets,\n        duration: Math.round((Date.now() - startTime) / 10) / 100,\n        start: new Date(startTime),\n        finish: new Date()\n      }\n\n      statusCodes.forEach((code, index) => { result[(index + 1) + 'xx'] = code })\n\n      const resultObj = isMainThread && !opts.skipAggregateResult ? aggregateResult(result, opts, histograms) : result\n\n      if (opts.forever) {\n        // we don't call callback when in forever mode, so this is the\n        // only place we could notify user when each round finishes\n        tracker.emit('done', resultObj)\n      } else {\n        latencies.destroy()\n        requests.destroy()\n        throughput.destroy()\n        cb(null, resultObj)\n      }\n\n      const restartFn = () => {\n        stop = false\n        stopTimer = setTimeout(() => {\n          stop = true\n        }, opts.duration * 1000)\n        errors = 0\n        timeouts = 0\n        mismatches = 0\n        totalBytes = 0\n        totalRequests = 0\n        totalCompletedRequests = 0\n        resets = 0\n        statusCodes.fill(0)\n        requests.reset()\n        latencies.reset()\n        throughput.reset()\n        startTime = Date.now()\n\n        // reinitialise clients\n        if (opts.overallRate && (opts.overallRate < opts.connections)) opts.connections = opts.overallRate\n        clients = []\n        initialiseClients(clients)\n\n        interval.reschedule(1000)\n        tracker.emit('start')\n      }\n\n      // the restart function\n      setImmediate(() => {\n        if (opts.forever && restart && isMainThread) restartFn()\n      })\n    }\n  }\n\n  function initialiseClients (clients) {\n    for (let i = 0; i < opts.connections; i++) {\n      const url = urls[i % urls.length]\n      if (!amount && !opts.maxConnectionRequests && opts.maxOverallRequests) {\n        url.responseMax = distributeNums(opts.maxOverallRequests, i)\n      }\n      if (amount) {\n        url.responseMax = distributeNums(amount, i)\n        if (url.responseMax === 0) {\n          throw Error('connections cannot be greater than amount')\n        }\n      }\n      if (!opts.connectionRate && opts.overallRate) {\n        url.rate = distributeNums(opts.overallRate, i)\n      }\n      if (opts.initialContext) {\n        url.initialContext = opts.initialContext\n      }\n\n      if (opts.tlsOptions) {\n        url.tlsOptions = opts.tlsOptions\n      }\n\n      const client = new Client(url)\n      client.on('response', onResponse)\n      client.on('connError', onError)\n      client.on('mismatch', onExpectMismatch)\n      client.on('reset', () => { resets++ })\n      client.on('timeout', onTimeout)\n      client.on('request', () => { totalRequests++ })\n      client.on('done', onDone)\n      clients.push(client)\n\n      // we will miss the initial request emits because the client emits request on construction\n      totalRequests += url.pipelining < url.rate ? url.rate : url.pipelining\n    }\n\n    function distributeNums (x, i) {\n      return (Math.floor(x / opts.connections) + (((i + 1) <= (x % opts.connections)) ? 1 : 0))\n    }\n\n    function onResponse (statusCode, resBytes, responseTime, rate) {\n      tracker.emit('response', this, statusCode, resBytes, responseTime)\n      const codeIndex = Math.floor(parseInt(statusCode) / 100) - 1\n      statusCodes[codeIndex] += 1\n\n      if (!statusCodeStats[statusCode]) {\n        statusCodeStats[statusCode] = { count: 1 }\n      } else {\n        statusCodeStats[statusCode].count++\n      }\n\n      // only recordValue 2xx latencies\n      if (codeIndex === 1 || includeErrorStats) {\n        if (rate && !opts.ignoreCoordinatedOmission) {\n          latencies.recordValueWithExpectedInterval(responseTime, Math.ceil(1 / rate))\n        } else {\n          latencies.recordValue(responseTime)\n        }\n      }\n      if (codeIndex === 1 || includeErrorStats) bytes += resBytes\n      counter++\n    }\n\n    function onError (error) {\n      for (let i = 0; i < opts.pipelining; i++) tracker.emit('reqError', error)\n      errors++\n      if (opts.debug) console.error(error)\n      if (opts.bailout && errors >= opts.bailout) stop = true\n    }\n\n    function onExpectMismatch (bpdyStr) {\n      for (let i = 0; i < opts.pipelining; i++) {\n        tracker.emit('reqMismatch', bpdyStr)\n      }\n\n      mismatches++\n      if (opts.bailout && mismatches >= opts.bailout) stop = true\n    }\n\n    // treat a timeout as a special type of error\n    function onTimeout () {\n      const error = new Error('request timed out')\n      for (let i = 0; i < opts.pipelining; i++) tracker.emit('reqError', error)\n      errors++\n      timeouts++\n      if (opts.bailout && errors >= opts.bailout) stop = true\n    }\n\n    function onDone () {\n      if (!--numRunning) stop = true\n    }\n  }\n\n  tracker.stop = () => {\n    stop = true\n    restart = false\n  }\n\n  return tracker\n} // run\n\nmodule.exports = run\n"
  },
  {
    "path": "lib/runTracker.js",
    "content": "'use strict'\n\nconst EE = require('events').EventEmitter\nconst run = require('./run')\nconst noop = require('./noop')\n\nfunction runTracker (opts, tracker, cb) {\n  cb = cb || noop\n  tracker = tracker || new EE()\n\n  run(opts, tracker, cb)\n\n  return tracker\n}\n\nmodule.exports = runTracker\n"
  },
  {
    "path": "lib/subargAliases.js",
    "content": "'use strict'\n\nconst clone = require('lodash.clonedeep')\n\nconst subArgAlias = {\n  warmup: {\n    c: 'connections',\n    d: 'duration'\n  }\n}\n\n// Expects args to have already been processed by subarg\nfunction generateSubargAliases (args) {\n  const aliasedArgs = clone(args)\n\n  function isParentAliasInArgs (argKey) {\n    return aliasedArgs[argKey]\n  }\n\n  function isSubargAnAlias (parentAlias, subArg) {\n    return parentAlias[subArg]\n  }\n\n  function mapAliasForSubarg (parentAlias, parentKey) {\n    const parentArgs = aliasedArgs[parentKey]\n    for (const subArg in parentArgs) {\n      if (isSubargAnAlias(parentAlias, subArg)) {\n        parentArgs[parentAlias[subArg]] = parentArgs[subArg]\n      }\n    }\n  }\n\n  for (const parentKey in subArgAlias) {\n    const parentAlias = subArgAlias[parentKey]\n    if (isParentAliasInArgs(parentKey)) {\n      mapAliasForSubarg(parentAlias, parentKey)\n    }\n  }\n\n  return aliasedArgs\n}\n\nmodule.exports = generateSubargAliases\n"
  },
  {
    "path": "lib/url.js",
    "content": "'use strict'\n\n/**\n * check the url is not an empty string or empty array\n * @param url\n */\nfunction checkURL (url) {\n  return (typeof url === 'string' && url) ||\n    (Array.isArray(url) && url.length > 0)\n}\n\n/**\n *\n * @param url\n * @param asArray\n * @returns\n */\nfunction ofURL (url, asArray) {\n  if (Array.isArray(url)) return url\n\n  if (typeof url === 'string') {\n    return {\n      map (fn) {\n        if (asArray) return [fn(url)]\n        return fn(url)\n      }\n    }\n  }\n\n  throw new Error('url should only be a string or an array of string')\n}\n\nexports.checkURL = checkURL\nexports.ofURL = ofURL\n"
  },
  {
    "path": "lib/util.js",
    "content": "'use strict'\n\nconst semver = require('semver')\nconst hasWorkerSupport = semver.gte(process.versions.node, '11.7.0')\n\nfunction getPropertyCaseInsensitive (obj, key) {\n  for (const objKey of Object.keys(obj)) {\n    if (objKey.toLowerCase() === key.toLowerCase()) return obj[objKey]\n  }\n}\n\nmodule.exports = { hasWorkerSupport, getPropertyCaseInsensitive }\n"
  },
  {
    "path": "lib/validate.js",
    "content": "'use strict'\n\nconst defaultOptions = require('./defaultOptions')\nconst timestring = require('timestring')\nconst { checkURL } = require('./url')\nconst multipart = require('./multipart')\nconst { parseHAR } = require('./parseHAR')\nconst { hasWorkerSupport } = require('./util')\n\nconst isValidFn = (opt) => (!opt || typeof opt === 'function' || typeof opt === 'string')\n\nconst lessThanOneError = (label) => new Error(`${label} can not be less than 1`)\nconst greaterThanZeroError = (label) => new Error(`${label} must be greater than 0`)\nconst minIfPresent = (val, min) => val !== null && val < min\n\nfunction safeRequire (path) {\n  if (typeof path === 'string') {\n    try {\n      return require(path)\n    } catch (err) {}\n  }\n\n  return path\n}\n\nfunction defaultOpts (opts) {\n  const setupClient = opts.workers ? opts.setupClient : safeRequire(opts.setupClient)\n  const verifyBody = opts.workers ? opts.verifyBody : safeRequire(opts.verifyBody)\n\n  const requests = opts.requests\n    ? opts.requests.map((r) => {\n      const setupRequest = opts.workers ? r.setupRequest : safeRequire(r.setupRequest)\n      const onResponse = opts.workers ? r.onResponse : safeRequire(r.onResponse)\n\n      return {\n        ...r,\n        ...(setupRequest ? { setupRequest } : undefined),\n        ...(onResponse ? { onResponse } : undefined)\n      }\n    })\n    : undefined\n\n  return {\n    ...defaultOptions,\n    ...opts,\n    ...(setupClient ? { setupClient } : undefined),\n    ...(verifyBody ? { verifyBody } : undefined),\n    ...(requests ? { requests } : undefined)\n  }\n}\n\nmodule.exports = function validateOpts (opts, cbPassedIn) {\n  if (opts.workers && !hasWorkerSupport) return new Error('Please use node >= 11.7.0 for workers support')\n  // these need to be validated before defaulting\n  if (minIfPresent(opts.bailout, 1)) return lessThanOneError('bailout threshold')\n  if (minIfPresent(opts.connectionRate, 1)) return lessThanOneError('connectionRate')\n  if (minIfPresent(opts.overallRate, 1)) return lessThanOneError('bailout overallRate')\n  if (minIfPresent(opts.amount, 1)) return lessThanOneError('amount')\n  if (minIfPresent(opts.maxConnectionRequests, 1)) return lessThanOneError('maxConnectionRequests')\n  if (minIfPresent(opts.maxOverallRequests, 1)) return lessThanOneError('maxOverallRequests')\n\n  if (opts.form) {\n    opts.method = opts.method || 'POST'\n  }\n\n  // fill in defaults after\n  opts = defaultOpts(opts)\n\n  if (opts.json === true) {\n    opts.renderProgressBar = opts.renderResultsTable = opts.renderLatencyTable = false\n  }\n\n  if (opts.requests) {\n    if (opts.requests.some(r => !isValidFn(r.setupRequest))) {\n      return new Error('Invalid option setupRequest, please provide a function (or file path when in workers mode)')\n    }\n\n    if (opts.requests.some(r => !isValidFn(r.onResponse))) {\n      return new Error('Invalid option onResponse, please provide a function (or file path when in workers mode)')\n    }\n  }\n\n  if (!isValidFn(opts.setupClient)) {\n    return new Error('Invalid option setupClient, please provide a function (or file path when in workers mode)')\n  }\n\n  if (!isValidFn(opts.verifyBody)) {\n    return new Error('Invalid option verifyBody, please provide a function (or file path when in workers mode)')\n  }\n\n  if (!checkURL(opts.url) && !opts.socketPath) {\n    return new Error('url or socketPath option required')\n  }\n\n  if (typeof opts.duration === 'string') {\n    if (/[a-zA-Z]/.exec(opts.duration)) {\n      try {\n        opts.duration = timestring(opts.duration)\n      } catch (error) {\n        return error\n      }\n    } else {\n      opts.duration = Number(opts.duration.trim())\n    }\n  }\n\n  if (typeof opts.duration !== 'number') {\n    return new Error('duration entered was in an invalid format')\n  }\n\n  if (opts.duration < 0) {\n    return new Error('duration can not be less than 0')\n  }\n\n  opts.sampleInt = parseFloat(opts.sampleInt)\n\n  if (isNaN(opts.sampleInt)) {\n    return new Error('sample interval entered was in an invalid format')\n  }\n\n  if (opts.sampleInt < 0) {\n    return new Error('sample interval can not be less than 0')\n  }\n\n  if (opts.expectBody && opts.requests !== defaultOptions.requests) {\n    return new Error('expectBody cannot be used in conjunction with requests')\n  }\n\n  if (opts.form) {\n    try {\n      // Parse multipart upfront to make sure there's no errors\n      const data = multipart(opts.form)\n      opts.form = opts.workers ? opts.form : data // but use parsed data only if not in workers mode\n    } catch (error) {\n      return error\n    }\n  }\n\n  opts.harRequests = new Map()\n  if (opts.har) {\n    try {\n      opts.harRequests = parseHAR(opts.har)\n    } catch (error) {\n      return error\n    }\n  }\n\n  if (opts.connections < 1) return lessThanOneError('connections')\n  if (opts.pipelining < 1) return lessThanOneError('pipelining factor')\n  if (opts.timeout < 1) return greaterThanZeroError('timeout')\n\n  if (opts.ignoreCoordinatedOmission && !opts.connectionRate && !opts.overallRate) {\n    return new Error('ignoreCoordinatedOmission makes no sense without connectionRate or overallRate')\n  }\n\n  if (opts.forever && cbPassedIn) {\n    return new Error('should not use the callback parameter when the `forever` option is set to true. Use the `done` event on this event emitter')\n  }\n\n  if (opts.forever && opts.workers) {\n    return new Error('Using `forever` option isn\\'t currently supported with workers')\n  }\n\n  return opts\n}\n"
  },
  {
    "path": "lib/worker.js",
    "content": "'use strict'\n\nconst { isMainThread, parentPort, workerData } = require('worker_threads')\nconst multipart = require('./multipart')\nconst run = require('./run')\n\nconst createHist = (name) => ({\n  __custom: true,\n  recordValue: v => updateHist(name, v),\n  destroy: () => {},\n  reset: () => resetHist(name)\n})\n\nconst updateHist = (name, value) => {\n  parentPort.postMessage({\n    cmd: 'UPDATE_HIST',\n    data: { name, value }\n  })\n}\n\nconst resetHist = (name) => {\n  parentPort.postMessage({\n    cmd: 'RESET_HIST',\n    data: { name }\n  })\n}\n\nfunction runTracker (opts, cb) {\n  const tracker = run({\n    ...opts,\n    ...(opts.form ? { form: multipart(opts.form) } : undefined),\n    ...(opts.setupClient ? { setupClient: require(opts.setupClient) } : undefined),\n    ...(opts.verifyBody ? { verifyBody: require(opts.verifyBody) } : undefined),\n    requests: opts.requests\n      ? opts.requests.map(r => ({\n        ...r,\n        ...(r.setupRequest ? { setupRequest: require(r.setupRequest) } : undefined),\n        ...(r.onResponse ? { onResponse: require(r.onResponse) } : undefined)\n      }))\n      : undefined,\n    histograms: {\n      requests: createHist('requests'),\n      throughput: createHist('throughput')\n    }\n  }, null, cb)\n\n  tracker.on('tick', (data) => {\n    parentPort.postMessage({ cmd: 'TICK', data })\n  })\n\n  return {\n    stop: tracker.stop\n  }\n}\n\nif (!isMainThread) {\n  const { opts } = workerData\n  let tracker\n\n  parentPort.on('message', (msg) => {\n    const { cmd } = msg\n\n    if (cmd === 'START') {\n      tracker = runTracker(opts, (error, data) => {\n        parentPort.postMessage({ cmd: error ? 'ERROR' : 'RESULT', error, data })\n        parentPort.close()\n      })\n    } else if (cmd === 'STOP') {\n      tracker.stop()\n      parentPort.close()\n    }\n  })\n}\n"
  },
  {
    "path": "lib/worker_threads.js",
    "content": "'use strict'\n\nlet workerThreads = {}\n\ntry {\n  workerThreads = require('worker_threads')\n} catch (err) {\n  if (err) {\n    // we don't need the error but can't have catch block\n    // without err as node 8 doesn't support that\n  }\n\n  workerThreads = {\n    isMainThread: true\n  }\n}\n\nmodule.exports = workerThreads\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"autocannon\",\n  \"version\": \"8.0.0\",\n  \"description\": \"Fast HTTP benchmarking tool written in Node.js\",\n  \"main\": \"autocannon.js\",\n  \"bin\": {\n    \"autocannon\": \"autocannon.js\"\n  },\n  \"scripts\": {\n    \"test\": \"standard && tap test/serial/*.test.js test/*.test.js\",\n    \"standard:fix\": \"standard --fix\"\n  },\n  \"pre-commit\": [\n    \"test\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/mcollina/autocannon.git\"\n  },\n  \"keywords\": [\n    \"http\",\n    \"soak\",\n    \"load\",\n    \"fast\",\n    \"wrk\",\n    \"ab\",\n    \"test\"\n  ],\n  \"author\": \"Matteo Collina <hello@matteocollina.com>\",\n  \"contributors\": [\n    \"Glen Keane <glenkeane.94@gmail.com>\",\n    \"Donald Robertson <donaldarobertson89@gmail.com\",\n    \"Salman Mitha <SalmanMitha@gmail.com>\"\n  ],\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/mcollina/autocannon/issues\"\n  },\n  \"homepage\": \"https://github.com/mcollina/autocannon#readme\",\n  \"devDependencies\": {\n    \"ansi-regex\": \"^5.0.1\",\n    \"bl\": \"^6.0.0\",\n    \"busboy\": \"^0.3.1\",\n    \"pre-commit\": \"^1.1.2\",\n    \"proxyquire\": \"^2.1.3\",\n    \"sinon\": \"^15.0.0\",\n    \"split2\": \"^4.0.0\",\n    \"standard\": \"^17.0.0\",\n    \"tap\": \"^16.0.0\",\n    \"why-is-node-running\": \"^2.3.0\"\n  },\n  \"dependencies\": {\n    \"@minimistjs/subarg\": \"^1.0.0\",\n    \"chalk\": \"^4.1.0\",\n    \"char-spinner\": \"^1.0.1\",\n    \"cli-table3\": \"^0.6.0\",\n    \"color-support\": \"^1.1.1\",\n    \"cross-argv\": \"^2.0.0\",\n    \"form-data\": \"^4.0.0\",\n    \"has-async-hooks\": \"^1.0.0\",\n    \"hdr-histogram-js\": \"^3.0.0\",\n    \"hdr-histogram-percentiles-obj\": \"^3.0.0\",\n    \"http-parser-js\": \"^0.5.2\",\n    \"hyperid\": \"^3.0.0\",\n    \"lodash.chunk\": \"^4.2.0\",\n    \"lodash.clonedeep\": \"^4.5.0\",\n    \"lodash.flatten\": \"^4.4.0\",\n    \"manage-path\": \"^2.0.0\",\n    \"on-net-listen\": \"^1.1.1\",\n    \"pretty-bytes\": \"^5.4.1\",\n    \"progress\": \"^2.0.3\",\n    \"reinterval\": \"^1.1.0\",\n    \"retimer\": \"^3.0.0\",\n    \"semver\": \"^7.3.2\",\n    \"timestring\": \"^6.0.0\"\n  }\n}\n"
  },
  {
    "path": "samples/bench-multi-url.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nfunction createHandler (serverName) {\n  return function (req, res) {\n    console.log(serverName + ' received request')\n    res.end('hello world')\n  }\n}\n\nconst server1 = http.createServer(createHandler('server1'))\nconst server2 = http.createServer(createHandler('server2'))\n\nserver1.listen(0, startBench)\nserver2.listen(0, startBench)\n\nfunction startBench () {\n  const url = [\n    'http://localhost:' + server1.address().port,\n    'http://localhost:' + server2.address().port\n  ]\n\n  // same with run the follow command in cli\n  // autocannon -d 10 -c 2 http://localhost:xxxx http://localhost:yyyy\n  autocannon({\n    url,\n    // connection number should n times of the number of server\n    connections: 2,\n    duration: 10,\n    requests: [\n      {\n        method: 'GET',\n        path: '/'\n      }\n    ]\n  }, finishedBench)\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n    process.exit(1)\n  }\n}\n"
  },
  {
    "path": "samples/customise-individual-connection.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nlet connection = 0\n\nfunction handle (req, res) {\n  res.end('hello world')\n}\n\nfunction startBench () {\n  const url = 'http://localhost:' + server.address().port\n\n  autocannon({\n    url,\n    connections: 1000,\n    duration: 10,\n    setupClient\n  }, finishedBench)\n\n  function setupClient (client) {\n    client.setBody('connection number', connection++)\n  }\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/customise-verifyBody-workers.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst path = require('path')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  res.end('hello world')\n}\n\nfunction startBench () {\n  const url = 'http://localhost:' + server.address().port\n\n  autocannon({\n    url,\n    connections: 1000,\n    duration: 10,\n    workers: 2,\n    verifyBody: path.join(__dirname, 'helpers', 'verify-body')\n  }, finishedBench)\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/customise-verifyBody.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  res.end('hello world')\n}\n\nfunction startBench () {\n  const url = 'http://localhost:' + server.address().port\n\n  autocannon({\n    url,\n    connections: 1000,\n    duration: 10,\n    verifyBody\n  }, finishedBench)\n\n  function verifyBody (body) {\n    return body.indexOf('<html>') > -1\n  }\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/helpers/on-response.js",
    "content": "'use strict'\n\nmodule.exports = (status, body, context) => {\n  if (status === 200) {\n    context.user = JSON.parse(body)\n  } // on error, you may abort the benchmark\n}\n"
  },
  {
    "path": "samples/helpers/setup-request.js",
    "content": "'use strict'\n\nmodule.exports = (req, context) => ({\n  ...req,\n  path: `/user/${context.user.id}`,\n  body: JSON.stringify({\n    ...context.user,\n    lastName: 'Doe'\n  })\n})\n"
  },
  {
    "path": "samples/helpers/verify-body.js",
    "content": "'use strict'\n\nmodule.exports = (body) => {\n  return true\n}\n"
  },
  {
    "path": "samples/init-context.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  const body = []\n  // this route handler simply returns whatever it gets from response\n  req\n    .on('data', chunk => body.push(chunk))\n    .on('end', () => res.end(Buffer.concat(body)))\n}\n\nfunction startBench () {\n  const url = 'http://localhost:' + server.address().port\n\n  autocannon({\n    url,\n    connections: 1,\n    amount: 1,\n    initialContext: { user: { firstName: 'Salman' } },\n    requests: [\n      {\n        // use data from context\n        method: 'PUT',\n        setupRequest: (req, context) => ({\n          ...req,\n          path: `/user/${context.user.id}`,\n          body: JSON.stringify({\n            ...context.user,\n            lastName: 'Mitha'\n          })\n        })\n      }\n    ]\n  }, finishedBench)\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/modifying-request.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  res.end('hello world')\n}\n\nfunction startBench () {\n  const url = 'http://localhost:' + server.address().port\n\n  const instance = autocannon({\n    url,\n    connections: 1000,\n    duration: 10\n  }, finishedBench)\n\n  let message = 0\n  // modify the body on future requests\n  instance.on('response', function (client, statusCode, returnBytes, responseTime) {\n    client.setBody('message ' + message++)\n  })\n\n  let headers = 0\n  // modify the headers on future requests\n  // this wipes any existing headers out with the new ones\n  instance.on('response', function (client, statusCode, returnBytes, responseTime) {\n    const newHeaders = {}\n    newHeaders[`header${headers++}`] = `headerValue${headers++}`\n    client.setHeaders(newHeaders)\n  })\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/request-context-workers.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst path = require('path')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  const body = []\n  // this route handler simply returns whatever it gets from response\n  req\n    .on('data', chunk => body.push(chunk))\n    .on('end', () => res.end(Buffer.concat(body)))\n}\n\nfunction startBench () {\n  const url = 'http://localhost:' + server.address().port\n\n  autocannon({\n    url,\n    duration: 2,\n    workers: 2,\n    requests: [\n      {\n        // let's create a new user\n        method: 'POST',\n        path: '/users',\n        body: JSON.stringify({ firstName: 'Jane', id: 10 }),\n        onResponse: path.join(__dirname, 'helpers', 'on-response')\n      },\n      {\n        // now we'll give them a last name\n        method: 'PUT',\n        setupRequest: path.join(__dirname, 'helpers', 'setup-request')\n      }\n    ]\n  }, finishedBench)\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/request-context.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  const body = []\n  // this route handler simply returns whatever it gets from response\n  req\n    .on('data', chunk => body.push(chunk))\n    .on('end', () => res.end(Buffer.concat(body)))\n}\n\nfunction startBench () {\n  const url = 'http://localhost:' + server.address().port\n\n  autocannon({\n    url,\n    requests: [\n      {\n        // let's create a new user\n        method: 'POST',\n        path: '/users',\n        body: JSON.stringify({ firstName: 'Jane', id: 10 }),\n        onResponse: (status, body, context) => {\n          if (status === 200) {\n            context.user = JSON.parse(body)\n          } // on error, you may abort the benchmark\n        }\n      },\n      {\n        // now we'll give them a last name\n        method: 'PUT',\n        setupRequest: (req, context) => ({\n          ...req,\n          path: `/user/${context.user.id}`,\n          body: JSON.stringify({\n            ...context.user,\n            lastName: 'Doe'\n          })\n        })\n      }\n    ]\n  }, finishedBench)\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/requests-sample.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  res.end('hello world')\n}\n\nfunction startBench () {\n  const url = 'http://localhost:' + server.address().port\n\n  autocannon({\n    url,\n    connections: 1000,\n    duration: 10,\n    headers: {\n      // by default we add an auth token to all requests\n      auth: 'A Pregenerated auth token'\n    },\n    requests: [\n      {\n        method: 'POST', // this should be a post for logging in\n        path: '/login',\n        body: 'valid login details',\n        // overwrite our default headers,\n        // so we don't add an auth token\n        // for this request\n        headers: {}\n      },\n      {\n        path: '/mySecretDetails'\n        // this will automatically add the pregenerated auth token\n      },\n      {\n        method: 'GET', // this should be a put for modifying secret details\n        path: '/mySecretDetails',\n        headers: { // let submit some json?\n          'Content-type': 'application/json; charset=utf-8'\n        },\n        // we need to stringify the json first\n        body: JSON.stringify({\n          name: 'my new name'\n        }),\n        setupRequest: reqData => {\n          reqData.method = 'PUT' // we are overriding the method 'GET' to 'PUT' here\n          return reqData\n        }\n      }\n    ]\n  }, finishedBench)\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/track-run-workers.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  res.end('hello world')\n}\n\nfunction startBench () {\n  const instance = autocannon({\n    connections: 100,\n    duration: 2,\n    url: 'http://localhost:' + server.address().port,\n    workers: 2\n  }, finishedBench)\n\n  autocannon.track(instance)\n\n  // this is used to kill the instance on CTRL-C\n  process.once('SIGINT', () => {\n    instance.stop()\n  })\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/track-run.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  res.end('hello world')\n}\n\nfunction startBench () {\n  const instance = autocannon({\n    url: 'http://localhost:' + server.address().port\n  }, finishedBench)\n\n  autocannon.track(instance)\n\n  // this is used to kill the instance on CTRL-C\n  process.once('SIGINT', () => {\n    instance.stop()\n  })\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "samples/using-id-replacement.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst autocannon = require('../autocannon')\n\nconst server = http.createServer(handle)\n\nserver.listen(0, startBench)\n\nfunction handle (req, res) {\n  res.end('hello world')\n}\n\nfunction startBench () {\n  const url = 'http://localhost:' + server.address().port\n\n  autocannon({\n    url,\n    connections: 1000,\n    duration: 10,\n    requests: [\n      {\n        method: 'POST',\n        path: '/register',\n        headers: {\n          'Content-type': 'application/json; charset=utf-8'\n        },\n        body: JSON.stringify({\n          name: 'New User',\n          email: 'new-[<id>]@user.com' // [<id>] will be replaced with generated HyperID at run time\n        })\n      }\n    ],\n    idReplacement: true\n  }, finishedBench)\n\n  function finishedBench (err, res) {\n    console.log('finished bench', err, res)\n  }\n}\n"
  },
  {
    "path": "server.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst https = require('https')\nconst fs = require('fs')\nconst path = require('path')\n\nconst options = {\n  key: fs.readFileSync(path.join(__dirname, 'test', '/key.pem')),\n  cert: fs.readFileSync(path.join(__dirname, 'test', '/cert.pem')),\n  passphrase: 'test'\n}\nconst server = http.createServer(handle)\nconst server2 = https.createServer(options, handle)\n\nserver.listen(3000)\nserver2.listen(3001)\n\nfunction handle (req, res) {\n  res.end('hello world')\n}\n"
  },
  {
    "path": "test/aggregateResult.test.js",
    "content": "const { test } = require('tap')\nconst { startServer } = require('./helper')\nconst autocannon = require('../autocannon')\nconst aggregateResult = autocannon.aggregateResult\nconst server = startServer()\nconst url = 'http://localhost:' + server.address().port\n\ntest('exec separate autocannon instances with skipAggregateResult, then aggregateResult afterwards', async (t) => {\n  t.plan(2)\n\n  const opts = {\n    url,\n    connections: 1,\n    maxOverallRequests: 10,\n    skipAggregateResult: true\n  }\n\n  const results = await Promise.all([\n    autocannon(opts),\n    autocannon(opts)\n  ])\n\n  const aggregateResults = aggregateResult(results, opts)\n\n  t.equal(aggregateResults['2xx'], 20)\n  t.equal(aggregateResults.requests.total, 20)\n})\n\ntest('aggregateResult must be passed opts with at least a URL or socketPath property', async (t) => {\n  t.plan(2)\n  t.throws(() => aggregateResult([]), 'url or socketPath option required')\n  t.throws(() => aggregateResult([], {}), 'url or socketPath option required')\n})\n"
  },
  {
    "path": "test/argumentParsing.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst Autocannon = require('../autocannon')\nconst fs = require('fs')\n\ntest('parse argument', (t) => {\n  t.plan(4)\n\n  const args = Autocannon.parseArguments([\n    '-H', 'X-Http-Method-Override=GET',\n    '-m', 'POST',\n    '-b', 'the body',\n    'http://localhost/foo/bar'\n  ])\n\n  t.equal(args.url, 'http://localhost/foo/bar')\n  t.strictSame(args.headers, { 'X-Http-Method-Override': 'GET' })\n  t.equal(args.method, 'POST')\n  t.equal(args.body, 'the body')\n})\n\ntest('parse argument with multiple headers', (t) => {\n  t.plan(3)\n\n  const args = Autocannon.parseArguments([\n    '-H', 'header1=value1',\n    '-H', 'header2=value2',\n    '-H', 'header3=value3',\n    '-H', 'header4=value4',\n    '-H', 'header5=value5',\n    'http://localhost/foo/bar'\n  ])\n\n  t.equal(args.url, 'http://localhost/foo/bar')\n  t.strictSame(args.headers, {\n    header1: 'value1',\n    header2: 'value2',\n    header3: 'value3',\n    header4: 'value4',\n    header5: 'value5'\n  })\n  t.equal(args.method, 'GET')\n})\n\ntest('parse argument with multiple complex headers', (t) => {\n  t.plan(3)\n\n  const args = Autocannon.parseArguments([\n    '-H', 'header1=value1;data=asd',\n    '-H', 'header2=value2;data=asd',\n    '-H', 'header3=value3;data=asd',\n    '-H', 'header4=value4;data=asd',\n    '-H', 'header5=value5;data=asd',\n    'http://localhost/foo/bar'\n  ])\n\n  t.equal(args.url, 'http://localhost/foo/bar')\n  t.strictSame(args.headers, {\n    header1: 'value1;data=asd',\n    header2: 'value2;data=asd',\n    header3: 'value3;data=asd',\n    header4: 'value4;data=asd',\n    header5: 'value5;data=asd'\n  })\n  t.equal(args.method, 'GET')\n})\n\ntest('parse argument with multiple headers in standard notation', (t) => {\n  t.plan(3)\n\n  const args = Autocannon.parseArguments([\n    '-H', 'header1: value1',\n    '-H', 'header2: value2',\n    '-H', 'header3: value3',\n    '-H', 'header4: value4',\n    '-H', 'header5: value5',\n    'http://localhost/foo/bar'\n  ])\n\n  t.equal(args.url, 'http://localhost/foo/bar')\n  t.strictSame(args.headers, {\n    header1: ' value1',\n    header2: ' value2',\n    header3: ' value3',\n    header4: ' value4',\n    header5: ' value5'\n  })\n  t.equal(args.method, 'GET')\n})\n\ntest('parse argument with multiple complex headers in standard notation', (t) => {\n  t.plan(3)\n\n  const args = Autocannon.parseArguments([\n    '-H', 'header1:value1;data=asd',\n    '-H', 'header2:value2;data=asd',\n    '-H', 'header3:value3;data=asd',\n    '-H', 'header4:value4;data=asd',\n    '-H', 'header5:value5;data=asd',\n    'http://localhost/foo/bar'\n  ])\n\n  t.equal(args.url, 'http://localhost/foo/bar')\n  t.strictSame(args.headers, {\n    header1: 'value1;data=asd',\n    header2: 'value2;data=asd',\n    header3: 'value3;data=asd',\n    header4: 'value4;data=asd',\n    header5: 'value5;data=asd'\n  })\n  t.equal(args.method, 'GET')\n})\n\ntest('parse argument with \"=\" in value header', (t) => {\n  t.plan(1)\n\n  const args = Autocannon.parseArguments([\n    '-H', 'header1=foo=bar',\n    'http://localhost/foo/bar'\n  ])\n\n  t.strictSame(args.headers, {\n    header1: 'foo=bar'\n  })\n})\n\ntest('parse argument ending space in value header', (t) => {\n  t.plan(1)\n\n  const args = Autocannon.parseArguments([\n    '-H', 'header1=foo=bar ',\n    'http://localhost/foo/bar'\n  ])\n\n  t.strictSame(args.headers, {\n    header1: 'foo=bar '\n  })\n})\n\ntest('parse argument with \":\" in value header', (t) => {\n  t.plan(1)\n\n  const args = Autocannon.parseArguments([\n    '-H', 'header1=foo:bar',\n    'http://localhost/foo/bar'\n  ])\n\n  t.strictSame(args.headers, {\n    header1: 'foo:bar'\n  })\n})\n\ntest('parse argument not correctly formatted header', (t) => {\n  t.plan(1)\n\n  t.throws(() => {\n    Autocannon.parseArguments([\n      '-H', 'header1',\n      'http://localhost/foo/bar'\n    ])\n  }, /An HTTP header was not correctly formatted/)\n})\n\ntest('parse argument with multiple url', (t) => {\n  t.plan(2)\n  const args = Autocannon.parseArguments([\n    'localhost/foo/bar',\n    'http://localhost/baz/qux'\n  ])\n\n  t.equal(args.url[0], 'http://localhost/foo/bar')\n  t.equal(args.url[1], 'http://localhost/baz/qux')\n})\n\ntest('parse argument with input file and multiple workers', (t) => {\n  t.plan(3)\n\n  const inputPath = 'help.txt'\n  const args = Autocannon.parseArguments([\n    '-m', 'POST',\n    '-w', '2',\n    '-a', 10,\n    '-i', inputPath,\n    '-H', 'Content-Type=application/json',\n    'http://localhost/foo/bar'\n  ])\n\n  t.equal(args.url, 'http://localhost/foo/bar')\n  t.equal(args.method, 'POST')\n  t.equal(args.body, fs.readFileSync(inputPath, 'utf8'))\n})\n\ntest('parse argument with cert, key and multiple ca paths', (t) => {\n  t.plan(5)\n\n  const certPath = 'test/cert.pem'\n  const keyPath = 'test/key.pem'\n  const caPath1 = 'help.txt'\n  const caPath2 = 'package.json'\n  const args = Autocannon.parseArguments([\n    '-m', 'POST',\n    '--cert', certPath,\n    '--key', keyPath,\n    '--ca', '[', caPath1, caPath2, ']',\n    'http://localhost/foo/bar'\n  ])\n\n  t.equal(args.url, 'http://localhost/foo/bar')\n  t.equal(args.method, 'POST')\n  t.same(args.tlsOptions.cert, fs.readFileSync(certPath))\n  t.same(args.tlsOptions.key, fs.readFileSync(keyPath))\n  t.same(args.tlsOptions.ca, [fs.readFileSync(caPath1), fs.readFileSync(caPath2)])\n})\n\ntest('parse argument with cert, key and single ca path', (t) => {\n  t.plan(5)\n\n  const certPath = 'test/cert.pem'\n  const keyPath = 'test/key.pem'\n  const caPath = 'help.txt'\n  const args = Autocannon.parseArguments([\n    '-m', 'POST',\n    '--cert', certPath,\n    '--key', keyPath,\n    '--ca', caPath,\n    'http://localhost/foo/bar'\n  ])\n\n  t.equal(args.url, 'http://localhost/foo/bar')\n  t.equal(args.method, 'POST')\n  t.same(args.tlsOptions.cert, fs.readFileSync(certPath))\n  t.same(args.tlsOptions.key, fs.readFileSync(keyPath))\n  t.same(args.tlsOptions.ca, [fs.readFileSync(caPath)])\n})\n"
  },
  {
    "path": "test/basic-auth.test.js",
    "content": "'use strict'\n\nconst t = require('tap')\nconst split = require('split2')\nconst path = require('path')\nconst childProcess = require('child_process')\nconst helper = require('./helper')\n\nconst lines = [\n  /Running 1s test @ .*$/,\n  /10 connections.*$/,\n  /$/,\n  /.*/,\n  /$/,\n  /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\n  /.*/,\n  /Latency.*$/,\n  /$/,\n  /.*/,\n  /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\n  /.*/,\n  /Req\\/Sec.*$/,\n  /.*/,\n  /Bytes\\/Sec.*$/,\n  /.*/,\n  /$/,\n  /Req\\/Bytes counts sampled once per second.*$/,\n  /# of samples: 10*$/,\n  /$/,\n  /.* requests in ([0-9]|\\.)+s, .* read/\n]\n\nt.plan(lines.length * 2)\n\nconst server = helper.startBasicAuthServer()\nconst url = 'http://foo:bar@localhost:' + server.address().port\n\nconst child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-d', '1', url], {\n  cwd: __dirname,\n  env: process.env,\n  stdio: ['ignore', 'pipe', 'pipe'],\n  detached: false\n})\n\nt.teardown(() => {\n  child.kill()\n})\n\nchild\n  .stderr\n  .pipe(split())\n  .on('data', (line) => {\n    const regexp = lines.shift()\n    t.ok(regexp, 'we are expecting this line')\n    t.ok(regexp.test(line), 'line matches ' + regexp)\n  })\n"
  },
  {
    "path": "test/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIEVzCCAz+gAwIBAgIJAJn02lrTTFjxMA0GCSqGSIb3DQEBBQUAMHoxCzAJBgNV\nBAYTAklFMRIwEAYDVQQIEwl3YXRlcmZvcmQxDTALBgNVBAcTBHRlc3QxDTALBgNV\nBAoTBHRlc3QxDTALBgNVBAsTBHRlc3QxDTALBgNVBAMTBHRlc3QxGzAZBgkqhkiG\n9w0BCQEWDHRlc0B0ZXN0LmNvbTAeFw0xNjA2MjcxNDE4MDdaFw0xOTAzMjMxNDE4\nMDdaMHoxCzAJBgNVBAYTAklFMRIwEAYDVQQIEwl3YXRlcmZvcmQxDTALBgNVBAcT\nBHRlc3QxDTALBgNVBAoTBHRlc3QxDTALBgNVBAsTBHRlc3QxDTALBgNVBAMTBHRl\nc3QxGzAZBgkqhkiG9w0BCQEWDHRlc0B0ZXN0LmNvbTCCASIwDQYJKoZIhvcNAQEB\nBQADggEPADCCAQoCggEBAK0PhkEuPPEQNc1/96rapuEazVaa5p74QAn4PNOPIKaz\nXWyLheBF78N320w6jB4eqAe3o6XMtt28iK+q+HejLZt7v+m6c7lHDtfcLSG8CEJ3\ndfwR/iOfCLRlDeZyWvxouf9/s3FSAM5VqKb9kmc/Pt2+opWlX1cZvdfkg/lzSHUu\nFwmuxOAONKt2dPiEvDSiSs99Kv0+jSgMmy+4D8LGyvxFCQu67bh6a2zGEEYAcAib\nRpw+Fb/AK8VYPW528SaWHRT7CcDgzdXaMfos3EWOQ/Cc0Q+MgqVfSmqTEUPXAc41\nY4Lvvl5GSHQ4lve3jIR05xenxcMIZ8BP7fJ3BfjXCxsCAwEAAaOB3zCB3DAdBgNV\nHQ4EFgQUYtl9YCe7XZ4F0MvA627f+BOJoVYwgawGA1UdIwSBpDCBoYAUYtl9YCe7\nXZ4F0MvA627f+BOJoVahfqR8MHoxCzAJBgNVBAYTAklFMRIwEAYDVQQIEwl3YXRl\ncmZvcmQxDTALBgNVBAcTBHRlc3QxDTALBgNVBAoTBHRlc3QxDTALBgNVBAsTBHRl\nc3QxDTALBgNVBAMTBHRlc3QxGzAZBgkqhkiG9w0BCQEWDHRlc0B0ZXN0LmNvbYIJ\nAJn02lrTTFjxMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAJN7pnlv\nSascD2V0I+9wirpuuNnfUP5YCFaAjky4DREV+DRPXEL2tzTQqsEeFts8BHBH+Jz3\nytYP5NZSjuToF9czu8v3+mPCSqzdOFKruJbl/lAokLJWan8Z3qfXWZQL79C2I7Ih\nhSBnH/O+jZz9FPRJ2ydR8DB0LdGVKkQFvZynPZOh7D4NKvrEgFad4p6EBFshO+8N\n1ALfR/2mrJOkBHfHPVWMmy6DoXWyVijPuLaa+l2TzdQJycl6CAJw6F7tPoO75qKY\nMAcIKOW5F9Zv7I3aqmoLDOaOh43FeT2JLvODe2TIaytWckoFesGadEgvAzCAXC4r\nArqQX2nVUdasOnQ=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/cli-ipc.test.js",
    "content": "'use strict'\n\nconst t = require('tap')\nconst split = require('split2')\nconst os = require('os')\nconst path = require('path')\nconst childProcess = require('child_process')\nconst helper = require('./helper')\n\nconst win = process.platform === 'win32'\n\nconst lines = [\n  /Running 1s test @ http:\\/\\/example.com\\/foo \\([^)]*\\)$/,\n  /10 connections.*$/,\n  /$/,\n  /.*/,\n  /$/,\n  /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\n  /.*/,\n  /Latency.*$/,\n  /$/,\n  /.*/,\n  /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\n  /.*/,\n  /Req\\/Sec.*$/,\n  /.*/,\n  /Bytes\\/Sec.*$/,\n  /.*/,\n  /$/,\n  /Req\\/Bytes counts sampled once per second.*$/,\n  /# of samples: 10*$/,\n  /$/,\n  /.* requests in ([0-9]|\\.)+s, .* read/\n]\n\nif (!win) {\n  // If not Windows we can predict exactly how many lines there will be. On\n  // Windows we rely on t.end() being called.\n  t.plan(lines.length)\n}\n\nt.autoend(false)\nt.teardown(function () {\n  child.kill()\n})\n\nconst socketPath = win\n  ? path.join('\\\\\\\\?\\\\pipe', process.cwd(), 'autocannon-' + Date.now())\n  : path.join(os.tmpdir(), 'autocannon-' + Date.now() + '.sock')\n\nhelper.startServer({ socketPath })\n\nconst child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-d', '1', '-S', socketPath, 'example.com/foo'], {\n  cwd: __dirname,\n  env: process.env,\n  stdio: ['ignore', 'pipe', 'pipe'],\n  detached: false\n})\n\n// For handling the last line on Windows\nlet errorLine = false\nlet failsafeTimer\n\nchild\n  .stderr\n  .pipe(split())\n  .on('data', (line) => {\n    let regexp = lines.shift()\n    const lastLine = lines.length === 0\n\n    if (regexp) {\n      t.ok(regexp.test(line), 'line matches ' + regexp)\n\n      if (lastLine && win) {\n        // We can't be sure the error line is outputted on Windows, so in case\n        // this really is the last line, we'll set a timer to auto-end the test\n        // in case there are no more lines.\n        failsafeTimer = setTimeout(function () {\n          t.end()\n        }, 1000)\n      }\n    } else if (!errorLine && win) {\n      // On Windows a few errors are expected. We'll accept a 1% error rate on\n      // the pipe.\n      errorLine = true\n      clearTimeout(failsafeTimer)\n      regexp = /^(\\d+) errors \\(0 timeouts\\)$/\n      const match = line.match(regexp)\n      t.ok(match, 'line matches ' + regexp)\n      const errors = Number(match[1])\n      t.ok(errors / 15000 < 0.01, `should have less than 1% errors on Windows (had ${errors} errors)`)\n      t.end()\n    } else {\n      throw new Error('Unexpected line: ' + JSON.stringify(line))\n    }\n  })\n"
  },
  {
    "path": "test/cli.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst split = require('split2')\nconst path = require('path')\nconst fs = require('fs')\nconst os = require('os')\nconst childProcess = require('child_process')\nconst helper = require('./helper')\nconst { hasWorkerSupport } = require('../lib/util')\n\ntest('should run benchmark against server', (t) => {\n  const lines = [\n    /Running 1s test @ .*$/,\n    /10 connections.*$/,\n    /$/,\n    /.*/,\n    /$/,\n    /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\n    /.*/,\n    /Latency.*$/,\n    /$/,\n    /.*/,\n    /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\n    /.*/,\n    /Req\\/Sec.*$/,\n    /.*/,\n    /Bytes\\/Sec.*$/,\n    /.*/,\n    /$/,\n    /Req\\/Bytes counts sampled once per second.*$/,\n    /# of samples: 1.*$/,\n    /$/,\n    /.* requests in ([0-9]|\\.)+s, .* read/\n  ]\n\n  t.plan(lines.length * 2)\n\n  const server = helper.startServer()\n  const url = 'http://localhost:' + server.address().port\n\n  const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-d', '1', url], {\n    cwd: __dirname,\n    env: process.env,\n    stdio: ['ignore', 'pipe', 'pipe'],\n    detached: false\n  })\n\n  t.teardown(() => {\n    try {\n      child.kill()\n    } catch {}\n  })\n\n  child\n    .stderr\n    .pipe(split())\n    .on('data', (line) => {\n      const regexp = lines.shift()\n      t.ok(regexp, 'we are expecting this line')\n      t.ok(regexp.test(line), 'line matches ' + regexp)\n    })\n    .on('end', t.end)\n})\n\ntest('should parse HAR file and run requests', (t) => {\n  const lines = [\n    /Running \\d+ requests test @ .*$/,\n    /1 connections.*$/,\n    /$/,\n    /.*/,\n    /$/,\n    /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\n    /.*/,\n    /Latency.*$/,\n    /$/,\n    /.*/,\n    /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\n    /.*/,\n    /Req\\/Sec.*$/,\n    /.*/,\n    /Bytes\\/Sec.*$/,\n    /.*/,\n    /$/,\n    /Req\\/Bytes counts sampled once per second.*$/,\n    /# of samples: 1.*$/,\n    /$/,\n    /.* requests in ([0-9]|\\.)+s, .* read/\n  ]\n\n  t.plan(lines.length)\n\n  const server = helper.startServer()\n  const url = `http://localhost:${server.address().port}`\n  const harPath = path.join(os.tmpdir(), 'autocannon-test.har')\n  const har = helper.customizeHAR('./fixtures/httpbin-simple-get.json', 'https://httpbin.org', url)\n  fs.writeFileSync(harPath, JSON.stringify(har))\n\n  const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-a', 4, '-c', 1, '--har', harPath, url], {\n    cwd: __dirname,\n    env: process.env,\n    stdio: ['ignore', 'pipe', 'pipe'],\n    detached: false\n  })\n\n  t.teardown(() => {\n    try {\n      child.kill()\n    } catch {}\n  })\n\n  child\n    .stderr\n    .pipe(split())\n    .on('data', (line) => {\n      const regexp = lines.shift()\n      t.ok(regexp.test(line), `\"${line}\" matches ${regexp}`)\n    })\n    .on('end', t.end)\n})\n\ntest('should throw on unknown HAR file', (t) => {\n  t.plan(1)\n\n  const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-a', 4, '-c', 1, '--har', 'does not exist', 'http://localhost'], {\n    cwd: __dirname,\n    env: process.env,\n    stdio: ['ignore', 'pipe', 'pipe'],\n    detached: false\n  })\n\n  t.teardown(() => {\n    try {\n      child.kill()\n    } catch {}\n  })\n\n  const lines = []\n  child\n    .stderr\n    .pipe(split())\n    .on('data', line => lines.push(line))\n    .on('end', () => {\n      const output = lines.join('\\n')\n      t.ok(output.includes('Error: Failed to load HAR file content: ENOENT'), `Unexpected output:\\n${output}`)\n      t.end()\n    })\n})\n\ntest('should throw on invalid HAR file', (t) => {\n  t.plan(1)\n\n  const harPath = path.join(os.tmpdir(), 'autocannon-test.har')\n  fs.writeFileSync(harPath, 'not valid JSON content')\n\n  const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-a', 4, '-c', 1, '--har', harPath, 'http://localhost'], {\n    cwd: __dirname,\n    env: process.env,\n    stdio: ['ignore', 'pipe', 'pipe'],\n    detached: false\n  })\n\n  t.teardown(() => {\n    try {\n      child.kill()\n    } catch {}\n  })\n\n  const lines = []\n  child\n    .stderr\n    .pipe(split())\n    .on('data', line => lines.push(line))\n    .on('end', () => {\n      const output = lines.join('\\n')\n      t.ok(output.includes('Error: Failed to load HAR file content: Unexpected token'), `Unexpected output:\\n${output}`)\n      t.end()\n    })\n})\n\ntest('should write warning about unused HAR requests', (t) => {\n  t.plan(1)\n\n  const server = helper.startServer()\n  const url = `http://localhost:${server.address().port}`\n  const harPath = path.join(os.tmpdir(), 'autocannon-test.har')\n  const har = helper.customizeHAR('./fixtures/multi-domains.json', 'https://httpbin.org', url)\n  fs.writeFileSync(harPath, JSON.stringify(har))\n\n  const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-a', 4, '-c', 1, '--har', harPath, url], {\n    cwd: __dirname,\n    env: process.env,\n    stdio: ['ignore', 'pipe', 'pipe'],\n    detached: false\n  })\n\n  t.teardown(() => {\n    try {\n      child.kill()\n    } catch {}\n  })\n\n  const lines = []\n  child\n    .stderr\n    .pipe(split())\n    .on('data', line => lines.push(line))\n    .on('end', () => {\n      const output = lines.join('\\n')\n      t.ok(output.includes(`Warning: skipping requests to 'https://github.com' as the target is ${url}`), `Unexpected output:\\n${output}`)\n      t.end()\n    })\n})\n\ntest('run with workers', { skip: !hasWorkerSupport }, (t) => {\n  const lines = [\n    /Running 1s test @ .*$/,\n    /10 connections.*$/,\n    /4 workers.*$/,\n    /$/,\n    /.*/,\n    /$/,\n    /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\n    /.*/,\n    /Latency.*$/,\n    /$/,\n    /.*/,\n    /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\n    /.*/,\n    /Req\\/Sec.*$/,\n    /.*/,\n    /Bytes\\/Sec.*$/,\n    /.*/,\n    /$/,\n    /Req\\/Bytes counts sampled once per second.*$/,\n    /.*$/,\n    /$/,\n    /.* requests in ([0-9]|\\.)+s, .* read/\n  ]\n\n  t.plan(lines.length * 2)\n\n  const server = helper.startServer()\n  const url = 'http://localhost:' + server.address().port\n\n  const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-d', '1', url, '--workers', '4'], {\n    cwd: __dirname,\n    env: process.env,\n    stdio: ['ignore', 'pipe', 'pipe'],\n    detached: false\n  })\n\n  t.teardown(() => {\n    try {\n      child.kill()\n    } catch {}\n  })\n\n  child\n    .stderr\n    .pipe(split())\n    .on('data', (line) => {\n      const regexp = lines.shift()\n      t.ok(regexp, 'we are expecting this line')\n      t.ok(regexp.test(line), 'line matches ' + regexp)\n    })\n    .on('end', t.end)\n})\n\ntest('should run handle PUT bodies', (t) => {\n  t.test('\"number\" bodies work', t => {\n    t.plan(2)\n\n    const server = helper.startServer()\n    const url = 'http://localhost:' + server.address().port\n\n    const cmd = [\n      path.join(__dirname, '..'),\n      '-d', '1',\n      '-m', 'PUT',\n      '-H', 'content-type=\"application/x-www-form-urlencoded\"',\n      '-b', '1',\n      url\n    ]\n    const child = childProcess.spawn(process.execPath, cmd, {\n      cwd: __dirname,\n      env: process.env,\n      stdio: ['ignore', 'pipe', 'pipe'],\n      detached: false\n    })\n\n    t.teardown(() => {\n      try {\n        child.kill()\n      } catch {}\n    })\n\n    const outputLines = []\n    child\n      .stderr\n      .pipe(split())\n      .on('data', (line) => {\n        outputLines.push(line)\n      })\n      .on('end', () => {\n        t.equal(\n          outputLines.some(l => l === 'body must be either a string or a buffer'),\n          false\n        )\n        t.equal(\n          /.* requests in ([0-9]|\\.)+s, .* read/.test(outputLines.pop()),\n          true\n        )\n        t.end()\n      })\n  })\n\n  t.test('\"string\" bodies work', t => {\n    t.plan(2)\n\n    const server = helper.startServer()\n    const url = 'http://localhost:' + server.address().port\n\n    const cmd = [\n      path.join(__dirname, '..'),\n      '-d', '1',\n      '-m', 'PUT',\n      '-H', 'content-type=\"application/x-www-form-urlencoded\"',\n      '-b', '\"1\"',\n      url\n    ]\n    const child = childProcess.spawn(process.execPath, cmd, {\n      cwd: __dirname,\n      env: process.env,\n      stdio: ['ignore', 'pipe', 'pipe'],\n      detached: false\n    })\n\n    t.teardown(() => {\n      try {\n        child.kill()\n      } catch {}\n    })\n\n    const outputLines = []\n    child\n      .stderr\n      .pipe(split())\n      .on('data', (line) => {\n        outputLines.push(line)\n      })\n      .on('end', () => {\n        t.equal(\n          outputLines.some(l => l === 'body must be either a string or a buffer'),\n          false\n        )\n        t.equal(\n          /.* requests in ([0-9]|\\.)+s, .* read/.test(outputLines.pop()),\n          true\n        )\n        t.end()\n      })\n  })\n\n  t.end()\n})\n"
  },
  {
    "path": "test/debug.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst Autocannon = require('../autocannon')\n\ntest('debug works', (t) => {\n  t.plan(5)\n\n  const args = Autocannon.parseArguments([\n    '-H', 'X-Http-Method-Override=GET',\n    '-m', 'POST',\n    '-b', 'the body',\n    '--debug',\n    'http://localhost/foo/bar'\n  ])\n\n  t.equal(args.url, 'http://localhost/foo/bar')\n  t.strictSame(args.headers, { 'X-Http-Method-Override': 'GET' })\n  t.equal(args.method, 'POST')\n  t.equal(args.body, 'the body')\n  t.equal(args.debug, true)\n})\n"
  },
  {
    "path": "test/envPort.test.js",
    "content": "'use strict'\n\nconst why = require('why-is-node-running')\nconst t = require('tap')\nconst split = require('split2')\nconst path = require('path')\nconst childProcess = require('child_process')\nconst helper = require('./helper')\n\nsetInterval(function () {\n  console.log(why())\n}, 30000).unref()\n\nconst lines = [\n  /Running 1s test @ .*$/,\n  /10 connections.*$/,\n  /$/,\n  /.*/,\n  /$/,\n  /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\n  /.*/,\n  /Latency.*$/,\n  /$/,\n  /.*/,\n  /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\n  /.*/,\n  /Req\\/Sec.*$/,\n  /$/,\n  /Bytes\\/Sec.*$/,\n  /.*/,\n  /$/,\n  /Req\\/Bytes counts sampled once per second.*$/,\n  /# of samples: 10*$/,\n  /$/,\n  /.* requests in ([0-9]|\\.)+s, .* read/\n]\n\nt.plan(lines.length * 2 + 2)\n\nconst server = helper.startServer()\nconst port = server.address().port\nconst url = '/path' // no hostname\n\nconst child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-d', '1', url], {\n  cwd: __dirname,\n  env: Object.assign({}, process.env, {\n    PORT: port\n  }),\n  stdio: ['ignore', 'pipe', 'pipe'],\n  detached: false\n})\n\nt.teardown(() => {\n  try {\n    child.kill()\n  } catch {}\n})\n\nchild\n  .stderr\n  .pipe(split())\n  .on('data', (line) => {\n    const regexp = lines.shift()\n    t.ok(regexp, 'we are expecting this line')\n    t.ok(regexp.test(line), 'line matches ' + regexp)\n  })\n  .on('end', () => {\n    t.ok(server.autocannonConnects > 0, 'targeted the correct port')\n  })\n\nconst noPortChild = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), url], {\n  cwd: __dirname,\n  env: process.env,\n  stdio: ['ignore', 'pipe', 'pipe'],\n  detached: false\n})\n\nnoPortChild.on('exit', (code) => {\n  t.equal(code, 1, 'should exit with error when a hostless URL is passed and no PORT var is available')\n})\n"
  },
  {
    "path": "test/fixtures/example-result.json",
    "content": "{\r\n    \"title\": \"example result\",\r\n    \"url\": \"https://httpbin.org/get\",\r\n    \"requests\": {\r\n      \"average\": 250,\r\n      \"mean\": 250,\r\n      \"stddev\": 117,\r\n      \"min\": 133,\r\n      \"max\": 367,\r\n      \"total\": 500,\r\n      \"p0_001\": 133,\r\n      \"p0_01\": 133,\r\n      \"p0_1\": 133,\r\n      \"p1\": 133,\r\n      \"p2_5\": 133,\r\n      \"p10\": 133,\r\n      \"p25\": 133,\r\n      \"p50\": 133,\r\n      \"p75\": 367,\r\n      \"p90\": 367,\r\n      \"p97_5\": 367,\r\n      \"p99\": 367,\r\n      \"p99_9\": 367,\r\n      \"p99_99\": 367,\r\n      \"p99_999\": 367,\r\n      \"sent\": 500\r\n    },\r\n    \"latency\": {\r\n      \"average\": 100.1,\r\n      \"mean\": 100.1,\r\n      \"stddev\": 84.74,\r\n      \"min\": 47,\r\n      \"max\": 424,\r\n      \"p0_001\": 47,\r\n      \"p0_01\": 47,\r\n      \"p0_1\": 47,\r\n      \"p1\": 53,\r\n      \"p2_5\": 55,\r\n      \"p10\": 58,\r\n      \"p25\": 59,\r\n      \"p50\": 67,\r\n      \"p75\": 107,\r\n      \"p90\": 126,\r\n      \"p97_5\": 378,\r\n      \"p99\": 386,\r\n      \"p99_9\": 424,\r\n      \"p99_99\": 424,\r\n      \"p99_999\": 424,\r\n      \"totalCount\": 500\r\n    },\r\n    \"throughput\": {\r\n      \"average\": 52468,\r\n      \"mean\": 52468,\r\n      \"stddev\": 24556,\r\n      \"min\": 27915,\r\n      \"max\": 77030,\r\n      \"total\": 104945,\r\n      \"p0_001\": 27919,\r\n      \"p0_01\": 27919,\r\n      \"p0_1\": 27919,\r\n      \"p1\": 27919,\r\n      \"p2_5\": 27919,\r\n      \"p10\": 27919,\r\n      \"p25\": 27919,\r\n      \"p50\": 27919,\r\n      \"p75\": 77055,\r\n      \"p90\": 77055,\r\n      \"p97_5\": 77055,\r\n      \"p99\": 77055,\r\n      \"p99_9\": 77055,\r\n      \"p99_99\": 77055,\r\n      \"p99_999\": 77055\r\n    },\r\n    \"errors\": 0,\r\n    \"timeouts\": 0,\r\n    \"mismatches\": 0,\r\n    \"duration\": 2.06,\r\n\t\"sampleInt\": 1000,\r\n\t\"samples\": 10,\r\n    \"start\": \"2020-11-06T09:38:31.027Z\",\r\n    \"finish\": \"2020-11-06T09:38:33.089Z\",\r\n    \"connections\": 40,\r\n    \"pipelining\": 1,\r\n    \"non2xx\": 0,\r\n    \"resets\": 0,\r\n    \"1xx\": 0,\r\n    \"2xx\": 500,\r\n    \"3xx\": 0,\r\n    \"4xx\": 0,\r\n    \"5xx\": 0,\r\n    \"statusCodeStats\": {\r\n      \"200\": { \"count\": \"500\" }, \r\n      \"302\": { \"count\": \"0\" },\r\n      \"401\": { \"count\": \"0\" },\r\n      \"403\": { \"count\": \"0\" }\r\n    }\r\n  }\r\n  \r\n"
  },
  {
    "path": "test/fixtures/httpbin-get.json",
    "content": "{\n  \"log\": {\n    \"version\": \"1.2\",\n    \"creator\": {\n      \"name\": \"Firefox\",\n      \"version\": \"80.0.1\"\n    },\n    \"browser\": {\n      \"name\": \"Firefox\",\n      \"version\": \"80.0.1\"\n    },\n    \"pages\": [\n      {\n        \"startedDateTime\": \"2020-09-28T20:47:18.735+02:00\",\n        \"id\": \"page_1\",\n        \"title\": \"https://httpbin.org/post\",\n        \"pageTimings\": {\n          \"onContentLoad\": 170,\n          \"onLoad\": 187\n        }\n      }\n    ],\n    \"entries\": [\n      {\n        \"pageref\": \"page_2\",\n        \"startedDateTime\": \"2020-09-28T22:03:15.885+02:00\",\n        \"request\": {\n          \"bodySize\": 0,\n          \"method\": \"GET\",\n          \"url\": \"https://httpbin.org/get\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"Host\",\n              \"value\": \"httpbin.org\"\n            },\n            {\n              \"name\": \"User-Agent\",\n              \"value\": \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\"\n            },\n            {\n              \"name\": \"Accept\",\n              \"value\": \"*/*\"\n            },\n            {\n              \"name\": \"Accept-Language\",\n              \"value\": \"fr,en;q=0.5\"\n            },\n            {\n              \"name\": \"Accept-Encoding\",\n              \"value\": \"gzip, deflate, br\"\n            },\n            {\n              \"name\": \"Referer\",\n              \"value\": \"https://httpbin.org/\"\n            },\n            {\n              \"name\": \"DNT\",\n              \"value\": \"1\"\n            },\n            {\n              \"name\": \"Connection\",\n              \"value\": \"keep-alive\"\n            }\n          ],\n          \"cookies\": [],\n          \"queryString\": [],\n          \"headersSize\": 272\n        },\n        \"response\": {\n          \"status\": 200,\n          \"statusText\": \"OK\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"date\",\n              \"value\": \"Mon, 28 Sep 2020 20:03:16 GMT\"\n            },\n            {\n              \"name\": \"content-type\",\n              \"value\": \"application/json\"\n            },\n            {\n              \"name\": \"content-length\",\n              \"value\": \"461\"\n            },\n            {\n              \"name\": \"server\",\n              \"value\": \"gunicorn/19.9.0\"\n            },\n            {\n              \"name\": \"access-control-allow-origin\",\n              \"value\": \"*\"\n            },\n            {\n              \"name\": \"access-control-allow-credentials\",\n              \"value\": \"true\"\n            },\n            {\n              \"name\": \"X-Firefox-Spdy\",\n              \"value\": \"h2\"\n            }\n          ],\n          \"cookies\": [],\n          \"content\": {\n            \"mimeType\": \"application/json\",\n            \"size\": 461,\n            \"text\": \"{\\n  \\\"args\\\": {}, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"gzip, deflate, br\\\", \\n    \\\"Accept-Language\\\": \\\"fr,en;q=0.5\\\", \\n    \\\"Dnt\\\": \\\"1\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"Referer\\\": \\\"https://httpbin.org/\\\", \\n    \\\"User-Agent\\\": \\\"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-5f724184-c2b2dc0aa856f9d04dac5d35\\\"\\n  }, \\n  \\\"origin\\\": \\\"78.192.173.27\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/get\\\"\\n}\\n\"\n          },\n          \"redirectURL\": \"\",\n          \"headersSize\": 224,\n          \"bodySize\": 685\n        },\n        \"cache\": {},\n        \"timings\": {\n          \"blocked\": 184,\n          \"dns\": 8,\n          \"connect\": 85,\n          \"ssl\": 88,\n          \"send\": 0,\n          \"wait\": 86,\n          \"receive\": 0\n        },\n        \"time\": 451,\n        \"_securityState\": \"secure\",\n        \"serverIPAddress\": \"35.170.21.246\",\n        \"connection\": \"443\"\n      },\n      {\n        \"pageref\": \"page_1\",\n        \"startedDateTime\": \"2020-09-28T21:46:29.772+02:00\",\n        \"request\": {\n          \"bodySize\": 0,\n          \"method\": \"GET\",\n          \"url\": \"https://httpbin.org/get?from=10&size=20&sort=+name\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"Host\",\n              \"value\": \"httpbin.org\"\n            },\n            {\n              \"name\": \"User-Agent\",\n              \"value\": \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\"\n            },\n            {\n              \"name\": \"Accept\",\n              \"value\": \"*/*\"\n            },\n            {\n              \"name\": \"Accept-Language\",\n              \"value\": \"fr,en;q=0.5\"\n            },\n            {\n              \"name\": \"Accept-Encoding\",\n              \"value\": \"gzip, deflate, br\"\n            },\n            {\n              \"name\": \"Referer\",\n              \"value\": \"https://httpbin.org/\"\n            },\n            {\n              \"name\": \"DNT\",\n              \"value\": \"1\"\n            },\n            {\n              \"name\": \"Connection\",\n              \"value\": \"keep-alive\"\n            },\n            {\n              \"name\": \"TE\",\n              \"value\": \"Trailers\"\n            }\n          ],\n          \"cookies\": [],\n          \"queryString\": [\n            {\n              \"name\": \"from\",\n              \"value\": \"10\"\n            },\n            {\n              \"name\": \"size\",\n              \"value\": \"20\"\n            },\n            {\n              \"name\": \"sort\",\n              \"value\": \" name\"\n            }\n          ],\n          \"headersSize\": 299\n        },\n        \"response\": {\n          \"status\": 200,\n          \"statusText\": \"OK\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"date\",\n              \"value\": \"Mon, 28 Sep 2020 19:46:29 GMT\"\n            },\n            {\n              \"name\": \"content-type\",\n              \"value\": \"application/json\"\n            },\n            {\n              \"name\": \"content-length\",\n              \"value\": \"549\"\n            },\n            {\n              \"name\": \"server\",\n              \"value\": \"gunicorn/19.9.0\"\n            },\n            {\n              \"name\": \"access-control-allow-origin\",\n              \"value\": \"*\"\n            },\n            {\n              \"name\": \"access-control-allow-credentials\",\n              \"value\": \"true\"\n            },\n            {\n              \"name\": \"X-Firefox-Spdy\",\n              \"value\": \"h2\"\n            }\n          ],\n          \"cookies\": [],\n          \"content\": {\n            \"mimeType\": \"application/json\",\n            \"size\": 549,\n            \"text\": \"{\\n  \\\"args\\\": {\\n    \\\"from\\\": \\\"10\\\", \\n    \\\"size\\\": \\\"20\\\", \\n    \\\"sort\\\": \\\" name\\\"\\n  }, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"gzip, deflate, br\\\", \\n    \\\"Accept-Language\\\": \\\"fr,en;q=0.5\\\", \\n    \\\"Dnt\\\": \\\"1\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"Referer\\\": \\\"https://httpbin.org/\\\", \\n    \\\"User-Agent\\\": \\\"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-5f723d95-9675a1b77254a636c2f09fa3\\\"\\n  }, \\n  \\\"origin\\\": \\\"78.192.173.27\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/get?from=10&size=20&sort=+name\\\"\\n}\\n\"\n          },\n          \"redirectURL\": \"\",\n          \"headersSize\": 224,\n          \"bodySize\": 773\n        },\n        \"cache\": {},\n        \"timings\": {\n          \"blocked\": 0,\n          \"dns\": 0,\n          \"connect\": 0,\n          \"ssl\": 0,\n          \"send\": 0,\n          \"wait\": 168,\n          \"receive\": 0\n        },\n        \"time\": 168,\n        \"_securityState\": \"secure\",\n        \"serverIPAddress\": \"35.170.21.246\",\n        \"connection\": \"443\"\n      }\n    ]\n  }\n}"
  },
  {
    "path": "test/fixtures/httpbin-post.json",
    "content": "{\n  \"log\": {\n    \"version\": \"1.2\",\n    \"creator\": {\n      \"name\": \"Firefox\",\n      \"version\": \"80.0.1\"\n    },\n    \"browser\": {\n      \"name\": \"Firefox\",\n      \"version\": \"80.0.1\"\n    },\n    \"pages\": [\n      {\n        \"startedDateTime\": \"2020-09-28T21:41:16.913+02:00\",\n        \"id\": \"page_1\",\n        \"title\": \"httpbin.org\",\n        \"pageTimings\": {\n          \"onContentLoad\": -100471,\n          \"onLoad\": -100459\n        }\n      }\n    ],\n    \"entries\": [\n      {\n        \"pageref\": \"page_1\",\n        \"startedDateTime\": \"2020-09-28T21:41:16.913+02:00\",\n        \"request\": {\n          \"bodySize\": 362,\n          \"method\": \"POST\",\n          \"url\": \"https://httpbin.org/post\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"Host\",\n              \"value\": \"httpbin.org\"\n            },\n            {\n              \"name\": \"User-Agent\",\n              \"value\": \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\"\n            },\n            {\n              \"name\": \"Accept\",\n              \"value\": \"*/*\"\n            },\n            {\n              \"name\": \"Accept-Language\",\n              \"value\": \"fr,en;q=0.5\"\n            },\n            {\n              \"name\": \"Accept-Encoding\",\n              \"value\": \"gzip, deflate, br\"\n            },\n            {\n              \"name\": \"Referer\",\n              \"value\": \"https://httpbin.org/\"\n            },\n            {\n              \"name\": \"Content-Type\",\n              \"value\": \"multipart/form-data; boundary=---------------------------31420230025845772252453285324\"\n            },\n            {\n              \"name\": \"Origin\",\n              \"value\": \"https://httpbin.org\"\n            },\n            {\n              \"name\": \"Content-Length\",\n              \"value\": \"362\"\n            },\n            {\n              \"name\": \"DNT\",\n              \"value\": \"1\"\n            },\n            {\n              \"name\": \"Connection\",\n              \"value\": \"keep-alive\"\n            },\n            {\n              \"name\": \"TE\",\n              \"value\": \"Trailers\"\n            }\n          ],\n          \"cookies\": [],\n          \"queryString\": [],\n          \"headersSize\": 426,\n          \"postData\": {\n            \"mimeType\": \"multipart/form-data; boundary=---------------------------31420230025845772252453285324\",\n            \"params\": [],\n            \"text\": \"-----------------------------31420230025845772252453285324\\r\\nContent-Disposition: form-data; name=\\\"text\\\"\\r\\n\\r\\na text value\\r\\n-----------------------------31420230025845772252453285324\\r\\nContent-Disposition: form-data; name=\\\"file\\\"; filename=\\\"blob\\\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\nHello World!\\n\\r\\n-----------------------------31420230025845772252453285324--\\r\\n\"\n          }\n        },\n        \"response\": {\n          \"status\": 200,\n          \"statusText\": \"OK\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"date\",\n              \"value\": \"Mon, 28 Sep 2020 19:41:16 GMT\"\n            },\n            {\n              \"name\": \"content-type\",\n              \"value\": \"application/json\"\n            },\n            {\n              \"name\": \"content-length\",\n              \"value\": \"766\"\n            },\n            {\n              \"name\": \"server\",\n              \"value\": \"gunicorn/19.9.0\"\n            },\n            {\n              \"name\": \"access-control-allow-origin\",\n              \"value\": \"https://httpbin.org\"\n            },\n            {\n              \"name\": \"access-control-allow-credentials\",\n              \"value\": \"true\"\n            },\n            {\n              \"name\": \"X-Firefox-Spdy\",\n              \"value\": \"h2\"\n            }\n          ],\n          \"cookies\": [],\n          \"content\": {\n            \"mimeType\": \"application/json\",\n            \"size\": 766,\n            \"text\": \"{\\n  \\\"args\\\": {}, \\n  \\\"data\\\": \\\"\\\", \\n  \\\"files\\\": {\\n    \\\"file\\\": \\\"Hello World!\\\\n\\\"\\n  }, \\n  \\\"form\\\": {\\n    \\\"text\\\": \\\"a text value\\\"\\n  }, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"gzip, deflate, br\\\", \\n    \\\"Accept-Language\\\": \\\"fr,en;q=0.5\\\", \\n    \\\"Content-Length\\\": \\\"362\\\", \\n    \\\"Content-Type\\\": \\\"multipart/form-data; boundary=---------------------------31420230025845772252453285324\\\", \\n    \\\"Dnt\\\": \\\"1\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"Origin\\\": \\\"https://httpbin.org\\\", \\n    \\\"Referer\\\": \\\"https://httpbin.org/\\\", \\n    \\\"User-Agent\\\": \\\"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-5f723c5c-b0e8a0609820985731015e80\\\"\\n  }, \\n  \\\"json\\\": null, \\n  \\\"origin\\\": \\\"78.192.173.27\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/post\\\"\\n}\\n\"\n          },\n          \"redirectURL\": \"\",\n          \"headersSize\": 242,\n          \"bodySize\": 1008\n        },\n        \"cache\": {},\n        \"timings\": {\n          \"blocked\": 0,\n          \"dns\": 0,\n          \"connect\": 0,\n          \"ssl\": 0,\n          \"send\": 0,\n          \"wait\": 89,\n          \"receive\": 0\n        },\n        \"time\": 89,\n        \"_securityState\": \"secure\",\n        \"serverIPAddress\": \"35.170.21.246\",\n        \"connection\": \"443\"\n      },\n      {\n        \"pageref\": \"page_1\",\n        \"startedDateTime\": \"2020-09-28T21:43:55.877+02:00\",\n        \"request\": {\n          \"bodySize\": 27,\n          \"method\": \"POST\",\n          \"url\": \"https://httpbin.org/post\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"Host\",\n              \"value\": \"httpbin.org\"\n            },\n            {\n              \"name\": \"User-Agent\",\n              \"value\": \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\"\n            },\n            {\n              \"name\": \"Accept\",\n              \"value\": \"*/*\"\n            },\n            {\n              \"name\": \"Accept-Language\",\n              \"value\": \"fr,en;q=0.5\"\n            },\n            {\n              \"name\": \"Accept-Encoding\",\n              \"value\": \"gzip, deflate, br\"\n            },\n            {\n              \"name\": \"Referer\",\n              \"value\": \"https://httpbin.org/\"\n            },\n            {\n              \"name\": \"Content-Type\",\n              \"value\": \"application/x-www-form-urlencoded\"\n            },\n            {\n              \"name\": \"Origin\",\n              \"value\": \"https://httpbin.org\"\n            },\n            {\n              \"name\": \"Content-Length\",\n              \"value\": \"27\"\n            },\n            {\n              \"name\": \"DNT\",\n              \"value\": \"1\"\n            },\n            {\n              \"name\": \"Connection\",\n              \"value\": \"keep-alive\"\n            },\n            {\n              \"name\": \"TE\",\n              \"value\": \"Trailers\"\n            }\n          ],\n          \"cookies\": [],\n          \"queryString\": [],\n          \"headersSize\": 372,\n          \"postData\": {\n            \"mimeType\": \"application/x-www-form-urlencoded\",\n            \"params\": [\n              {\n                \"name\": \"text\",\n                \"value\": \"a text value\"\n              },\n              {\n                \"name\": \"number\",\n                \"value\": \"10\"\n              }\n            ],\n            \"text\": \"text=a+text+value&number=10\"\n          }\n        },\n        \"response\": {\n          \"status\": 200,\n          \"statusText\": \"OK\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"date\",\n              \"value\": \"Mon, 28 Sep 2020 19:43:55 GMT\"\n            },\n            {\n              \"name\": \"content-type\",\n              \"value\": \"application/json\"\n            },\n            {\n              \"name\": \"content-length\",\n              \"value\": \"701\"\n            },\n            {\n              \"name\": \"server\",\n              \"value\": \"gunicorn/19.9.0\"\n            },\n            {\n              \"name\": \"access-control-allow-origin\",\n              \"value\": \"https://httpbin.org\"\n            },\n            {\n              \"name\": \"access-control-allow-credentials\",\n              \"value\": \"true\"\n            },\n            {\n              \"name\": \"X-Firefox-Spdy\",\n              \"value\": \"h2\"\n            }\n          ],\n          \"cookies\": [],\n          \"content\": {\n            \"mimeType\": \"application/json\",\n            \"size\": 701,\n            \"text\": \"{\\n  \\\"args\\\": {}, \\n  \\\"data\\\": \\\"\\\", \\n  \\\"files\\\": {}, \\n  \\\"form\\\": {\\n    \\\"number\\\": \\\"10\\\", \\n    \\\"text\\\": \\\"a text value\\\"\\n  }, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"gzip, deflate, br\\\", \\n    \\\"Accept-Language\\\": \\\"fr,en;q=0.5\\\", \\n    \\\"Content-Length\\\": \\\"27\\\", \\n    \\\"Content-Type\\\": \\\"application/x-www-form-urlencoded\\\", \\n    \\\"Dnt\\\": \\\"1\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"Origin\\\": \\\"https://httpbin.org\\\", \\n    \\\"Referer\\\": \\\"https://httpbin.org/\\\", \\n    \\\"User-Agent\\\": \\\"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-5f723cfb-880669c8b0b3a9628bea7db3\\\"\\n  }, \\n  \\\"json\\\": null, \\n  \\\"origin\\\": \\\"78.192.173.27\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/post\\\"\\n}\\n\"\n          },\n          \"redirectURL\": \"\",\n          \"headersSize\": 242,\n          \"bodySize\": 943\n        },\n        \"cache\": {},\n        \"timings\": {\n          \"blocked\": 0,\n          \"dns\": 1,\n          \"connect\": 0,\n          \"ssl\": 0,\n          \"send\": 0,\n          \"wait\": 91,\n          \"receive\": 0\n        },\n        \"time\": 92,\n        \"_securityState\": \"secure\",\n        \"serverIPAddress\": \"35.170.21.246\",\n        \"connection\": \"443\"\n      }\n    ]\n  }\n}"
  },
  {
    "path": "test/fixtures/httpbin-simple-get.json",
    "content": "{\n  \"log\": {\n    \"version\": \"1.2\",\n    \"creator\": {\n      \"name\": \"Firefox\",\n      \"version\": \"80.0.1\"\n    },\n    \"browser\": {\n      \"name\": \"Firefox\",\n      \"version\": \"80.0.1\"\n    },\n    \"entries\": [\n      {\n        \"startedDateTime\": \"2020-09-28T22:03:15.885+02:00\",\n        \"request\": {\n          \"bodySize\": 0,\n          \"method\": \"GET\",\n          \"url\": \"https://httpbin.org/get\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"Host\",\n              \"value\": \"httpbin.org\"\n            },\n            {\n              \"name\": \"User-Agent\",\n              \"value\": \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\"\n            },\n            {\n              \"name\": \"Accept\",\n              \"value\": \"*/*\"\n            },\n            {\n              \"name\": \"Accept-Language\",\n              \"value\": \"fr,en;q=0.5\"\n            },\n            {\n              \"name\": \"Accept-Encoding\",\n              \"value\": \"gzip, deflate, br\"\n            },\n            {\n              \"name\": \"Referer\",\n              \"value\": \"https://httpbin.org/\"\n            },\n            {\n              \"name\": \"DNT\",\n              \"value\": \"1\"\n            },\n            {\n              \"name\": \"Connection\",\n              \"value\": \"keep-alive\"\n            }\n          ],\n          \"cookies\": [],\n          \"queryString\": [],\n          \"headersSize\": 272\n        },\n        \"response\": {\n          \"status\": 200,\n          \"statusText\": \"OK\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"date\",\n              \"value\": \"Mon, 28 Sep 2020 20:03:16 GMT\"\n            },\n            {\n              \"name\": \"content-type\",\n              \"value\": \"application/json\"\n            },\n            {\n              \"name\": \"content-length\",\n              \"value\": \"461\"\n            },\n            {\n              \"name\": \"server\",\n              \"value\": \"gunicorn/19.9.0\"\n            },\n            {\n              \"name\": \"access-control-allow-origin\",\n              \"value\": \"*\"\n            },\n            {\n              \"name\": \"access-control-allow-credentials\",\n              \"value\": \"true\"\n            },\n            {\n              \"name\": \"X-Firefox-Spdy\",\n              \"value\": \"h2\"\n            }\n          ],\n          \"cookies\": [],\n          \"content\": {\n            \"mimeType\": \"application/json\",\n            \"size\": 461,\n            \"text\": \"{\\n  \\\"args\\\": {}, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"gzip, deflate, br\\\", \\n    \\\"Accept-Language\\\": \\\"fr,en;q=0.5\\\", \\n    \\\"Dnt\\\": \\\"1\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"Referer\\\": \\\"https://httpbin.org/\\\", \\n    \\\"User-Agent\\\": \\\"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-5f724184-c2b2dc0aa856f9d04dac5d35\\\"\\n  }, \\n  \\\"origin\\\": \\\"78.192.173.27\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/get\\\"\\n}\\n\"\n          },\n          \"redirectURL\": \"\",\n          \"headersSize\": 224,\n          \"bodySize\": 685\n        },\n        \"cache\": {},\n        \"timings\": {\n          \"blocked\": 184,\n          \"dns\": 8,\n          \"connect\": 85,\n          \"ssl\": 88,\n          \"send\": 0,\n          \"wait\": 86,\n          \"receive\": 0\n        },\n        \"time\": 451,\n        \"_securityState\": \"secure\",\n        \"serverIPAddress\": \"35.170.21.246\",\n        \"connection\": \"443\"\n      }\n    ]\n  }\n}"
  },
  {
    "path": "test/fixtures/multi-domains.json",
    "content": "{\n  \"log\": {\n    \"version\": \"1.2\",\n    \"creator\": {\n      \"name\": \"Firefox\",\n      \"version\": \"80.0.1\"\n    },\n    \"browser\": {\n      \"name\": \"Firefox\",\n      \"version\": \"80.0.1\"\n    },\n    \"pages\": [\n      {\n        \"startedDateTime\": \"2020-09-28T21:41:16.913+02:00\",\n        \"id\": \"page_1\",\n        \"title\": \"httpbin.org\",\n        \"pageTimings\": {\n          \"onContentLoad\": -100471,\n          \"onLoad\": -100459\n        }\n      }\n    ],\n    \"entries\": [\n      {\n        \"pageref\": \"page_1\",\n        \"startedDateTime\": \"2020-09-28T21:41:16.913+02:00\",\n        \"request\": {\n          \"bodySize\": 362,\n          \"method\": \"POST\",\n          \"url\": \"https://httpbin.org/post\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"Host\",\n              \"value\": \"httpbin.org\"\n            },\n            {\n              \"name\": \"User-Agent\",\n              \"value\": \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\"\n            },\n            {\n              \"name\": \"Accept\",\n              \"value\": \"*/*\"\n            },\n            {\n              \"name\": \"Accept-Language\",\n              \"value\": \"fr,en;q=0.5\"\n            },\n            {\n              \"name\": \"Accept-Encoding\",\n              \"value\": \"gzip, deflate, br\"\n            },\n            {\n              \"name\": \"Referer\",\n              \"value\": \"https://httpbin.org/\"\n            },\n            {\n              \"name\": \"Content-Type\",\n              \"value\": \"multipart/form-data; boundary=---------------------------31420230025845772252453285324\"\n            },\n            {\n              \"name\": \"Origin\",\n              \"value\": \"https://httpbin.org\"\n            },\n            {\n              \"name\": \"Content-Length\",\n              \"value\": \"362\"\n            },\n            {\n              \"name\": \"DNT\",\n              \"value\": \"1\"\n            },\n            {\n              \"name\": \"Connection\",\n              \"value\": \"keep-alive\"\n            },\n            {\n              \"name\": \"TE\",\n              \"value\": \"Trailers\"\n            }\n          ],\n          \"cookies\": [],\n          \"queryString\": [],\n          \"headersSize\": 426,\n          \"postData\": {\n            \"mimeType\": \"multipart/form-data; boundary=---------------------------31420230025845772252453285324\",\n            \"params\": [],\n            \"text\": \"-----------------------------31420230025845772252453285324\\r\\nContent-Disposition: form-data; name=\\\"text\\\"\\r\\n\\r\\na text value\\r\\n-----------------------------31420230025845772252453285324\\r\\nContent-Disposition: form-data; name=\\\"file\\\"; filename=\\\"blob\\\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\nHello World!\\n\\r\\n-----------------------------31420230025845772252453285324--\\r\\n\"\n          }\n        },\n        \"response\": {\n          \"status\": 200,\n          \"statusText\": \"OK\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"date\",\n              \"value\": \"Mon, 28 Sep 2020 19:41:16 GMT\"\n            },\n            {\n              \"name\": \"content-type\",\n              \"value\": \"application/json\"\n            },\n            {\n              \"name\": \"content-length\",\n              \"value\": \"766\"\n            },\n            {\n              \"name\": \"server\",\n              \"value\": \"gunicorn/19.9.0\"\n            },\n            {\n              \"name\": \"access-control-allow-origin\",\n              \"value\": \"https://httpbin.org\"\n            },\n            {\n              \"name\": \"access-control-allow-credentials\",\n              \"value\": \"true\"\n            },\n            {\n              \"name\": \"X-Firefox-Spdy\",\n              \"value\": \"h2\"\n            }\n          ],\n          \"cookies\": [],\n          \"content\": {\n            \"mimeType\": \"application/json\",\n            \"size\": 766,\n            \"text\": \"{\\n  \\\"args\\\": {}, \\n  \\\"data\\\": \\\"\\\", \\n  \\\"files\\\": {\\n    \\\"file\\\": \\\"Hello World!\\\\n\\\"\\n  }, \\n  \\\"form\\\": {\\n    \\\"text\\\": \\\"a text value\\\"\\n  }, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"gzip, deflate, br\\\", \\n    \\\"Accept-Language\\\": \\\"fr,en;q=0.5\\\", \\n    \\\"Content-Length\\\": \\\"362\\\", \\n    \\\"Content-Type\\\": \\\"multipart/form-data; boundary=---------------------------31420230025845772252453285324\\\", \\n    \\\"Dnt\\\": \\\"1\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"Origin\\\": \\\"https://httpbin.org\\\", \\n    \\\"Referer\\\": \\\"https://httpbin.org/\\\", \\n    \\\"User-Agent\\\": \\\"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-5f723c5c-b0e8a0609820985731015e80\\\"\\n  }, \\n  \\\"json\\\": null, \\n  \\\"origin\\\": \\\"78.192.173.27\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/post\\\"\\n}\\n\"\n          },\n          \"redirectURL\": \"\",\n          \"headersSize\": 242,\n          \"bodySize\": 1008\n        },\n        \"cache\": {},\n        \"timings\": {\n          \"blocked\": 0,\n          \"dns\": 0,\n          \"connect\": 0,\n          \"ssl\": 0,\n          \"send\": 0,\n          \"wait\": 89,\n          \"receive\": 0\n        },\n        \"time\": 89,\n        \"_securityState\": \"secure\",\n        \"serverIPAddress\": \"35.170.21.246\",\n        \"connection\": \"443\"\n      },\n      {\n        \"pageref\": \"page_1\",\n        \"startedDateTime\": \"2020-09-28T21:43:55.877+02:00\",\n        \"request\": {\n          \"bodySize\": 27,\n          \"method\": \"POST\",\n          \"url\": \"https://github.com\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"Host\",\n              \"value\": \"github.com\"\n            },\n            {\n              \"name\": \"User-Agent\",\n              \"value\": \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\"\n            },\n            {\n              \"name\": \"Accept\",\n              \"value\": \"*/*\"\n            },\n            {\n              \"name\": \"Accept-Language\",\n              \"value\": \"fr,en;q=0.5\"\n            },\n            {\n              \"name\": \"Accept-Encoding\",\n              \"value\": \"gzip, deflate, br\"\n            },\n            {\n              \"name\": \"Referer\",\n              \"value\": \"https://github.com/\"\n            },\n            {\n              \"name\": \"Content-Type\",\n              \"value\": \"application/x-www-form-urlencoded\"\n            },\n            {\n              \"name\": \"Origin\",\n              \"value\": \"https://github.com\"\n            },\n            {\n              \"name\": \"Content-Length\",\n              \"value\": \"27\"\n            },\n            {\n              \"name\": \"DNT\",\n              \"value\": \"1\"\n            },\n            {\n              \"name\": \"Connection\",\n              \"value\": \"keep-alive\"\n            },\n            {\n              \"name\": \"TE\",\n              \"value\": \"Trailers\"\n            }\n          ],\n          \"cookies\": [],\n          \"queryString\": [],\n          \"headersSize\": 372,\n          \"postData\": {\n            \"mimeType\": \"application/x-www-form-urlencoded\",\n            \"params\": [\n              {\n                \"name\": \"text\",\n                \"value\": \"a text value\"\n              },\n              {\n                \"name\": \"number\",\n                \"value\": \"10\"\n              }\n            ],\n            \"text\": \"text=a+text+value&number=10\"\n          }\n        },\n        \"response\": {\n          \"status\": 200,\n          \"statusText\": \"OK\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"date\",\n              \"value\": \"Mon, 28 Sep 2020 19:43:55 GMT\"\n            },\n            {\n              \"name\": \"content-type\",\n              \"value\": \"application/json\"\n            },\n            {\n              \"name\": \"content-length\",\n              \"value\": \"701\"\n            },\n            {\n              \"name\": \"server\",\n              \"value\": \"gunicorn/19.9.0\"\n            },\n            {\n              \"name\": \"access-control-allow-origin\",\n              \"value\": \"https://httpbin.org\"\n            },\n            {\n              \"name\": \"access-control-allow-credentials\",\n              \"value\": \"true\"\n            },\n            {\n              \"name\": \"X-Firefox-Spdy\",\n              \"value\": \"h2\"\n            }\n          ],\n          \"cookies\": [],\n          \"content\": {\n            \"mimeType\": \"application/json\",\n            \"size\": 701,\n            \"text\": \"{\\n  \\\"args\\\": {}, \\n  \\\"data\\\": \\\"\\\", \\n  \\\"files\\\": {}, \\n  \\\"form\\\": {\\n    \\\"number\\\": \\\"10\\\", \\n    \\\"text\\\": \\\"a text value\\\"\\n  }, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"gzip, deflate, br\\\", \\n    \\\"Accept-Language\\\": \\\"fr,en;q=0.5\\\", \\n    \\\"Content-Length\\\": \\\"27\\\", \\n    \\\"Content-Type\\\": \\\"application/x-www-form-urlencoded\\\", \\n    \\\"Dnt\\\": \\\"1\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"Origin\\\": \\\"https://httpbin.org\\\", \\n    \\\"Referer\\\": \\\"https://httpbin.org/\\\", \\n    \\\"User-Agent\\\": \\\"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-5f723cfb-880669c8b0b3a9628bea7db3\\\"\\n  }, \\n  \\\"json\\\": null, \\n  \\\"origin\\\": \\\"78.192.173.27\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/post\\\"\\n}\\n\"\n          },\n          \"redirectURL\": \"\",\n          \"headersSize\": 242,\n          \"bodySize\": 943\n        },\n        \"cache\": {},\n        \"timings\": {\n          \"blocked\": 0,\n          \"dns\": 1,\n          \"connect\": 0,\n          \"ssl\": 0,\n          \"send\": 0,\n          \"wait\": 91,\n          \"receive\": 0\n        },\n        \"time\": 92,\n        \"_securityState\": \"secure\",\n        \"serverIPAddress\": \"35.170.21.246\",\n        \"connection\": \"443\"\n      },\n      {\n        \"pageref\": \"page_1\",\n        \"startedDateTime\": \"2020-09-28T21:46:29.772+02:00\",\n        \"request\": {\n          \"bodySize\": 0,\n          \"method\": \"GET\",\n          \"url\": \"https://httpbin.org/get?from=10&size=20&sort=+name\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"Host\",\n              \"value\": \"httpbin.org\"\n            },\n            {\n              \"name\": \"User-Agent\",\n              \"value\": \"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\"\n            },\n            {\n              \"name\": \"Accept\",\n              \"value\": \"*/*\"\n            },\n            {\n              \"name\": \"Accept-Language\",\n              \"value\": \"fr,en;q=0.5\"\n            },\n            {\n              \"name\": \"Accept-Encoding\",\n              \"value\": \"gzip, deflate, br\"\n            },\n            {\n              \"name\": \"Referer\",\n              \"value\": \"https://httpbin.org/\"\n            },\n            {\n              \"name\": \"DNT\",\n              \"value\": \"1\"\n            },\n            {\n              \"name\": \"Connection\",\n              \"value\": \"keep-alive\"\n            },\n            {\n              \"name\": \"TE\",\n              \"value\": \"Trailers\"\n            }\n          ],\n          \"cookies\": [],\n          \"queryString\": [\n            {\n              \"name\": \"from\",\n              \"value\": \"10\"\n            },\n            {\n              \"name\": \"size\",\n              \"value\": \"20\"\n            },\n            {\n              \"name\": \"sort\",\n              \"value\": \" name\"\n            }\n          ],\n          \"headersSize\": 299\n        },\n        \"response\": {\n          \"status\": 200,\n          \"statusText\": \"OK\",\n          \"httpVersion\": \"HTTP/2\",\n          \"headers\": [\n            {\n              \"name\": \"date\",\n              \"value\": \"Mon, 28 Sep 2020 19:46:29 GMT\"\n            },\n            {\n              \"name\": \"content-type\",\n              \"value\": \"application/json\"\n            },\n            {\n              \"name\": \"content-length\",\n              \"value\": \"549\"\n            },\n            {\n              \"name\": \"server\",\n              \"value\": \"gunicorn/19.9.0\"\n            },\n            {\n              \"name\": \"access-control-allow-origin\",\n              \"value\": \"*\"\n            },\n            {\n              \"name\": \"access-control-allow-credentials\",\n              \"value\": \"true\"\n            },\n            {\n              \"name\": \"X-Firefox-Spdy\",\n              \"value\": \"h2\"\n            }\n          ],\n          \"cookies\": [],\n          \"content\": {\n            \"mimeType\": \"application/json\",\n            \"size\": 549,\n            \"text\": \"{\\n  \\\"args\\\": {\\n    \\\"from\\\": \\\"10\\\", \\n    \\\"size\\\": \\\"20\\\", \\n    \\\"sort\\\": \\\" name\\\"\\n  }, \\n  \\\"headers\\\": {\\n    \\\"Accept\\\": \\\"*/*\\\", \\n    \\\"Accept-Encoding\\\": \\\"gzip, deflate, br\\\", \\n    \\\"Accept-Language\\\": \\\"fr,en;q=0.5\\\", \\n    \\\"Dnt\\\": \\\"1\\\", \\n    \\\"Host\\\": \\\"httpbin.org\\\", \\n    \\\"Referer\\\": \\\"https://httpbin.org/\\\", \\n    \\\"User-Agent\\\": \\\"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0\\\", \\n    \\\"X-Amzn-Trace-Id\\\": \\\"Root=1-5f723d95-9675a1b77254a636c2f09fa3\\\"\\n  }, \\n  \\\"origin\\\": \\\"78.192.173.27\\\", \\n  \\\"url\\\": \\\"https://httpbin.org/get?from=10&size=20&sort=+name\\\"\\n}\\n\"\n          },\n          \"redirectURL\": \"\",\n          \"headersSize\": 224,\n          \"bodySize\": 773\n        },\n        \"cache\": {},\n        \"timings\": {\n          \"blocked\": 0,\n          \"dns\": 0,\n          \"connect\": 0,\n          \"ssl\": 0,\n          \"send\": 0,\n          \"wait\": 168,\n          \"receive\": 0\n        },\n        \"time\": 168,\n        \"_securityState\": \"secure\",\n        \"serverIPAddress\": \"35.170.21.246\",\n        \"connection\": \"443\"\n      }\n    ]\n  }\n}"
  },
  {
    "path": "test/forever.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst initJob = require('../lib/init')\nconst helper = require('./helper')\nconst server = helper.startServer()\n\ntest('running with forever set to true and passing in a callback should cause an error to be returned in the callback', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    forever: true\n  }, (err, res) => {\n    t.ok(err, 'should be error when callback passed to run')\n    t.notOk(res, 'should not exist')\n    t.end()\n  })\n})\n\ntest('run forever should run until .stop() is called', (t) => {\n  t.plan(3)\n  let numRuns = 0\n\n  const instance = initJob({\n    url: `http://localhost:${server.address().port}`,\n    duration: 0.5,\n    forever: true\n  })\n\n  instance.on('done', (results) => {\n    t.ok(results, 'should have gotten results')\n    if (++numRuns === 2) {\n      instance.stop()\n      setTimeout(() => {\n        t.ok(true, 'should have reached here without the callback being called again')\n        t.end()\n      }, 1000)\n    }\n  })\n})\n"
  },
  {
    "path": "test/format.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst format = require('../lib/format')\n\nconst pairs = {\n  2: 2,\n  '2k': 2000,\n  '4k': 4042,\n  '2300k': 2300000\n}\n\nObject.keys(pairs).forEach((expected) => {\n  const original = pairs[expected]\n  test(`format ${original} into ${expected}`, (t) => {\n    t.equal(expected, format(original))\n    t.end()\n  })\n})\n"
  },
  {
    "path": "test/helper.js",
    "content": "'use strict'\n\nconst http = require('http')\nconst https = require('https')\nconst tls = require('tls')\nconst fs = require('fs')\nconst path = require('path')\nconst BusBoy = require('busboy')\n\nfunction startServer (opts) {\n  opts = opts || {}\n\n  if (!Array.isArray(opts.responses)) {\n    opts.responses = []\n  }\n\n  const fixedStatusCode = opts.statusCode || 200\n  const server = http.createServer(handle)\n  server.autocannonConnects = 0\n  server.autocannonRequests = 0\n\n  server.on('connection', () => { server.autocannonConnects++ })\n\n  server.listen(opts.socketPath || 0)\n\n  function handle (req, res) {\n    let { statusCode, body, headers } = opts.responses[server.autocannonRequests] || {}\n\n    server.autocannonRequests++\n\n    if (!statusCode) {\n      statusCode = fixedStatusCode\n    }\n\n    if (!body) {\n      body = opts.body || 'hello world'\n    }\n\n    res.statusCode = statusCode\n    const reply = () => {\n      const bodyToWrite = typeof body === 'function' ? body(req) : body\n\n      if (headers) {\n        res.writeHead(statusCode, headers)\n      }\n      res.end(statusCode < 200 ? undefined : bodyToWrite)\n    }\n\n    if (opts.delayResponse) {\n      setTimeout(reply, opts.delayResponse)\n    } else {\n      reply()\n    }\n  }\n\n  server.unref()\n\n  return server\n}\n\nfunction startTrailerServer () {\n  const server = http.createServer(handle)\n\n  function handle (req, res) {\n    res.writeHead(200, { 'Content-Type': 'text/plain', Trailer: 'Content-MD5' })\n    res.write('hello ')\n    res.addTrailers({ 'Content-MD5': '7895bf4b8828b55ceaf47747b4bca667' })\n    res.end('world')\n  }\n\n  server.listen(0)\n\n  server.unref()\n\n  return server\n}\n\n// this server won't reply to requests\nfunction startTimeoutServer () {\n  const server = http.createServer(() => {})\n\n  server.listen(0)\n  server.unref()\n\n  return server\n}\n\n// this server destroys the socket on connection, should result in ECONNRESET\nfunction startSocketDestroyingServer () {\n  const server = http.createServer(handle)\n\n  function handle (req, res) {\n    res.destroy()\n    server.close()\n  }\n\n  server.listen(0)\n  server.unref()\n\n  return server\n}\n\n// this server won't reply to requests\nfunction startHttpsServer (opts = {}) {\n  const options = {\n    key: fs.readFileSync(path.join(__dirname, '/key.pem')),\n    cert: fs.readFileSync(path.join(__dirname, '/cert.pem')),\n    passphrase: 'test'\n  }\n\n  const server = https.createServer(options, handle)\n\n  server.listen(opts.socketPath || 0)\n\n  function handle (req, res) {\n    res.end('hello world')\n  }\n\n  server.unref()\n\n  return server\n}\n\n// this server will echo the SNI Server Name and emailAddress from the client certificate in a HTTP header\nfunction startTlsServer () {\n  const key = fs.readFileSync(path.join(__dirname, '/key.pem'))\n  const cert = fs.readFileSync(path.join(__dirname, '/cert.pem'))\n  const passphrase = 'test'\n\n  const options = {\n    key,\n    cert,\n    passphrase,\n    requestCert: true,\n    rejectUnauthorized: false\n  }\n\n  const server = tls.createServer(options, handle)\n\n  server.listen(0)\n\n  function handle (socket) {\n    const servername = socket.servername || ''\n    const certificate = socket.getPeerCertificate()\n    const email = (certificate && certificate.subject && certificate.subject.emailAddress) || ''\n    socket.on('data', function (data) {\n      // Assume this is a http get request and send back the servername in an otherwise empty reponse.\n      socket.write('HTTP/1.1 200 OK\\n')\n      socket.write('X-servername: ' + servername + '\\n')\n      if (email) {\n        socket.write('X-email: ' + email + '\\n')\n      }\n      socket.write('Content-Length: 0\\n\\n')\n      socket.setEncoding('utf8')\n      socket.pipe(socket)\n    })\n\n    socket.on('error', noop)\n  }\n\n  server.unref()\n\n  return server\n}\n\nfunction startMultipartServer (opts = {}, test = () => {}) {\n  const server = http.createServer(handle)\n  const allowed = ['POST', 'PUT']\n  function handle (req, res) {\n    if (allowed.includes(req.method)) {\n      const bboy = new BusBoy({ headers: req.headers, ...opts })\n      const fileData = []\n      const payload = {}\n      bboy.on('file', (fieldname, file, filename, encoding, mimetype) => {\n        payload[fieldname] = {\n          filename,\n          encoding,\n          mimetype\n        }\n        file.on('data', data => fileData.push(data))\n      })\n      bboy.on('field', (fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) => {\n        payload[fieldname] = val\n      })\n      bboy.on('finish', () => {\n        res.statusCode = fileData.length ? 201 : 400\n        res.write(JSON.stringify(payload))\n        res.end()\n        test(payload)\n      })\n      req.pipe(bboy)\n    } else {\n      res.statusCode = 404\n      res.write(JSON.stringify({}))\n      res.end()\n    }\n  }\n\n  server.listen(0)\n  server.unref()\n\n  return server\n}\nfunction startBasicAuthServer () {\n  const server = http.createServer(handle)\n\n  function handle (req, res) {\n    if (!req.headers.authorization || req.headers.authorization.indexOf('Basic ') === -1) {\n      res.writeHead(401)\n      return res.end()\n    }\n\n    res.writeHead(200)\n    res.end('hello world')\n  }\n\n  server.listen(0)\n  server.unref()\n\n  return server\n}\n\nfunction customizeHAR (fixturePath, replaced, domain) {\n  const har = JSON.parse(JSON.stringify(require(fixturePath)))\n  for (const entry of har.log.entries) {\n    entry.request.url = entry.request.url.replace(replaced, domain)\n  }\n  return har\n}\n\nmodule.exports.startServer = startServer\nmodule.exports.startTimeoutServer = startTimeoutServer\nmodule.exports.startSocketDestroyingServer = startSocketDestroyingServer\nmodule.exports.startHttpsServer = startHttpsServer\nmodule.exports.startTrailerServer = startTrailerServer\nmodule.exports.startTlsServer = startTlsServer\nmodule.exports.startMultipartServer = startMultipartServer\nmodule.exports.startBasicAuthServer = startBasicAuthServer\nmodule.exports.customizeHAR = customizeHAR\n\nfunction noop () {}\n"
  },
  {
    "path": "test/httpClient.test.js",
    "content": "'use strict'\n\nconst os = require('os')\nconst http = require('http')\nconst path = require('path')\nconst test = require('tap').test\nconst Client = require('../lib/httpClient')\nconst helper = require('./helper')\nconst server = helper.startServer()\nconst timeoutServer = helper.startTimeoutServer()\nconst httpsServer = helper.startHttpsServer()\nconst tlsServer = helper.startTlsServer()\nconst trailerServer = helper.startTrailerServer()\nconst bl = require('bl')\nconst fs = require('fs')\n\nconst makeResponseFromBody = ({ server, method, body, headers = {} }) => {\n  const sentHeaders = {\n    Connection: 'keep-alive',\n    ...headers\n  }\n  if (!sentHeaders['Content-Length'] && body) {\n    sentHeaders['Content-Length'] = body.length\n  }\n  return `${method} / HTTP/1.1\\r\\nHost: localhost:${\n    server.address().port\n  }\\r\\n${\n    Object.keys(sentHeaders).map(name => `${name}: ${sentHeaders[name]}`).join('\\r\\n')\n  }\\r\\n${\n    body ? `\\r\\n${body}` : '\\r\\n'\n  }`\n}\n\ntest('client calls a server twice', (t) => {\n  t.plan(4)\n\n  const client = new Client(server.address())\n  let count = 0\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    t.ok(length > 'hello world'.length, 'length includes the headers')\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client calls a https server twice', (t) => {\n  t.plan(4)\n\n  const opts = httpsServer.address()\n  opts.protocol = 'https:'\n  const client = new Client(opts)\n  let count = 0\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    t.ok(length > 'hello world'.length, 'length includes the headers')\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client calculates correct duration when using pipelining', (t) => {\n  t.plan(6)\n\n  const delayResponse = 500\n  const lazyServer = helper.startServer({ delayResponse })\n  const opts = lazyServer.address()\n  opts.pipelining = 2\n  const startTime = process.hrtime()\n  const client = new Client(opts)\n  let count = 0\n\n  client.on('response', (statusCode, length, duration) => {\n    t.equal(statusCode, 200, 'status code matches')\n    t.ok(duration > delayResponse, `Expected response delay > ${delayResponse}ms but got ${duration}ms`)\n\n    const hrduration = process.hrtime(startTime)\n    const maxExpectedDuration = hrduration[0] * 1e3 + hrduration[1] / 1e6\n    t.ok(duration < maxExpectedDuration, `Expected response delay < ${maxExpectedDuration}ms but got ${duration}ms`)\n\n    if (++count === 2) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client calls a tls server without SNI servername twice', (t) => {\n  t.plan(4)\n\n  const opts = tlsServer.address()\n  opts.protocol = 'https:'\n  const client = new Client(opts)\n  let count = 0\n\n  client.on('headers', (response) => {\n    t.equal(response.statusCode, 200, 'status code matches')\n    t.same(response.headers, ['X-servername', '', 'Content-Length', '0'])\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client calls a tls server with SNI servername twice', (t) => {\n  t.plan(4)\n\n  const opts = tlsServer.address()\n  opts.protocol = 'https:'\n  opts.servername = 'example.com'\n  const client = new Client(opts)\n  let count = 0\n\n  client.on('headers', (response) => {\n    t.equal(response.statusCode, 200, 'status code matches')\n    t.same(response.headers, ['X-servername', opts.servername, 'Content-Length', '0'])\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client uses SNI servername from URL hostname by default', (t) => {\n  t.plan(4)\n\n  const opts = tlsServer.address()\n  opts.protocol = 'https:'\n  opts.hostname = 'localhost'\n  const client = new Client(opts)\n  let count = 0\n\n  client.on('headers', (response) => {\n    t.equal(response.statusCode, 200, 'status code matches')\n    t.same(response.headers, ['X-servername', opts.hostname, 'Content-Length', '0'])\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client prefers SNI servername from opts over URL hostname', (t) => {\n  t.plan(4)\n\n  const opts = tlsServer.address()\n  opts.protocol = 'https:'\n  opts.hostname = 'localhost'\n  opts.servername = 'example.com'\n  const client = new Client(opts)\n  let count = 0\n\n  client.on('headers', (response) => {\n    t.equal(response.statusCode, 200, 'status code matches')\n    t.same(response.headers, ['X-servername', opts.servername, 'Content-Length', '0'])\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client ignores IP address in hostname-derived SNI servername', (t) => {\n  t.plan(4)\n\n  const opts = tlsServer.address()\n  opts.protocol = 'https:'\n  opts.hostname = opts.address\n  const client = new Client(opts)\n  let count = 0\n\n  client.on('headers', (response) => {\n    t.equal(response.statusCode, 200, 'status code matches')\n    t.same(response.headers, ['X-servername', '', 'Content-Length', '0'])\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client ignores falsy SNI servername', (t) => {\n  t.plan(4)\n\n  const opts = tlsServer.address()\n  opts.protocol = 'https:'\n  opts.servername = ''\n  const client = new Client(opts)\n  let count = 0\n\n  client.on('headers', (response) => {\n    t.equal(response.statusCode, 200, 'status code matches')\n    t.same(response.headers, ['X-servername', '', 'Content-Length', '0'])\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client passes through tlsOptions to connect', (t) => {\n  t.plan(4)\n\n  const opts = tlsServer.address()\n  opts.protocol = 'https:'\n  opts.tlsOptions = {\n    key: fs.readFileSync(path.join(__dirname, '/key.pem')),\n    cert: fs.readFileSync(path.join(__dirname, '/cert.pem')),\n    passphrase: 'test'\n  }\n  const client = new Client(opts)\n  let count = 0\n\n  client.on('headers', (response) => {\n    t.equal(response.statusCode, 200, 'status code matches')\n    t.same(response.headers, ['X-servername', '', 'X-email', 'tes@test.com', 'Content-Length', '0'])\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('http client automatically reconnects', (t) => {\n  t.plan(4)\n\n  const client = new Client(server.address())\n  let count = 0\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    t.ok(length > 'hello world'.length, 'length includes the headers')\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n\n  server.once('request', function (req, res) {\n    setImmediate(() => {\n      req.socket.destroy()\n    })\n  })\n})\n\ntest('http clients should have a different body', (t) => {\n  t.plan(3)\n\n  let clientCnt = 0\n  const clients = []\n  const reqArray = ['John', 'Gabriel', 'Jason']\n  const opts = server.address()\n\n  opts.setupClient = (client) => {\n    client.setBody(JSON.stringify({ name: reqArray[clientCnt] }))\n    clientCnt++\n  }\n\n  for (let i = 0; i < 3; i++) {\n    const client = new Client(opts)\n\n    clients.push(client)\n  }\n\n  for (let i = 0; i < clients.length; i++) {\n    const client = clients[i]\n    const body = JSON.parse(client.requestIterator.currentRequest.body)\n\n    t.equal(body.name, reqArray[i], 'body match')\n    client.destroy()\n  }\n})\n\ntest('client supports custom headers', (t) => {\n  t.plan(3)\n\n  const opts = server.address()\n  opts.headers = {\n    hello: 'world'\n  }\n  const client = new Client(opts)\n\n  server.once('request', (req, res) => {\n    t.equal(req.headers.hello, 'world', 'custom header matches')\n  })\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    t.ok(length > 'hello world'.length, 'length includes the headers')\n    client.destroy()\n  })\n})\n\ntest('client supports custom headers in requests', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.requests = [\n    {\n      path: '/',\n      method: 'POST',\n      headers: {\n        'content-type': 'application/json'\n      },\n      body: JSON.stringify({\n        foo: 'example'\n      })\n    }\n  ]\n  const client = new Client(opts)\n\n  server.once('request', (req, res) => {\n    t.equal(req.headers['content-type'], 'application/json', 'custom header matches')\n  })\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    client.destroy()\n  })\n})\n\ntest('client supports host custom header', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.headers = {\n    host: 'www.autocannon.com'\n  }\n  const client = new Client(opts)\n\n  server.once('request', (req, res) => {\n    t.equal(req.headers.host, 'www.autocannon.com', 'host header matches')\n  })\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    client.destroy()\n  })\n})\n\ntest('client supports host custom header with mixed case', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.headers = {\n    Host: 'www.autocannon.com'\n  }\n  const client = new Client(opts)\n\n  server.once('request', (req, res) => {\n    t.equal(req.headers.host, 'www.autocannon.com', 'host header matches')\n  })\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    client.destroy()\n  })\n})\n\ntest('client supports response trailers', (t) => {\n  t.plan(3)\n\n  const client = new Client(trailerServer.address())\n  let n = 0\n  client.on('body', (raw) => {\n    if (++n === 1) {\n      // trailer value\n      t.ok(/7895bf4b8828b55ceaf47747b4bca667/.test(raw.toString()))\n    }\n  })\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    t.ok(length > 'hello world'.length, 'length includes the headers')\n    client.destroy()\n  })\n})\n\n;\n[\n  'DELETE',\n  'POST',\n  'PUT'\n].forEach((method) => {\n  test(`client supports ${method}`, (t) => {\n    t.plan(3)\n\n    const opts = server.address()\n    opts.method = method\n\n    const client = new Client(opts)\n\n    server.once('request', (req, res) => {\n      t.equal(req.method, method, 'custom method matches')\n    })\n\n    client.on('response', (statusCode, length) => {\n      t.equal(statusCode, 200, 'status code matches')\n      t.ok(length > 'hello world'.length, 'length includes the headers')\n      client.destroy()\n    })\n  })\n})\n\ntest('client supports sending a body', (t) => {\n  t.plan(4)\n\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.body = Buffer.from('hello world')\n\n  const client = new Client(opts)\n\n  server.once('request', (req, res) => {\n    req.pipe(bl((err, body) => {\n      t.error(err)\n      t.same(body.toString(), opts.body.toString(), 'body matches')\n    }))\n  })\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    t.ok(length > 'hello world'.length, 'length includes the headers')\n    client.destroy()\n  })\n})\n\ntest('client supports sending a body which is a string', (t) => {\n  t.plan(4)\n\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.body = 'hello world'\n\n  const client = new Client(opts)\n\n  server.once('request', (req, res) => {\n    req.pipe(bl((err, body) => {\n      t.error(err)\n      t.same(body.toString(), opts.body, 'body matches')\n    }))\n  })\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    t.ok(length > 'hello world'.length, 'length includes the headers')\n    client.destroy()\n  })\n})\n\ntest('client supports sending a body passed on the CLI as JSON array', (t) => {\n  t.plan(3)\n\n  const body = [{ a: [{ b: 1 }] }]\n  const jsonBody = JSON.stringify(body)\n\n  // this odd format is parsed by the subarg parser when body is a JSON array\n  const mangledBody = { _: [JSON.stringify(body[0])] }\n\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.body = mangledBody\n\n  const client = new Client(opts)\n\n  server.once('request', (req, res) => {\n    req.pipe(bl((err, body) => {\n      t.error(err)\n      t.same(body.toString(), jsonBody, 'body matches')\n    }))\n  })\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    client.destroy()\n  })\n})\n\ntest('client supports changing the body', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.body = 'hello world'\n\n  const client = new Client(opts)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts }), 'request is okay before modifying')\n\n  const body = 'modified'\n  client.setBody(body)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts, body }), 'body changes updated request')\n  client.destroy()\n})\n\ntest('client supports changing the headers', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  const client = new Client(opts)\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts }), 'request is okay before modifying')\n\n  const headers = { header: 'modified' }\n  client.setHeaders(headers)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts, headers }), 'header changes updated request')\n  client.destroy()\n})\n\ntest('client supports changing the headers and body', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.body = 'hello world'\n  opts.method = 'POST'\n\n  const client = new Client(opts)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts }), 'request is okay before modifying')\n\n  const body = 'modified'\n  const headers = { header: 'modifiedHeader' }\n  client.setBody(body)\n  client.setHeaders(headers)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts, headers, body }), 'changes updated request')\n  client.destroy()\n})\n\ntest('client supports changing the headers and body together', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.body = 'hello world'\n  opts.method = 'POST'\n\n  const client = new Client(opts)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts }), 'request is okay before modifying')\n\n  const body = 'modified'\n  const headers = { header: 'modifiedHeader' }\n  client.setHeadersAndBody(headers, body)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts, headers, body }), 'changes updated request')\n  client.destroy()\n})\n\ntest('client supports changing the headers and body with null values', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.body = 'hello world'\n  opts.method = 'POST'\n\n  const client = new Client(opts)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts }), 'request is okay before modifying')\n\n  client.setBody(null)\n  client.setHeaders(null)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, method: 'POST' }), 'changes updated request')\n  client.destroy()\n})\n\ntest('client supports changing the headers and body together with null values', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.body = 'hello world'\n  opts.method = 'POST'\n\n  const client = new Client(opts)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts }), 'request is okay before modifying')\n\n  client.setHeadersAndBody(null, null)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, method: 'POST' }), 'changes updated request')\n  client.destroy()\n})\n\ntest('client supports updating the current request object', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.body = 'hello world'\n  opts.method = 'POST'\n\n  const client = new Client(opts)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts }), 'request is okay before modifying')\n\n  const newReq = {\n    headers: {\n      header: 'modifiedHeader'\n    },\n    body: 'modified',\n    method: 'GET'\n  }\n  client.setRequest(newReq)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...newReq }), 'changes updated request')\n  client.destroy()\n})\n\ntest('client customiseRequest function overwrites the headers and body', (t) => {\n  t.plan(5)\n\n  const opts = server.address()\n  opts.body = 'hello world'\n  opts.method = 'POST'\n  const body = 'modified'\n  const headers = { header: 'modifiedHeader' }\n  opts.setupClient = (client) => {\n    t.ok(client.setHeadersAndBody, 'client had setHeadersAndBody method')\n    t.ok(client.setHeaders, 'client had setHeaders method')\n    t.ok(client.setBody, 'client had setBody method')\n\n    client.setHeadersAndBody(headers, body)\n  }\n\n  const client = new Client(opts)\n\n  t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts, headers, body }), 'changes updated request')\n\n  t.notSame(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts }), 'changes updated request')\n\n  client.destroy()\n})\n\ntest('client should throw when attempting to modify the request with a pipelining greater than 1', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.pipelining = 10\n  const client = new Client(opts)\n\n  t.throws(() => client.setHeaders({}))\n\n  client.destroy()\n})\n\ntest('client pipelined requests count should equal pipelining when greater than 1', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.pipelining = 10\n  const client = new Client(opts)\n\n  t.equal(client.pipelinedRequests.size(), client.opts.pipelining)\n\n  client.destroy()\n})\n\ntest('client should emit a timeout when no response is received', (t) => {\n  t.plan(1)\n\n  const opts = timeoutServer.address()\n  opts.timeout = 1\n  const client = new Client(opts)\n\n  client.on('timeout', () => {\n    t.ok(1, 'timeout should have happened')\n    client.destroy()\n  })\n})\n\ntest('client should emit 2 timeouts when no responses are received', (t) => {\n  t.plan(2)\n\n  const opts = timeoutServer.address()\n  opts.timeout = 1\n  const client = new Client(opts)\n  let count = 0\n  client.on('timeout', () => {\n    t.ok(1, 'timeout should have happened')\n    if (count++ > 0) {\n      client.destroy()\n    }\n  })\n})\n\ntest('client should have 2 different requests it iterates over', (t) => {\n  t.plan(3)\n  const server = helper.startServer()\n  const opts = server.address()\n\n  const requests = [\n    {\n      method: 'POST',\n      body: 'hello world again'\n    },\n    {\n      method: 'GET',\n      body: 'modified'\n    }\n  ]\n\n  const client = new Client(opts)\n  let number = 0\n  client.setRequests(requests)\n  client.on('response', (statusCode, length) => {\n    number++\n    switch (number) {\n      case 1:\n      case 3:\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...requests[0] }), 'request was okay')\n        break\n      case 2:\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...requests[1] }), 'body changes updated request')\n        break\n      case 4:\n        client.destroy()\n        t.end()\n        break\n    }\n  })\n})\n\ntest('client should emit reset when request iterator has reset', (t) => {\n  t.plan(1)\n  const server = helper.startServer()\n  const opts = server.address()\n\n  const requests = [\n    {\n      method: 'POST',\n      body: 'hello world again'\n    },\n    {\n      method: 'POST',\n      body: 'modified',\n      // falsey result will reset\n      setupRequest: () => {}\n    },\n    {\n      method: 'POST',\n      body: 'never used'\n    }\n  ]\n\n  const client = new Client(opts)\n  client.setRequests(requests)\n  client.on('reset', () => {\n    client.destroy()\n    t.end()\n  })\n  client.on('response', () => {\n    t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...requests[0] }), 'first request was okay')\n  })\n})\n\ntest('client should stop when first setupRequest() fails', (t) => {\n  t.plan(1)\n  const server = helper.startServer()\n  const opts = server.address()\n\n  const client = new Client(opts)\n  t.throws(\n    () => client.setRequests([{ method: 'GET', setupRequest: () => {} }]),\n    'First setupRequest() failed did not returned valid request. Stopping'\n  )\n  client.destroy()\n  t.end()\n})\n\ntest('client exposes response bodies and statuses', (t) => {\n  const server = helper.startServer({\n    body: ({ method }) => method === 'POST' ? 'hello!' : 'world!'\n  })\n  const opts = server.address()\n  opts.requests = [\n    {\n      method: 'POST',\n      body: 'hello world!',\n      onResponse: (status, body) => responses.push({ status, body })\n    },\n    {\n      method: 'POST',\n      body: 'hello world again'\n    },\n    {\n      method: 'GET',\n      onResponse: (status, body) => responses.push({ status, body })\n    }\n  ]\n  const responses = []\n\n  const client = new Client(opts)\n  let number = 0\n  client.on('response', (statusCode, length) => {\n    number++\n    switch (number) {\n      case 1:\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts.requests[0] }), 'first request')\n        t.same(responses, [{\n          status: 200,\n          body: 'hello!'\n        }])\n        break\n      case 2:\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts.requests[1] }), 'second request')\n        t.same(responses, [{\n          status: 200,\n          body: 'hello!'\n        }])\n        break\n      case 3:\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts.requests[2] }), 'third request')\n        t.same(responses, [{\n          status: 200,\n          body: 'hello!'\n        }, {\n          status: 200,\n          body: 'world!'\n        }])\n        break\n      case 4:\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts.requests[0] }), 'first request')\n        t.same(responses, [{\n          status: 200,\n          body: 'hello!'\n        }, {\n          status: 200,\n          body: 'world!'\n        }, {\n          status: 200,\n          body: 'hello!'\n        }])\n        client.destroy()\n        t.end()\n        break\n    }\n  })\n})\n\ntest('client keeps context and reset it when looping on requests', (t) => {\n  const server = helper.startServer()\n  const opts = server.address()\n  let number = 0\n  const expectedResponse = 'hello world'\n\n  opts.requests = [\n    {\n      method: 'POST',\n      body: 'hello world again',\n      onResponse: (status, body, context) => {\n        if (number < 3) {\n          t.same(context, {}, 'context was supposed to be null')\n          context.previousRes = body\n        }\n      }\n    },\n    {\n      method: 'PUT',\n      setupRequest: (req, context) => {\n        if (number < 3) {\n          t.same(context, { previousRes: expectedResponse }, 'context was supposed to contain previous response')\n        }\n        return Object.assign({}, req, { body: context.previousRes })\n      }\n    }\n  ]\n\n  const client = new Client(opts)\n  client.on('response', (statusCode, length) => {\n    number++\n    switch (number) {\n      case 1:\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, ...opts.requests[0] }), 'hard-coded body')\n        break\n      case 2:\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, method: 'PUT', body: expectedResponse }), 'dynamic body')\n        client.destroy()\n        t.end()\n        break\n    }\n  })\n})\n\ntest('client supports http basic authentication', (t) => {\n  t.plan(2)\n  const server = helper.startServer()\n  const opts = server.address()\n  opts.auth = 'username:password'\n  const client = new Client(opts)\n\n  server.once('request', (req, res) => {\n    t.equal(req.headers.authorization, 'Basic dXNlcm5hbWU6cGFzc3dvcmQ=', 'authorization header matches')\n  })\n\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    client.destroy()\n    t.end()\n  })\n})\n\ntest('should return client instance', (t) => {\n  t.plan(1)\n  const caller = {}\n  const opts = server.address()\n  opts.auth = 'username:password'\n  const client = Client.call(caller, opts)\n\n  t.type(client, Client)\n  client.destroy()\n})\n\ntest('client calls twice using socket on secure server', (t) => {\n  t.plan(4)\n  const socketPath = process.platform === 'win32'\n    ? path.join('\\\\\\\\?\\\\pipe', process.cwd(), 'autocannon-' + Date.now())\n    : path.join(os.tmpdir(), 'autocannon-' + Date.now() + '.sock')\n\n  helper.startHttpsServer({ socketPath })\n  const client = new Client({\n    url: 'localhost',\n    protocol: 'https:',\n    socketPath,\n    connections: 1\n  })\n  let count = 0\n  client.on('response', (statusCode, length) => {\n    t.equal(statusCode, 200, 'status code matches')\n    t.ok(length > 'hello world'.length, 'length includes the headers')\n    if (count++ > 0) {\n      client.destroy()\n      t.end()\n    }\n  })\n})\n\ntest('client emits mistmatch when expectBody doesn\\'t match actual body', (t) => {\n  const responses = ['hello...', 'world!']\n  const server = helper.startServer({\n    body: ({ method }) => responses[method === 'POST' ? 0 : 1]\n  })\n  const opts = server.address()\n  opts.requests = [\n    {\n      method: 'POST',\n      body: 'hi there!'\n    },\n    {\n      method: 'GET'\n    }\n  ]\n  opts.expectBody = responses[0]\n\n  const client = new Client(opts)\n  client.on('mismatch', (body) => {\n    // we expect body mismatch on second request\n    t.same(body, responses[1])\n    client.destroy()\n    t.end()\n  })\n})\n\ntest('client invokes appropriate onResponse when using pipelining', (t) => {\n  const server = helper.startServer({\n    body: ({ method }) => method\n  })\n  const opts = server.address()\n  opts.pipelining = 2\n  const responses = []\n  const onResponse = (status, body) => responses.push(body)\n  opts.requests = [\n    {\n      method: 'POST',\n      onResponse\n    },\n    {\n      method: 'GET',\n      onResponse\n    },\n    {\n      method: 'PUT',\n      onResponse\n    }\n  ]\n\n  const client = new Client(opts)\n  let number = 0\n  client.on('response', (statusCode, length) => {\n    number++\n    switch (number) {\n      case 1:\n        // 1st & 2nd were sent, receiving 1st\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, method: 'GET' }), 'current should be second request')\n        t.same(responses, ['POST'])\n        break\n      case 2:\n        // 3rd was sent as 1st is finished, receiving 2nd\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, method: 'PUT' }), 'current should be third request')\n        t.same(responses, ['POST', 'GET'])\n        break\n      case 3:\n        // 1st was resent, receiving 3rd\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, method: 'POST' }), 'current should be first request')\n        t.same(responses, ['POST', 'GET', 'PUT'])\n        break\n      case 4:\n        // 2nd was resent, receiving 1st\n        t.same(client.getRequestBuffer().toString(), makeResponseFromBody({ server, method: 'GET' }), 'current should be second request')\n        t.same(responses, ['POST', 'GET', 'PUT', 'POST'])\n        client.destroy()\n        t.end()\n        break\n    }\n  })\n})\n\ntest('client supports receiving large response body', (t) => {\n  t.plan(2)\n\n  const mockBody = Array.from({ length: 1024 * 10 }, (_, i) => `str-${i}`).join('\\n')\n  const server = http.createServer((req, res) => {\n    res.end(mockBody)\n  })\n  server.listen(0)\n  server.unref()\n\n  let onResponseCalled = 0\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.body = Buffer.from('hello world')\n  opts.requests = [\n    {\n      path: '/',\n      method: 'GET',\n      onResponse: (...args) => {\n        onResponseCalled++\n      }\n    }\n  ]\n\n  const client = new Client(opts)\n\n  client.on('response', (statusCode, length) => {\n    t.equal(onResponseCalled, 1, 'onResponse should be called only once')\n    t.equal(statusCode, 200, 'status code matches')\n    client.destroy()\n  })\n})\n"
  },
  {
    "path": "test/httpRequestBuilder.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst helper = require('./helper')\nconst server = helper.startServer()\nconst RequestBuilder = require('../lib/httpRequestBuilder')\nconst httpMethods = require('../lib/httpMethods')\n\ntest('request builder should create a request with sensible defaults', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n\n  const build = RequestBuilder(opts)\n\n  const result = build()\n  t.same(result,\n    Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\n\\r\\n`),\n    'request is okay')\n})\n\ntest('request builder should allow default overwriting', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  const build = RequestBuilder(opts)\n\n  const result = build()\n  t.same(result,\n    Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\n\\r\\n`),\n    'request is okay')\n})\n\ntest('request builder should allow per build overwriting', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  const build = RequestBuilder(opts)\n\n  const result = build({ method: 'GET' })\n\n  t.same(result,\n    Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\n\\r\\n`),\n    'request is okay')\n})\n\ntest('request builder should throw on unknown http method', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n\n  const build = RequestBuilder(opts)\n\n  t.throws(() => build({ method: 'UNKNOWN' }))\n})\n\ntest('request builder should accept all valid standard http methods', (t) => {\n  t.plan(httpMethods.length)\n  httpMethods.forEach((method) => {\n    const opts = server.address()\n\n    const build = RequestBuilder(opts)\n\n    t.doesNotThrow(() => build({ method }), `${method} should be usable by the request builded`)\n  })\n  t.end()\n})\n\ntest('request builder should add a Content-Length header when the body buffer exists as a default override', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.body = 'body'\n\n  const build = RequestBuilder(opts)\n\n  const result = build()\n  t.same(result,\n    Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 4\\r\\n\\r\\nbody`),\n    'request is okay')\n})\n\ntest('request builder should add a Content-Length header when the body buffer exists as per build override', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  const build = RequestBuilder(opts)\n\n  const result = build({ body: 'body' })\n  t.same(result,\n    Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 4\\r\\n\\r\\nbody`),\n    'request is okay')\n})\n\ntest('request builder should add only one HOST header', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.headers = {\n    Host: 'example.com'\n  }\n\n  const build = RequestBuilder(opts)\n\n  const result = build({ body: 'body' })\n  t.same(result,\n    Buffer.from('POST / HTTP/1.1\\r\\nConnection: keep-alive\\r\\nHost: example.com\\r\\nContent-Length: 4\\r\\n\\r\\nbody'),\n    'request is okay')\n})\n\ntest('request builder should add a Content-Length header with correct calculated value when the body buffer exists and idReplacement is enabled as a default override', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.body = '[<id>]'\n  opts.idReplacement = true\n\n  const build = RequestBuilder(opts)\n\n  const result = build()\n  t.same(result,\n    Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 33\\r\\n\\r\\n[<id>]`),\n    'request is okay')\n})\n\ntest('request builder should add a Content-Length header with value \"[<contentLength>]\" when the body buffer exists and idReplacement is enabled as a per build override', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.body = '[<id>]'\n\n  const build = RequestBuilder(opts)\n\n  const result = build({ idReplacement: true })\n  t.same(result,\n    Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 33\\r\\n\\r\\n[<id>]`),\n    'request is okay')\n})\n\ntest('request builder should allow http basic authentication', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.auth = 'username:password'\n\n  const build = RequestBuilder(opts)\n\n  const result = build()\n  t.same(result,\n    Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nAuthorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=\\r\\n\\r\\n`),\n    'request is okay')\n})\n\ntest('should throw error if body is not a string or a buffer', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n\n  const build = RequestBuilder(opts)\n\n  try {\n    build({ body: [] })\n  } catch (error) {\n    t.equal(error.message, 'body must be either a string or a buffer')\n  }\n})\n"
  },
  {
    "path": "test/key.pem",
    "content": "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: DES-EDE3-CBC,081E2A4DFAF06358\n\nyfmHgUwn2RV+CGBYKJMBfLu+EQCKIOprjUxSBPaytTwc6NsOeVjOb/iAcVvr0D6B\na6Kc8BrIMfVVjABghufAnJXav+aB6Q/LkfaqJp6AfVI86WMaCBb0Fzkg8IYYSbgy\nJiskTYmV1IrjYfR6TEtQmeFP0DMh1RU9VLIfehEFRMU7LcDh5Ah3bsZE5muBSlws\n98IVBwEwEXoQdVxLJmz+kR3fEjXYmgNZQzP6GFb3jQENV3XijQffgQ4ajREB5EZL\nZag+K0vwbFZORqWNfI5VFVVWOEErRF0BSDFZWJP8jbiSX4tNyEFRzO2dgdjGycqt\n3dtWbofY28Hjf36P3ee2mMHe3V8XZUtCtfufiYFLKHedHShg7FolfSSMmIfNmSgE\njCnTFNU4Fsq8I/q6ywfVh3CP/rjEe/I4GAU7wql0PW7LXjAHqRjLshJ09mX5J3w8\n+tvsrYiBLsgmtdnX47LDJDyeYZY5gbBaz9XsNd432z1M6lRILsT48RYwXEz5oHBE\nEl1oKNko/PHljDkTph/xI1jUlkYJ7w9//stao/9VLzn0XQG839+lz1MNi/opmpUi\n3P9Z3+yTbdwibRHLmgCR6PNE4EJdfv0VNantsxiSATyhIS/xPGhU4lich8gMNDcv\n9IcUSCTkmVGy8ksJITt/m7mespQ8Y1oD3xvXoaCbTJ8aig6EQ5flYwmmiIHrWSol\n5w5iKjkNbdNYftAg+RB6+EwrNz43oHLYRMB3dckYc1L+otd/X+lmlTlqOo8WDgh1\ncH4MI4cwjdYmFdp5Nhfd9/nWFtayqcj59IBgEAMzafbzcmhma6oJD9GsxSnw9eRY\nvb3b7fhtcqeKDgqfN4TklX/7kkoLpRVMEdW4uNyU0US1QC5gTxiFRW3/XGhaQJ6o\nquE2UP3qySp7kHsz++P6mya2xF2JNcKh1zxxF+wSnjymUxaWXz1LKGQs4iOjU8Xg\nHkPD3wTxtMXXZw/0LGMu7XLMFWMV3nuMGtt/I/GGtCbKbFkDOOI4imZWpmsvvO0s\nliMhhtCwJTqkYvlxPtfcLSKFJrwcCeb2xHcLEDAaRa5xUbUt3P60l7Ujyv3p+bVi\nsItGE6Mk8kGXglmZGBA0RIGhXlX7r6pNryqMUy0CU5/YniJFCKoaU9WOG8VwHynY\npsqvZ1umAtl4twe7SxF0znLdOGiYsOOQls5wcz1rDfj5IXRcbrWEeLwYD9b0oXms\nTvgkcfHlutZ+M+NtP7wGFBen1X68HsYBhvhLrVbox5bgMZVrXCN4+HiBO4VAwq+m\nc2akVF9kQfoR5iGMhzzgskH8c7TWuF7IDqA8KBuFKcicBHK/Ns/1ljycWr07cLJc\nWabX25aI0x58+ise5fJAQvNW6Oq2Jvt3qcUXnr2OMnR6WUQ7amgVC7JAdfRoahYU\n2WJl6jkCANIv5mY7ubEgVgXnuYYL4ljScpMZLER3cLW4uQnRXWo4zciz06+BadfI\nPjGb9xt+3EE4VB64O3yI9yBoDAM/lL0oadB8TPxFLN1NZA3gixbE6dXd5jGD9YxJ\nrJNu8xR4MUttiHA8+aVw84kiqsY8OzrjbJnVr2SSXARYw4dnz8DfxhPCt6hMCWnw\n-----END RSA PRIVATE KEY-----\n"
  },
  {
    "path": "test/onPort.test.js",
    "content": "'use strict'\n\nconst path = require('path')\nconst { test } = require('tap')\nconst spawn = require('child_process').spawn\nconst split = require('split2')\nconst hasAsyncHooks = require('has-async-hooks')\n\ntest('--on-port flag', { skip: !hasAsyncHooks() }, (t) => {\n  const lines = [\n    /Running 1s test @ .*$/,\n    /10 connections.*$/,\n    /$/,\n    /.*/,\n    /$/,\n    /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\n    /.*/,\n    /Latency.*$/,\n    /$/,\n    /.*/,\n    /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\n    /.*/,\n    /Req\\/Sec.*$/,\n    /$/,\n    /Bytes\\/Sec.*$/,\n    /.*/,\n    /$/,\n    /Req\\/Bytes counts sampled once per second.*$/,\n    /# of samples: 10*$/,\n    /$/,\n    /.* requests in ([0-9]|\\.)+s, .* read/\n  ]\n\n  t.plan(lines.length * 2)\n\n  const child = spawn(process.execPath, [\n    path.join(__dirname, '..'),\n    '-c', '10',\n    '-d', '1',\n    '--on-port', '/',\n    '--', 'node', path.join(__dirname, './targetProcess')\n  ], {\n    cwd: __dirname,\n    env: process.env,\n    stdio: ['ignore', 'pipe', 'pipe'],\n    detached: false\n  })\n\n  t.teardown(() => {\n    child.kill()\n  })\n\n  child\n    .stderr\n    .pipe(split())\n    .on('data', (line) => {\n      const regexp = lines.shift()\n      t.ok(regexp, 'we are expecting this line')\n      t.ok(regexp.test(line), 'line matches ' + regexp)\n    })\n})\n\ntest('assume --on-port flag if -- node is set', { skip: !hasAsyncHooks() }, (t) => {\n  const lines = [\n    /Running 1s test @ .*$/,\n    /10 connections.*$/,\n    /$/,\n    /.*/,\n    /$/,\n    /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\n    /.*/,\n    /Latency.*$/,\n    /$/,\n    /.*/,\n    /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\n    /.*/,\n    /Req\\/Sec.*$/,\n    /$/,\n    /Bytes\\/Sec.*$/,\n    /.*/,\n    /$/,\n    /Req\\/Bytes counts sampled once per second.*$/,\n    /# of samples: 10*$/,\n    /$/,\n    /.* requests in ([0-9]|\\.)+s, .* read/\n  ]\n\n  t.plan(lines.length * 2)\n\n  const child = spawn(process.execPath, [\n    path.join(__dirname, '..'),\n    '-c', '10',\n    '-d', '1',\n    '/',\n    '--', 'node', path.join(__dirname, './targetProcess')\n  ], {\n    cwd: __dirname,\n    env: process.env,\n    stdio: ['ignore', 'pipe', 'pipe'],\n    detached: false\n  })\n\n  t.teardown(() => {\n    child.kill()\n  })\n\n  child\n    .stderr\n    .pipe(split())\n    .on('data', (line) => {\n      const regexp = lines.shift()\n      t.ok(regexp, 'we are expecting this line')\n      t.ok(regexp.test(line), 'line matches ' + regexp + `actual: ${line}  expected: ${regexp}`)\n    })\n})\n"
  },
  {
    "path": "test/parseHAR.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst { parseHAR } = require('../lib/parseHAR')\n\ntest('should throw on empty HAR', (t) => {\n  t.plan(1)\n\n  t.throws(() => parseHAR(null), /Could not parse HAR content: no entries found/)\n  t.end()\n})\n\ntest('should throw on HAR with no entries', (t) => {\n  t.plan(1)\n\n  t.throws(() => parseHAR({\n    log: {\n      version: '1.2',\n      creator: {\n        name: 'Firefox',\n        version: '80.0.1'\n      },\n      pages: [\n        {\n          startedDateTime: '2020-09-28T16:43:28.987+02:00',\n          id: 'page_1',\n          title: 'mcollina/autocannon: fast HTTP/1.1 benchmarking tool written in Node.js',\n          pageTimings: {\n            onContentLoad: 1234,\n            onLoad: 1952\n          }\n        }\n      ]\n    }\n  }), /Could not parse HAR content: no entries found/)\n  t.end()\n})\n\ntest('should throw on HAR with empty entries', (t) => {\n  t.plan(1)\n\n  t.throws(() => parseHAR({\n    log: {\n      version: '1.2',\n      creator: {\n        name: 'Firefox',\n        version: '80.0.1'\n      },\n      pages: [\n        {\n          startedDateTime: '2020-09-28T16:43:28.987+02:00',\n          id: 'page_1',\n          title: 'mcollina/autocannon: fast HTTP/1.1 benchmarking tool written in Node.js',\n          pageTimings: {\n            onContentLoad: 1234,\n            onLoad: 1952\n          }\n        }\n      ],\n      entries: []\n    }\n  }), /Could not parse HAR content: no entries found/)\n  t.end()\n})\n\ntest('should parse and return GET entries', (t) => {\n  t.plan(1)\n\n  t.strictSame(parseHAR(require('./fixtures/httpbin-get.json')).get('https://httpbin.org'), [{\n    method: 'GET',\n    origin: 'https://httpbin.org',\n    path: '/get',\n    headers: {\n      Host: 'httpbin.org',\n      'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',\n      Accept: '*/*',\n      'Accept-Language': 'fr,en;q=0.5',\n      'Accept-Encoding': 'gzip, deflate, br',\n      Referer: 'https://httpbin.org/',\n      DNT: '1',\n      Connection: 'keep-alive'\n    }\n  }, {\n    method: 'GET',\n    origin: 'https://httpbin.org',\n    path: '/get?from=10&size=20&sort=+name',\n    headers: {\n      Host: 'httpbin.org',\n      'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',\n      Accept: '*/*',\n      'Accept-Language': 'fr,en;q=0.5',\n      'Accept-Encoding': 'gzip, deflate, br',\n      Referer: 'https://httpbin.org/',\n      DNT: '1',\n      Connection: 'keep-alive',\n      TE: 'Trailers'\n    }\n  }])\n  t.end()\n})\n\ntest('should throw on HAR with invalid entries', (t) => {\n  t.plan(2)\n\n  t.throws(() => parseHAR({\n    log: {\n      entries: ['invalid']\n    }\n  }), /Could not parse HAR content: invalid request in entry #1/)\n  t.throws(() => parseHAR({\n    log: {\n      entries: [{ request: { headers: [], url: 'http://localhost' } }, { request: null }]\n    }\n  }), /Could not parse HAR content: invalid request in entry #2/)\n\n  t.end()\n})\n\ntest('should throw on HAR with invalid headers', (t) => {\n  t.plan(2)\n  const url = 'http://localhost'\n\n  t.throws(() => parseHAR({\n    log: {\n      entries: [{ request: { headers: [], url } }, { request: { headers: ['foo'], url } }]\n    }\n  }), /Could not parse HAR content: invalid name or value in header #1 of entry #2/)\n  t.throws(() => parseHAR({\n    log: {\n      entries: [{ request: { headers: null } }]\n    }\n  }), /Could not parse HAR content: invalid headers array in entry #1/)\n  t.end()\n})\n\ntest('should parse and return POST entries', (t) => {\n  t.plan(1)\n\n  t.strictSame(parseHAR(require('./fixtures/httpbin-post.json')).get('https://httpbin.org'), [{\n    method: 'POST',\n    origin: 'https://httpbin.org',\n    path: '/post',\n    headers: {\n      Host: 'httpbin.org',\n      'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',\n      Accept: '*/*',\n      'Accept-Language': 'fr,en;q=0.5',\n      'Accept-Encoding': 'gzip, deflate, br',\n      Referer: 'https://httpbin.org/',\n      'Content-Type': 'multipart/form-data; boundary=---------------------------31420230025845772252453285324',\n      Origin: 'https://httpbin.org',\n      'Content-Length': '362',\n      DNT: '1',\n      Connection: 'keep-alive',\n      TE: 'Trailers'\n    },\n    body: '-----------------------------31420230025845772252453285324\\r\\nContent-Disposition: form-data; name=\"text\"\\r\\n\\r\\na text value\\r\\n-----------------------------31420230025845772252453285324\\r\\nContent-Disposition: form-data; name=\"file\"; filename=\"blob\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\nHello World!\\n\\r\\n-----------------------------31420230025845772252453285324--\\r\\n'\n  }, {\n    method: 'POST',\n    origin: 'https://httpbin.org',\n    path: '/post',\n    headers: {\n      Host: 'httpbin.org',\n      'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',\n      Accept: '*/*',\n      'Accept-Language': 'fr,en;q=0.5',\n      'Accept-Encoding': 'gzip, deflate, br',\n      Referer: 'https://httpbin.org/',\n      'Content-Type': 'application/x-www-form-urlencoded',\n      Origin: 'https://httpbin.org',\n      'Content-Length': '27',\n      DNT: '1',\n      Connection: 'keep-alive',\n      TE: 'Trailers'\n    },\n    body: 'text=a+text+value&number=10'\n  }])\n  t.end()\n})\n\ntest('should split requests per origin', (t) => {\n  t.plan(2)\n\n  const requetsPerOrigin = parseHAR(require('./fixtures/multi-domains.json'))\n  t.strictSame(requetsPerOrigin.get('https://httpbin.org'), [{\n    method: 'POST',\n    origin: 'https://httpbin.org',\n    path: '/post',\n    headers: {\n      Host: 'httpbin.org',\n      'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',\n      Accept: '*/*',\n      'Accept-Language': 'fr,en;q=0.5',\n      'Accept-Encoding': 'gzip, deflate, br',\n      Referer: 'https://httpbin.org/',\n      'Content-Type': 'multipart/form-data; boundary=---------------------------31420230025845772252453285324',\n      Origin: 'https://httpbin.org',\n      'Content-Length': '362',\n      DNT: '1',\n      Connection: 'keep-alive',\n      TE: 'Trailers'\n    },\n    body: '-----------------------------31420230025845772252453285324\\r\\nContent-Disposition: form-data; name=\"text\"\\r\\n\\r\\na text value\\r\\n-----------------------------31420230025845772252453285324\\r\\nContent-Disposition: form-data; name=\"file\"; filename=\"blob\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\nHello World!\\n\\r\\n-----------------------------31420230025845772252453285324--\\r\\n'\n  }, {\n    method: 'GET',\n    origin: 'https://httpbin.org',\n    path: '/get?from=10&size=20&sort=+name',\n    headers: {\n      Host: 'httpbin.org',\n      'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',\n      Accept: '*/*',\n      'Accept-Language': 'fr,en;q=0.5',\n      'Accept-Encoding': 'gzip, deflate, br',\n      Referer: 'https://httpbin.org/',\n      DNT: '1',\n      Connection: 'keep-alive',\n      TE: 'Trailers'\n    }\n  }])\n\n  t.strictSame(requetsPerOrigin.get('https://github.com'), [{\n    method: 'POST',\n    origin: 'https://github.com',\n    path: '/',\n    headers: {\n      Host: 'github.com',\n      'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0',\n      Accept: '*/*',\n      'Accept-Language': 'fr,en;q=0.5',\n      'Accept-Encoding': 'gzip, deflate, br',\n      Referer: 'https://github.com/',\n      'Content-Type': 'application/x-www-form-urlencoded',\n      Origin: 'https://github.com',\n      'Content-Length': '27',\n      DNT: '1',\n      Connection: 'keep-alive',\n      TE: 'Trailers'\n    },\n    body: 'text=a+text+value&number=10'\n  }])\n  t.end()\n})\n"
  },
  {
    "path": "test/pipelinedRequestsQueue.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst PipelinedRequestsQueue = require('../lib/pipelinedRequestsQueue')\n\ntest('PipelinedRequestsQueue measures time precisely', (t) => {\n  t.plan(2)\n\n  const delay = 42\n  const queue = new PipelinedRequestsQueue()\n\n  const startTime = process.hrtime()\n  queue.insertRequest()\n  setTimeout(() => {\n    const data = queue.terminateRequest()\n\n    t.ok(data.duration > delay, `Calculated duration ${data.duration} should not be less than the induced delay ${delay}`)\n\n    const hrduration = process.hrtime(startTime)\n    const maxExpectedDuration = Math.ceil(hrduration[0] * 1e3 + hrduration[1] / 1e6)\n    t.ok(data.duration <= maxExpectedDuration, `Calculated duration ${data.duration} should be less than the max expected duration ${maxExpectedDuration}`)\n  }, delay)\n})\n\ntest('PipelinedRequestsQueue is a queue/FIFO', (t) => {\n  const COUNT = 3\n  t.plan(COUNT)\n\n  const queue = new PipelinedRequestsQueue()\n\n  let count = COUNT\n  while (count > 0) {\n    queue.insertRequest(count--)\n  }\n\n  count = COUNT\n  while (count > 0) {\n    t.equal(queue.terminateRequest().req, count--)\n  }\n})\n\ntest('PipelinedRequestsQueue.clear() empties the queue', (t) => {\n  t.plan(5)\n\n  const queue = new PipelinedRequestsQueue()\n  t.equal(queue.size(), 0)\n  t.equal(queue.toArray().length, 0)\n  queue.insertRequest()\n  queue.insertRequest()\n  queue.insertRequest()\n  t.equal(queue.size(), 3)\n  t.equal(queue.toArray().length, 3)\n  queue.clear()\n  t.equal(queue.terminateRequest(), undefined)\n})\n\ntest('PipelinedRequestsQueue methods set values to the request in first-in-last-out order', (t) => {\n  t.plan(6)\n\n  const queue = new PipelinedRequestsQueue()\n  queue.insertRequest(1)\n  queue.insertRequest(2)\n\n  queue.addBody('1')\n  queue.addByteCount(1)\n  queue.setHeaders({ val: '1' })\n\n  const req1 = queue.terminateRequest()\n  t.equal(req1.req, 1)\n  t.equal(req1.body, '1')\n  t.same(req1.headers, { val: '1' })\n\n  queue.addBody('2')\n  queue.addByteCount(2)\n  queue.setHeaders({ val: '2' })\n\n  const req2 = queue.terminateRequest()\n  t.equal(req2.req, 2)\n  t.equal(req2.body, '2')\n  t.same(req2.headers, { val: '2' })\n})\n"
  },
  {
    "path": "test/printResult-process.js",
    "content": "'use strict'\r\n\r\nconst autocannon = require('../autocannon')\r\nconst exampleResult = require('./fixtures/example-result.json')\r\nconst crossArgv = require('cross-argv')\r\n\r\nlet opts = null\r\n\r\nif (process.argv.length > 2) {\r\n  const args = crossArgv(process.argv.slice(2))\r\n  opts = autocannon.parseArguments(args)\r\n}\r\n\r\nconst resultStr = autocannon.printResult(exampleResult, opts)\r\nprocess.stderr.write(resultStr)\r\n"
  },
  {
    "path": "test/printResult-renderStatusCodes.test.js",
    "content": "'use strict'\r\n\r\nconst test = require('tap').test\r\nconst split = require('split2')\r\nconst path = require('path')\r\nconst childProcess = require('child_process')\r\n\r\ntest('should stdout (print) the result', (t) => {\r\n  const lines = [\r\n    /.*/,\r\n    /$/,\r\n    /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\r\n    /.*/,\r\n    /Latency.*$/,\r\n    /$/,\r\n    /.*/,\r\n    /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\r\n    /.*/,\r\n    /Req\\/Sec.*$/,\r\n    /.*/,\r\n    /Bytes\\/Sec.*$/,\r\n    /.*/,\r\n    /.*/,\r\n    /Code.*Count.*$/,\r\n    /.*/,\r\n    /200.*500.*$/,\r\n    /.*/,\r\n    /302.*0.*$/,\r\n    /.*/,\r\n    /401.*0.*$/,\r\n    /.*/,\r\n    /403.*0.*$/,\r\n    /.*/,\r\n    /$/,\r\n    /Req\\/Bytes counts sampled once per second.*$/,\r\n    /# of samples: 10.*$/,\r\n    /$/,\r\n    /.* requests in ([0-9]|\\.)+s, .* read/\r\n  ]\r\n\r\n  t.plan(lines.length * 2)\r\n\r\n  const child = childProcess.spawn(process.execPath, [path.join(__dirname, 'printResult-process.js'), '--renderStatusCodes', 'http://127.0.0.1'], {\r\n    cwd: __dirname,\r\n    env: process.env,\r\n    stdio: ['ignore', 'pipe', 'pipe'],\r\n    detached: false\r\n  })\r\n\r\n  t.teardown(() => {\r\n    child.kill()\r\n  })\r\n\r\n  child\r\n    .stderr\r\n    .pipe(split())\r\n    .on('data', (line) => {\r\n      const regexp = lines.shift()\r\n      t.ok(regexp, 'we are expecting this line')\r\n      t.ok(regexp.test(line), 'line matches ' + regexp)\r\n    })\r\n    .on('end', t.end)\r\n})\r\n"
  },
  {
    "path": "test/printResult.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst split = require('split2')\nconst path = require('path')\nconst childProcess = require('child_process')\nconst { Writable } = require('stream')\nconst ansiRegex = require('ansi-regex')\nconst printResult = require('../lib/printResult')\n\ntest('should stdout (print) the result', (t) => {\n  const lines = [\n    /.*/,\n    /$/,\n    /Stat.*2\\.5%.*50%.*97\\.5%.*99%.*Avg.*Stdev.*Max.*$/,\n    /.*/,\n    /Latency.*$/,\n    /$/,\n    /.*/,\n    /Stat.*1%.*2\\.5%.*50%.*97\\.5%.*Avg.*Stdev.*Min.*$/,\n    /.*/,\n    /Req\\/Sec.*$/,\n    /.*/,\n    /Bytes\\/Sec.*$/,\n    /.*/,\n    /$/,\n    /Req\\/Bytes counts sampled once per second.*$/,\n    /# of samples: 10*$/,\n    /$/,\n    /.* requests in ([0-9]|\\.)+s, .* read/\n  ]\n\n  t.plan(lines.length * 2)\n\n  const child = childProcess.spawn(process.execPath, [path.join(__dirname, 'printResult-process.js')], {\n    cwd: __dirname,\n    env: process.env,\n    stdio: ['ignore', 'pipe', 'pipe'],\n    detached: false\n  })\n\n  t.teardown(() => {\n    child.kill()\n  })\n\n  child\n    .stderr\n    .pipe(split())\n    .on('data', (line) => {\n      const regexp = lines.shift()\n      t.ok(regexp, 'we are expecting this line')\n      t.ok(regexp.test(line), 'line matches ' + regexp)\n    })\n    .on('end', t.end)\n})\n\ntest('verify amount of total requests', (t) => {\n  t.plan(1)\n\n  // arrange\n  const connections = 10\n  const pipelining = 2\n  const result = {\n    connections,\n    pipelining,\n    latency: {},\n    requests: {\n      sent: connections * pipelining\n    },\n    throughput: {\n      average: 3319,\n      mean: 3319,\n      stddev: 0,\n      min: 3318,\n      max: 3318,\n      total: 3318,\n      p0_001: 3319,\n      p0_01: 3319,\n      p0_1: 3319,\n      p1: 3319,\n      p2_5: 3319,\n      p10: 3319,\n      p25: 3319,\n      p50: 3319,\n      p75: 3319,\n      p90: 3319,\n      p97_5: 3319,\n      p99: 3319,\n      p99_9: 3319,\n      p99_99: 3319,\n      p99_999: 3319\n    }\n  }\n\n  // act\n  const output = printResult(result, { })\n\n  // assert\n  const expectedRequests = connections * pipelining\n  t.match(output.includes(`${expectedRequests} requests in`), true)\n})\n\ntest('should not print when verbose(V=0) is false', (t) => {\n  t.plan(1)\n\n  const connections = 10\n  const pipelining = 2\n  const result = {\n    connections,\n    pipelining,\n    latency: {},\n    requests: {\n      sent: connections * pipelining\n    },\n    throughput: {\n      average: 3319,\n      mean: 3319,\n      stddev: 0,\n      min: 3318,\n      max: 3318,\n      total: 3318,\n      p0_001: 3319,\n      p0_01: 3319,\n      p0_1: 3319,\n      p1: 3319,\n      p2_5: 3319,\n      p10: 3319,\n      p25: 3319,\n      p50: 3319,\n      p75: 3319,\n      p90: 3319,\n      p97_5: 3319,\n      p99: 3319,\n      p99_9: 3319,\n      p99_99: 3319,\n      p99_999: 3319\n    }\n  }\n\n  // act\n  const output = printResult(result, { verbose: false })\n  t.ok(output.split('\\n').length === 2)\n})\n\ntest('should print with color when color is supported', (t) => {\n  t.plan(1)\n\n  const connections = 10\n  const pipelining = 2\n  const result = {\n    connections,\n    pipelining,\n    latency: {},\n    requests: {\n      sent: connections * pipelining\n    },\n    throughput: {\n      average: 3319,\n      mean: 3319,\n      stddev: 0,\n      min: 3318,\n      max: 3318,\n      total: 3318,\n      p0_001: 3319,\n      p0_01: 3319,\n      p0_1: 3319,\n      p1: 3319,\n      p2_5: 3319,\n      p10: 3319,\n      p25: 3319,\n      p50: 3319,\n      p75: 3319,\n      p90: 3319,\n      p97_5: 3319,\n      p99: 3319,\n      p99_9: 3319,\n      p99_99: 3319,\n      p99_999: 3319\n    }\n  }\n\n  const { FORCE_COLOR, NO_COLOR, COLOR, CI, COLORTERM } = process.env\n  delete process.env.FORCE_COLOR\n  delete process.env.NO_COLOR\n  delete process.env.COLOR\n  delete process.env.CI\n  process.env.COLORTERM = 'truecolor'\n  const outputStream = new Writable({\n    write () {}\n  })\n  outputStream.isTTY = true\n\n  // act\n  const output = printResult(result, { outputStream })\n  t.ok(ansiRegex().test(output))\n\n  // cleanup\n  process.env.FORCE_COLOR = FORCE_COLOR\n  process.env.NO_COLOR = NO_COLOR\n  process.env.COLOR = COLOR\n  process.env.CI = CI\n  process.env.COLORTERM = COLORTERM\n})\n\ntest('should not print with any color when color is not supported', (t) => {\n  t.plan(1)\n\n  const connections = 10\n  const pipelining = 2\n  const result = {\n    connections,\n    pipelining,\n    latency: {},\n    requests: {\n      sent: connections * pipelining\n    },\n    throughput: {\n      average: 3319,\n      mean: 3319,\n      stddev: 0,\n      min: 3318,\n      max: 3318,\n      total: 3318,\n      p0_001: 3319,\n      p0_01: 3319,\n      p0_1: 3319,\n      p1: 3319,\n      p2_5: 3319,\n      p10: 3319,\n      p25: 3319,\n      p50: 3319,\n      p75: 3319,\n      p90: 3319,\n      p97_5: 3319,\n      p99: 3319,\n      p99_9: 3319,\n      p99_99: 3319,\n      p99_999: 3319\n    }\n  }\n\n  const { FORCE_COLOR, NO_COLOR, COLOR, CI, COLORTERM } = process.env\n  delete process.env.FORCE_COLOR\n  delete process.env.NO_COLOR\n  delete process.env.COLOR\n  delete process.env.CI\n  process.env.COLORTERM = 'truecolor'\n  const outputStream = new Writable({\n    write () {}\n  })\n\n  // act\n  const output = printResult(result, { outputStream })\n  t.ok(!ansiRegex().test(output))\n\n  // cleanup\n  process.env.FORCE_COLOR = FORCE_COLOR\n  process.env.NO_COLOR = NO_COLOR\n  process.env.COLOR = COLOR\n  process.env.CI = CI\n  process.env.COLORTERM = COLORTERM\n})\n"
  },
  {
    "path": "test/progressTracker.test.js",
    "content": "'use strict'\n\nconst helper = require('./helper')\nconst test = require('tap').test\nconst { defaultMaxListeners } = require('events')\nconst { Writable } = require('stream')\nconst sinon = require('sinon')\nconst autocannon = require('../autocannon')\nconst { hasWorkerSupport } = require('../lib/util')\n\ntest(`should not emit warnings when using >= ${defaultMaxListeners} workers`, { skip: !hasWorkerSupport }, t => {\n  const server = helper.startServer()\n\n  const instance = autocannon({\n    url: `http://localhost:${server.address().port}`,\n    workers: defaultMaxListeners,\n    duration: 1\n  })\n\n  setTimeout(() => {\n    instance.stop()\n    t.notOk(emitWarningSpy.called)\n    emitWarningSpy.restore()\n    t.end()\n  }, 2000)\n\n  const emitWarningSpy = sinon.spy(process, 'emitWarning')\n\n  autocannon.track(instance, {\n    outputStream: new Writable({\n      write () {}\n    })\n  })\n})\n"
  },
  {
    "path": "test/progressTracker.test.stub.js",
    "content": "'use strict'\n\nconst helper = require('./helper')\nconst test = require('tap').test\nconst progressTracker = require('../lib/progressTracker')\nconst autocannon = require('../autocannon')\n\ntest('progress tracker should throw if no instance is provided', t => {\n  t.plan(1)\n  try {\n    progressTracker(null, {})\n  } catch (error) {\n    t.same(error.message, 'instance required for tracking')\n  }\n})\n\ntest('should work', t => {\n  const server = helper.startServer({ statusCode: 404 })\n  const instance = autocannon({\n    url: `http://localhost:${server.address().port}`,\n    pipelining: 2\n  }, console.log)\n\n  setTimeout(() => {\n    instance.stop()\n    t.end()\n  }, 2000)\n\n  autocannon.track(instance, {\n    renderProgressBar: true,\n    renderLatencyTable: true\n  })\n})\n\ntest('should work with amount', t => {\n  const server = helper.startServer()\n  const instance = autocannon({\n    url: `http://localhost:${server.address().port}`,\n    pipelining: 1,\n    amount: 10\n  }, process.stdout)\n\n  setTimeout(() => {\n    instance.stop()\n    t.end()\n  }, 2000)\n  autocannon.track(instance, {\n    renderProgressBar: true\n  })\n  t.pass()\n})\n\ntest('should log mismatches', t => {\n  const server = helper.startServer()\n  const instance = autocannon({\n    url: `http://localhost:${server.address().port}`,\n    pipelining: 1,\n    amount: 10,\n    expectBody: 'modified'\n  }, console.log)\n\n  setTimeout(() => {\n    instance.stop()\n    t.end()\n  }, 2000)\n  autocannon.track(instance, {\n    renderProgressBar: true\n  })\n  t.pass()\n})\n\ntest('should log resets', t => {\n  const server = helper.startServer()\n  const instance = autocannon({\n    url: `http://localhost:${server.address().port}`,\n    connections: 1,\n    amount: 10,\n    requests: [\n      { method: 'GET' },\n      {\n        method: 'GET',\n        // falsey result will reset\n        setupRequest: () => {}\n      },\n      { method: 'GET' }\n    ]\n  }, console.log)\n\n  setTimeout(() => {\n    instance.stop()\n    t.end()\n  }, 2000)\n  autocannon.track(instance, {\n    renderProgressBar: true\n  })\n  t.pass()\n})\n"
  },
  {
    "path": "test/requestIterator.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst helper = require('./helper')\nconst server = helper.startServer()\nconst RequestIterator = require('../lib/requestIterator')\n\ntest('request iterator should create requests with sensible defaults', (t) => {\n  t.plan(3)\n\n  const opts = server.address()\n\n  let iterator = new RequestIterator(opts)\n\n  t.same(iterator.currentRequest.requestBuffer,\n    Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\n\\r\\n`),\n    'request is okay')\n\n  opts.requests = [{}]\n\n  iterator = new RequestIterator(opts)\n\n  t.same(iterator.currentRequest.requestBuffer,\n    Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\n\\r\\n`),\n    'request is okay')\n\n  opts.requests = []\n\n  iterator = new RequestIterator(opts)\n\n  t.notOk(iterator.currentRequest, 'request doesn\\'t exist')\n})\n\ntest('request iterator should create requests with overwritten defaults', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  const iterator = new RequestIterator(opts)\n\n  t.same(iterator.currentRequest.requestBuffer,\n    Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\n\\r\\n`),\n    'request is okay')\n})\n\ntest('request iterator should use headers from requests', (t) => {\n  t.plan(2)\n\n  const opts = server.address()\n  opts.requests = [\n    {\n      path: '/',\n      method: 'POST',\n      headers: {\n        'content-type': 'application/json'\n      },\n      body: JSON.stringify({ foo: 'bar' })\n    },\n    {\n      path: '/2',\n      method: 'POST',\n      headers: {\n        'content-type': 'text/html'\n      },\n      body: JSON.stringify({ foo: 'bar' })\n    }\n  ]\n  const iterator = new RequestIterator(opts)\n\n  t.same(iterator.currentRequest.headers['content-type'], 'application/json')\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.headers['content-type'], 'text/html')\n})\n\ntest('request iterator should create requests with overwritten defaults', (t) => {\n  t.plan(3)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  const requests = [\n    {\n      body: 'hello world'\n    },\n    {\n      method: 'GET',\n      body: 'modified'\n    }\n  ]\n\n  const request1Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 11\\r\\n\\r\\nhello world`)\n  const request2Res = Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 8\\r\\n\\r\\nmodified`)\n\n  opts.requests = requests\n\n  const iterator = new RequestIterator(opts)\n\n  t.same(iterator.currentRequest.requestBuffer, request1Res, 'request was okay')\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.requestBuffer, request2Res, 'request was okay')\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.requestBuffer, request1Res, 'request was okay')\n})\n\ntest('request iterator should allow for overwriting the requests passed in, but still use overwritten defaults', (t) => {\n  t.plan(5)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  const requests1 = [\n    {\n      body: 'hello world'\n    },\n    {\n      method: 'GET',\n      body: 'modified'\n    }\n  ]\n\n  const requests2 = [\n    {\n      body: 'hell0 w0rld'\n    }\n  ]\n\n  const request1Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 11\\r\\n\\r\\nhello world`)\n  const request2Res = Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 8\\r\\n\\r\\nmodified`)\n  const request3Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 11\\r\\n\\r\\nhell0 w0rld`)\n\n  opts.requests = requests1\n\n  const iterator = new RequestIterator(opts)\n\n  t.same(iterator.currentRequest.requestBuffer, request1Res, 'request was okay')\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.requestBuffer, request2Res, 'request was okay')\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.requestBuffer, request1Res, 'request was okay')\n\n  iterator.setRequests(requests2)\n  t.same(iterator.currentRequest.requestBuffer, request3Res, 'request was okay')\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.requestBuffer, request3Res, 'request was okay')\n})\n\ntest('request iterator should allow for rebuilding the current request', (t) => {\n  t.plan(6)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  const requests1 = [\n    {\n      body: 'hello world'\n    },\n    {\n      method: 'GET',\n      body: 'modified'\n    }\n  ]\n\n  const request1Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 11\\r\\n\\r\\nhello world`)\n  const request2Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 8\\r\\n\\r\\nmodified`)\n  const request3Res = Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 8\\r\\n\\r\\nmodified`)\n  const request4Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nheader: modifiedHeader\\r\\nContent-Length: 8\\r\\n\\r\\nmodified`)\n  const request5Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nheader: modifiedHeader\\r\\n\\r\\n`)\n\n  opts.requests = requests1\n\n  const iterator = new RequestIterator(opts)\n  t.same(iterator.currentRequest.requestBuffer, request1Res, 'request was okay')\n  iterator.setBody('modified')\n  t.same(iterator.currentRequest.requestBuffer, request2Res, 'request was okay')\n  iterator.nextRequest() // verify it didn't affect the other request\n  t.same(iterator.currentRequest.requestBuffer, request3Res, 'request was okay')\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.requestBuffer, request2Res, 'request was okay')\n  iterator.setHeaders({ header: 'modifiedHeader' })\n  t.same(iterator.currentRequest.requestBuffer, request4Res, 'request was okay')\n  iterator.setRequest() // this should build default request\n  t.same(iterator.currentRequest.requestBuffer, request5Res, 'request was okay')\n})\n\ntest('request iterator should not replace any [<id>] tags with generated IDs when calling move with idReplacement disabled', (t) => {\n  t.plan(3)\n\n  const opts = server.address()\n  opts.path = '/[%3Cid%3E]'\n  opts.method = 'POST'\n  opts.body = '[<id>]'\n  opts.requests = [{}]\n\n  const iterator = new RequestIterator(opts)\n  const result = iterator.currentRequest.requestBuffer.toString().trim()\n\n  const path = result.split(' ')[1]\n  t.equal(path, '/[%3Cid%3E]', '[<id>] should be present in path')\n\n  const contentLength = result.split('Content-Length: ')[1].slice(0, 1)\n  t.equal(contentLength, '6', 'Content-Length was incorrect')\n\n  const body = result.split('Content-Length: 6')[1].trim()\n  t.equal(body, '[<id>]', '[<id>] should be present in body')\n})\n\ntest('request iterator should replace all [<id>] tags with generated IDs when calling move with idReplacement enabled', (t) => {\n  t.plan(10)\n\n  function isUrlSafe (string) {\n    return /^[A-Za-z0-9\\-_]+$/.test(string)\n  }\n\n  const opts = server.address()\n  opts.method = 'POST'\n  opts.path = '/[%3Cid%3E]'\n  opts.body = '[<id>] [%3Cid%3E]'\n  opts.requests = [{}]\n  opts.idReplacement = true\n\n  const iterator = new RequestIterator(opts)\n  const first = iterator.currentRequest.requestBuffer.toString().trim()\n  const firstPath = first.split(' ')[1]\n  const firstId = firstPath.slice(1)\n\n  t.equal(first.includes('[<id>]'), false, 'One or more [<id>] tags were not replaced in body')\n  t.ok(first.includes('[%3Cid%3E]'), 'Encoded [<id>] tags should not be replaced in body')\n  t.equal(firstPath.includes('[%3Cid%3E]'), false, 'One or more [<id>] tags were not replaced in path')\n  t.ok(firstId.endsWith('0'), 'Generated ID should end with request number')\n  t.ok(isUrlSafe(firstId), 'Generated ID should be URL-safe')\n\n  iterator.nextRequest()\n  const second = iterator.currentRequest.requestBuffer.toString().trim()\n  const secondPath = second.split(' ')[1]\n  const secondId = secondPath.slice(1)\n\n  t.equal(second.includes('[<id>]'), false, 'One or more [<id>] tags were not replaced in body')\n  t.ok(second.includes('[%3Cid%3E]'), 'Encoded [<id>] tags should not be replaced in body')\n  t.equal(secondPath.includes('[%3Cid%3E]'), false, 'One or more [<id>] tags were not replaced in path')\n  t.ok(secondId.endsWith('1'), 'Generated ID should end with a unique request number')\n  t.ok(isUrlSafe(secondId), 'Generated ID should be URL-safe')\n})\n\ntest('request iterator should invoke onResponse callback when set', (t) => {\n  t.plan(9)\n\n  const opts = server.address()\n  opts.requests = [\n    {\n      onResponse: (status, body, context) => {\n        t.same(status, 200)\n        t.same(body, 'ok')\n        t.same(context, {})\n      }\n    },\n    {},\n    {\n      onResponse: (status, body, context) => {\n        t.same(status, 201)\n        t.same(body, '')\n        t.same(context, {})\n      }\n    }\n  ]\n\n  const iterator = new RequestIterator(opts)\n  iterator.recordBody(iterator.currentRequest, 200, 'ok')\n  iterator.nextRequest()\n  iterator.recordBody(iterator.currentRequest, 500, 'ignored')\n  iterator.nextRequest()\n  iterator.recordBody(iterator.currentRequest, 201, '')\n  // will reset the iterator\n  iterator.nextRequest()\n  iterator.recordBody(iterator.currentRequest, 200, 'ok')\n})\n\ntest('request iterator should properly mutate requests if a setupRequest function is located', (t) => {\n  t.plan(6)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  let i = 0\n\n  opts.requests = [\n    {\n      body: 'hello world',\n      setupRequest: req => {\n        req.body += i++\n        return req\n      }\n    },\n    {\n      method: 'POST',\n      body: 'modified',\n      setupRequest: req => {\n        req.method = 'GET'\n        return req\n      }\n    }\n  ]\n\n  const request1Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 12\\r\\n\\r\\nhello world0`)\n  const request2Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 9\\r\\n\\r\\nmodified1`)\n  const request3Res = Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 8\\r\\n\\r\\nmodified`)\n  const request4Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 9\\r\\n\\r\\nmodified2`)\n  const request5Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nheader: modifiedHeader\\r\\nContent-Length: 9\\r\\n\\r\\nmodified3`)\n  const request6Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nheader: modifiedHeader\\r\\n\\r\\n`)\n\n  const iterator = new RequestIterator(opts)\n  t.same(iterator.currentRequest.requestBuffer, request1Res, 'request was okay')\n  iterator.setBody('modified')\n  t.same(iterator.currentRequest.requestBuffer, request2Res, 'request was okay')\n  iterator.nextRequest() // verify it didn't affect the other request\n  t.same(iterator.currentRequest.requestBuffer, request3Res, 'request was okay')\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.requestBuffer, request4Res, 'request was okay')\n  iterator.setHeaders({ header: 'modifiedHeader' })\n  t.same(iterator.currentRequest.requestBuffer, request5Res, 'request was okay')\n  iterator.setRequest() // this should build default request\n  t.same(iterator.currentRequest.requestBuffer, request6Res, 'request was okay')\n})\n\ntest('request iterator should reset when setupRequest returns nothing', (t) => {\n  t.plan(12)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  let i = 0\n\n  opts.requests = [\n    { method: 'GET' },\n    {\n      body: 'hello world',\n      setupRequest: req => ++i >= 2 ? null : req\n    },\n    { method: 'PUT' }\n  ]\n\n  const requestGET = `GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\n\\r\\n`\n  const requestPUT = `PUT / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\n\\r\\n`\n  const requestPOST = `POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 11\\r\\n\\r\\nhello world`\n\n  const iterator = new RequestIterator(opts)\n  t.equal(iterator.resetted, false)\n  // first GET, i is 0\n  t.same(iterator.currentRequest.requestBuffer.toString(), requestGET, 'request 1 was okay')\n  iterator.nextRequest()\n  // first POST, i becomes 1\n  t.equal(iterator.resetted, false)\n  t.same(iterator.currentRequest.requestBuffer.toString(), requestPOST, 'request 2 was okay')\n  iterator.nextRequest()\n  // first PUT, i is 1\n  t.equal(iterator.resetted, false)\n  t.same(iterator.currentRequest.requestBuffer.toString(), requestPUT, 'request 3 was okay')\n  iterator.nextRequest()\n  // second GET, i is 1\n  t.equal(iterator.resetted, false)\n  t.same(iterator.currentRequest.requestBuffer.toString(), requestGET, 'request 4 was okay')\n  iterator.nextRequest()\n  // second POST, i becomes 2, pipeline is reset\n  t.equal(iterator.resetted, true)\n  t.same(iterator.currentRequest.requestBuffer.toString(), requestGET, 'request 5 was okay')\n  iterator.nextRequest()\n  // third POST, i becomes 3, pipeline is reset\n  t.equal(iterator.resetted, true)\n  t.same(iterator.currentRequest.requestBuffer.toString(), requestGET, 'request 6 was okay')\n})\n\ntest('request iterator should throw when first setupRequest returns nothing', (t) => {\n  t.plan(4)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  let i = 0\n  opts.requests = [{ body: 'hello world', setupRequest: req => ++i > 1 ? null : req }]\n  const requestPOST = `POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 11\\r\\n\\r\\nhello world`\n\n  const iterator = new RequestIterator(opts)\n  t.equal(iterator.resetted, false)\n  // first POST, i is 0\n  t.same(iterator.currentRequest.requestBuffer.toString(), requestPOST, 'request 1 was okay')\n  t.throws(() => iterator.nextRequest(), 'First setupRequest() failed did not returned valid request. Stopping')\n  t.equal(iterator.resetted, false)\n})\n\ntest('request iterator should maintain context while looping on requests', (t) => {\n  t.plan(4)\n\n  const opts = server.address()\n  opts.requests = [\n    {\n      setupRequest: (req, context) => {\n        t.same(context, {}, 'context was supposed to be empty for first request')\n        context.num = 1\n        context.init = true\n        return req\n      }\n    },\n    {\n      setupRequest: (req, context) => {\n        t.same(context, { num: 1, init: true }, 'context was supposed to be initialized for second request')\n        context.num++\n        return req\n      }\n    },\n    {\n      setupRequest: (req, context) => {\n        t.same(context, { num: 2, init: true }, 'context was supposed to be initialized for third request')\n        context.num++\n        return req\n      }\n    }\n  ]\n\n  const iterator = new RequestIterator(opts)\n  iterator.nextRequest()\n  iterator.nextRequest()\n  // will reset the iterator\n  iterator.nextRequest()\n})\n\ntest('request iterator should return instance of RequestIterator', t => {\n  t.plan(1)\n  const caller = {}\n  const opts = server.address()\n\n  const iterator = RequestIterator.call(caller, opts)\n  t.type(iterator, RequestIterator)\n})\n\ntest('request iterator should return next request buffer', (t) => {\n  t.plan(1)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  const requests = [\n    {\n      body: 'hello world'\n    },\n    {\n      method: 'GET',\n      body: 'modified'\n    }\n  ]\n\n  const request2Res = Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nContent-Length: 8\\r\\n\\r\\nmodified`)\n\n  opts.requests = requests\n\n  const iterator = new RequestIterator(opts)\n  const buffer = iterator.nextRequestBuffer()\n  t.same(request2Res, buffer, 'request is okay')\n})\n\ntest('request iterator should initialize context from options', (t) => {\n  t.plan(3)\n\n  const opts = server.address()\n  opts.initialContext = { foo: 'bar' }\n  opts.requests = [\n    {\n      setupRequest: (req, context) => {\n        t.same(context, { foo: 'bar' }, 'context should be initialized from opts')\n        context.baz = 'qux'\n        return req\n      }\n    },\n    {\n      setupRequest: (req, context) => {\n        t.same(context, { foo: 'bar', baz: 'qux' }, 'context should contain updated data')\n        return req\n      }\n    }\n  ]\n\n  const iterator = new RequestIterator(opts)\n  iterator.nextRequest()\n  // will reset and reinit context\n  iterator.nextRequest()\n})\n\ntest('request iterator should use the same headers when set', (t) => {\n  t.plan(6)\n\n  const opts = server.address()\n  opts.method = 'POST'\n\n  let i = 0\n\n  opts.requests = [\n    {\n      body: 'hello world',\n      setupRequest: req => {\n        req.body += i++\n        return req\n      }\n    },\n    {\n      method: 'POST',\n      body: 'modified',\n      setupRequest: req => {\n        req.method = 'GET'\n        return req\n      }\n    }\n  ]\n\n  const request1Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nAccess-Control-Allow-Credentials: true\\r\\nContent-Length: 12\\r\\n\\r\\nhello world1`)\n  const request2Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nAccess-Control-Allow-Credentials: true\\r\\nContent-Length: 12\\r\\n\\r\\nhello world1`)\n  const request3Res = Buffer.from(`GET / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nAccess-Control-Allow-Credentials: true\\r\\nContent-Length: 8\\r\\n\\r\\nmodified`)\n  const request4Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nAccess-Control-Allow-Credentials: true\\r\\nContent-Length: 12\\r\\n\\r\\nhello world2`)\n  const request5Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nAccess-Control-Allow-Credentials: true\\r\\nContent-Length: 12\\r\\n\\r\\nhello world2`)\n  const request6Res = Buffer.from(`POST / HTTP/1.1\\r\\nHost: localhost:${server.address().port}\\r\\nConnection: keep-alive\\r\\nAccess-Control-Allow-Credentials: true\\r\\n\\r\\n`)\n\n  const iterator = new RequestIterator(opts)\n  iterator.setHeaders({ 'Access-Control-Allow-Credentials': 'true' })\n  t.same(iterator.currentRequest.requestBuffer, request1Res, iterator.currentRequest.requestBuffer.toString())\n  t.same(iterator.currentRequest.requestBuffer, request2Res, iterator.currentRequest.requestBuffer.toString())\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.requestBuffer, request3Res, iterator.currentRequest.requestBuffer.toString())\n  iterator.nextRequest()\n  t.same(iterator.currentRequest.requestBuffer, request4Res, iterator.currentRequest.requestBuffer.toString())\n  t.same(iterator.currentRequest.requestBuffer, request5Res, iterator.currentRequest.requestBuffer.toString())\n  iterator.setRequest()\n  t.same(iterator.currentRequest.requestBuffer, request6Res, iterator.currentRequest.requestBuffer.toString())\n})\n\ntest('request iterator should invoke onResponse callback with headers in it', (t) => {\n  t.plan(4)\n\n  const opts = server.address()\n  opts.requests = [\n    {\n      onResponse: (status, body, context, headers) => {\n        t.same(status, 200)\n        t.same(body, 'ok')\n        t.same(context, {})\n        t.same(headers, { 'set-cookie': 123 })\n      }\n    }\n  ]\n\n  const iterator = new RequestIterator(opts)\n  iterator.recordBody(iterator.currentRequest, 200, 'ok', ['set-cookie', 123])\n  iterator.nextRequest()\n\n  t.end()\n})\n"
  },
  {
    "path": "test/run.test.js",
    "content": "'use strict'\n\nconst os = require('os')\nconst path = require('path')\nconst test = require('tap').test\nconst initJob = require('../lib/init')\nconst defaultOptions = require('../lib/defaultOptions')\nconst helper = require('./helper')\nconst server = helper.startServer()\n\ntest('init', (t) => {\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    duration: 2,\n    title: 'title321'\n  }, function (err, result) {\n    t.error(err)\n\n    t.ok(result.duration >= 2, 'duration is at least 2s')\n    t.equal(result.title, 'title321', 'title should be what was passed in')\n    t.equal(result.connections, 2, 'connections is the same')\n    t.equal(result.pipelining, 1, 'pipelining is the default')\n\n    t.ok(result.latency, 'latency exists')\n    t.type(result.latency.average, 'number', 'latency.average exists')\n    t.type(result.latency.stddev, 'number', 'latency.stddev exists')\n    t.ok(result.latency.min >= 0, 'latency.min exists')\n    t.type(result.latency.max, 'number', 'latency.max exists')\n    t.type(result.latency.p2_5, 'number', 'latency.p2_5 (2.5%) exists')\n    t.type(result.latency.p50, 'number', 'latency.p50 (50%) exists')\n    t.type(result.latency.p97_5, 'number', 'latency.p97_5 (97.5%) exists')\n    t.type(result.latency.p99, 'number', 'latency.p99 (99%) exists')\n\n    t.ok(result.requests, 'requests exists')\n    t.type(result.requests.average, 'number', 'requests.average exists')\n    t.type(result.requests.stddev, 'number', 'requests.stddev exists')\n    t.type(result.requests.min, 'number', 'requests.min exists')\n    t.type(result.requests.max, 'number', 'requests.max exists')\n    t.ok(result.requests.total >= result.requests.average * 2 / 100 * 95, 'requests.total exists')\n    t.type(result.requests.sent, 'number', 'sent exists')\n    t.ok(result.requests.sent >= result.requests.total, 'total requests made should be more than or equal to completed requests total')\n    t.type(result.requests.p1, 'number', 'requests.p1 (1%) exists')\n    t.type(result.requests.p2_5, 'number', 'requests.p2_5 (2.5%) exists')\n    t.type(result.requests.p50, 'number', 'requests.p50 (50%) exists')\n    t.type(result.requests.p97_5, 'number', 'requests.p97_5 (97.5%) exists')\n\n    t.ok(result.throughput, 'throughput exists')\n    t.type(result.throughput.average, 'number', 'throughput.average exists')\n    t.type(result.throughput.stddev, 'number', 'throughput.stddev exists')\n    t.type(result.throughput.min, 'number', 'throughput.min exists')\n    t.type(result.throughput.max, 'number', 'throughput.max exists')\n    t.ok(result.throughput.total >= result.throughput.average * 2 / 100 * 95, 'throughput.total exists')\n    t.type(result.throughput.p1, 'number', 'throughput.p1 (1%) exists')\n    t.type(result.throughput.p2_5, 'number', 'throughput.p2_5 (2.5%) exists')\n    t.type(result.throughput.p50, 'number', 'throughput.p50 (50%) exists')\n    t.type(result.throughput.p97_5, 'number', 'throughput.p97_5 (97.5%) exists')\n\n    t.ok(result.start, 'start time exists')\n    t.ok(result.finish, 'finish time exists')\n\n    t.equal(result.errors, 0, 'no errors')\n    t.equal(result.mismatches, 0, 'no mismatches')\n    t.equal(result.resets, 0, 'no resets')\n\n    t.equal(result['1xx'], 0, '1xx codes')\n    t.equal(result['2xx'], result.requests.total, '2xx codes')\n    t.equal(result['3xx'], 0, '3xx codes')\n    t.equal(result['4xx'], 0, '4xx codes')\n    t.equal(result['5xx'], 0, '5xx codes')\n    t.equal(result.non2xx, 0, 'non 2xx codes')\n\n    t.end()\n  })\n})\n\ntest('tracker.stop()', (t) => {\n  const tracker = initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    duration: 2\n  }, function (err, result) {\n    t.error(err)\n\n    t.ok(result.duration < 5, 'duration is lower because of stop')\n    t.notOk(result.title, 'title should not exist when not passed in')\n    t.equal(result.connections, 2, 'connections is the same')\n    t.equal(result.pipelining, 1, 'pipelining is the default')\n\n    t.ok(result.latency, 'latency exists')\n    t.type(result.latency.average, 'number', 'latency.average exists')\n    t.type(result.latency.stddev, 'number', 'latency.stddev exists')\n    t.ok(result.latency.min >= 0, 'latency.min exists')\n    t.type(result.latency.max, 'number', 'latency.max exists')\n    t.type(result.latency.p2_5, 'number', 'latency.p2_5 (2.5%) exists')\n    t.type(result.latency.p50, 'number', 'latency.p50 (50%) exists')\n    t.type(result.latency.p97_5, 'number', 'latency.p97_5 (97.5%) exists')\n    t.type(result.latency.p99, 'number', 'latency.p99 (99%) exists')\n\n    t.ok(result.requests, 'requests exists')\n    t.type(result.requests.average, 'number', 'requests.average exists')\n    t.type(result.requests.stddev, 'number', 'requests.stddev exists')\n    t.type(result.requests.min, 'number', 'requests.min exists')\n    t.type(result.requests.max, 'number', 'requests.max exists')\n    t.ok(result.requests.total >= result.requests.average * 2 / 100 * 95, 'requests.total exists')\n    t.type(result.requests.sent, 'number', 'sent exists')\n    t.ok(result.requests.sent >= result.requests.total, 'total requests made should be more than or equal to completed requests total')\n    t.type(result.requests.p1, 'number', 'requests.p1 (1%) exists')\n    t.type(result.requests.p2_5, 'number', 'requests.p2_5 (2.5%) exists')\n    t.type(result.requests.p50, 'number', 'requests.p50 (50%) exists')\n    t.type(result.requests.p97_5, 'number', 'requests.p97_5 (97.5%) exists')\n\n    t.ok(result.throughput, 'throughput exists')\n    t.type(result.throughput.average, 'number', 'throughput.average exists')\n    t.type(result.throughput.stddev, 'number', 'throughput.stddev exists')\n    t.type(result.throughput.min, 'number', 'throughput.min exists')\n    t.type(result.throughput.max, 'number', 'throughput.max exists')\n    t.ok(result.throughput.total >= result.throughput.average * 2 / 100 * 95, 'throughput.total exists')\n    t.type(result.throughput.p1, 'number', 'throughput.p1 (1%) exists')\n    t.type(result.throughput.p2_5, 'number', 'throughput.p2_5 (2.5%) exists')\n    t.type(result.throughput.p50, 'number', 'throughput.p50 (50%) exists')\n    t.type(result.throughput.p97_5, 'number', 'throughput.p97_5 (97.5%) exists')\n\n    t.ok(result.start, 'start time exists')\n    t.ok(result.finish, 'finish time exists')\n\n    t.equal(result.errors, 0, 'no errors')\n    t.equal(result.mismatches, 0, 'no mismatches')\n\n    t.equal(result['1xx'], 0, '1xx codes')\n    t.equal(result['2xx'], result.requests.total, '2xx codes')\n    t.equal(result['3xx'], 0, '3xx codes')\n    t.equal(result['4xx'], 0, '4xx codes')\n    t.equal(result['5xx'], 0, '5xx codes')\n    t.equal(result.non2xx, 0, 'non 2xx codes')\n\n    t.end()\n  })\n\n  t.ok(tracker.opts, 'opts exist on tracker')\n\n  setTimeout(() => {\n    tracker.stop()\n  }, 1000)\n})\n\ntest('requests.min should be 0 when there are no successful requests', (t) => {\n  initJob({\n    url: 'nonexistent',\n    connections: 1,\n    amount: 1\n  }, function (err, result) {\n    t.error(err)\n    t.equal(result.requests.min, 0, 'requests.min should be 0')\n    t.end()\n  })\n})\n\ntest('run should callback with an error with an invalid connections factor', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: -1\n  }, function (err, result) {\n    t.ok(err, 'invalid connections should cause an error')\n    t.notOk(result, 'results should not exist')\n    t.end()\n  })\n})\n\ntest('run should callback with an error with an invalid pipelining factor', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    pipelining: -1\n  }, function (err, result) {\n    t.ok(err, 'invalid pipelining should cause an error')\n    t.notOk(result, 'results should not exist')\n    t.end()\n  })\n})\n\ntest('run should callback with an error with an invalid bailout', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    bailout: -1\n  }, function (err, result) {\n    t.ok(err, 'invalid bailout should cause an error')\n    t.notOk(result, 'results should not exist')\n    t.end()\n  })\n})\n\ntest('run should callback with an error with an invalid duration', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    duration: -1\n  }, function (err, result) {\n    t.ok(err, 'invalid duration should cause an error')\n    t.notOk(result, 'results should not exist')\n    t.end()\n  })\n})\n\ntest('run should callback with an error when no url is passed in', (t) => {\n  t.plan(2)\n\n  initJob({}, function (err, result) {\n    t.ok(err, 'no url should cause an error')\n    t.notOk(result, 'results should not exist')\n    t.end()\n  })\n})\n\ntest('run should callback with an error after a bailout', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: 'http://localhost:4', // 4 = first unassigned port: https://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers\n    bailout: 1\n  }, function (err, result) {\n    t.error(err)\n    t.ok(result, 'results should not exist')\n    t.end()\n  })\n})\n\ntest('run should callback with an error using expectBody and requests', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    requests: [{ body: 'something' }],\n    expectBody: 'hello'\n  }, function (err, result) {\n    t.ok(err, 'expectBody used with requests should cause an error')\n    t.notOk(result, 'results should not exist')\n    t.end()\n  })\n})\n\ntest('run should handle context correctly', (t) => {\n  t.plan(1)\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 1,\n    amount: 1,\n    initialContext: { init: 'context' },\n    requests: [{\n      setupRequest: (req, context) => {\n        t.same(context, { init: 'context' }, 'context should be initialized from opts')\n        return req\n      }\n    }]\n  })\n})\n\ntest('run should allow users to enter timestrings to be used for duration', (t) => {\n  t.plan(3)\n\n  const instance = initJob({\n    url: 'http://localhost:' + server.address().port,\n    duration: '10m'\n  }, function (err, result) {\n    t.error(err)\n    t.ok(result, 'results should exist')\n    t.end()\n  })\n\n  t.equal(instance.opts.duration, 10 * 60, 'duration should have been parsed to be 600 seconds (10m)')\n\n  setTimeout(() => {\n    instance.stop()\n  }, 500)\n})\n\ntest('run should recognise valid urls without http at the start', (t) => {\n  t.plan(3)\n\n  initJob({\n    url: 'localhost:' + server.address().port,\n    duration: 1\n  }, (err, res) => {\n    t.error(err)\n    t.ok(res, 'results should exist')\n    t.equal(res.url, 'http://localhost:' + server.address().port, 'url should have http:// added to start')\n    t.end()\n  })\n})\n\ntest('run should produce count of mismatches with expectBody set', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    expectBody: 'body will not be this',\n    maxOverallRequests: 10,\n    timeout: 100\n  }, function (err, result) {\n    t.error(err)\n    t.equal(result.mismatches, 10)\n    t.end()\n  })\n})\n\ntest('run should produce 0 mismatches with expectBody set and matches', (t) => {\n  t.plan(2)\n\n  const responseBody = 'hello dave'\n  const server = helper.startServer({ body: responseBody })\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    expectBody: responseBody,\n    maxOverallRequests: 10\n  }, function (err, result) {\n    t.error(err)\n    t.equal(result.mismatches, 0)\n    t.end()\n  })\n})\n\ntest('run should produce count of mismatches with verifyBody set', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    verifyBody: function () {\n      return false\n    },\n    maxOverallRequests: 10,\n    timeout: 100\n  }, function (err, result) {\n    t.error(err)\n    t.equal(result.mismatches, 10)\n    t.end()\n  })\n})\n\ntest('run should produce 0 mismatches with verifyBody set and return true', (t) => {\n  t.plan(2)\n\n  const responseBody = 'hello dave'\n  const server = helper.startServer({ body: responseBody })\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    verifyBody: function (body) {\n      return body.indexOf('hello') > -1\n    },\n    maxOverallRequests: 10\n  }, function (err, result) {\n    t.error(err)\n    t.equal(result.mismatches, 0)\n    t.end()\n  })\n})\n\ntest('run should accept a unix socket/windows pipe', (t) => {\n  t.plan(11)\n\n  const socketPath = process.platform === 'win32'\n    ? path.join('\\\\\\\\?\\\\pipe', process.cwd(), 'autocannon-' + Date.now())\n    : path.join(os.tmpdir(), 'autocannon-' + Date.now() + '.sock')\n\n  helper.startServer({ socketPath })\n\n  initJob({\n    url: 'localhost',\n    socketPath,\n    connections: 2,\n    duration: 2\n  }, (err, result) => {\n    t.error(err)\n    t.ok(result, 'results should exist')\n    t.equal(result.socketPath, socketPath, 'socketPath should be included in result')\n    t.ok(result.requests.total > 0, 'should make at least one request')\n\n    if (process.platform === 'win32') {\n      // On Windows a few errors are expected. We'll accept a 1% error rate on\n      // the pipe.\n      t.ok(result.errors / result.requests.total < 0.01, `should have less than 1% errors on Windows (had ${result.errors} errors)`)\n    } else {\n      t.equal(result.errors, 0, 'no errors')\n    }\n\n    t.equal(result['1xx'], 0, '1xx codes')\n    t.equal(result['2xx'], result.requests.total, '2xx codes')\n    t.equal(result['3xx'], 0, '3xx codes')\n    t.equal(result['4xx'], 0, '4xx codes')\n    t.equal(result['5xx'], 0, '5xx codes')\n    t.equal(result.non2xx, 0, 'non 2xx codes')\n    t.end()\n  })\n})\n\nfor (let i = 1; i <= 5; i++) {\n  test(`run should count all ${i}xx status codes`, (t) => {\n    const server = helper.startServer({ statusCode: i * 100 + 2 })\n\n    initJob({\n      url: `http://localhost:${server.address().port}`,\n      connections: 2,\n      duration: 2\n    }, (err, result) => {\n      t.error(err)\n\n      t.ok(result[`${i}xx`], `${i}xx status codes recorded`)\n\n      t.ok(result.latency, 'latency exists')\n      t.ok(!Number.isNaN(result.latency.average), 'latency.average is not NaN')\n      t.type(result.latency.average, 'number', 'latency.average exists')\n      t.type(result.latency.stddev, 'number', 'latency.stddev exists')\n      t.ok(result.latency.min >= 0, 'latency.min exists')\n      t.type(result.latency.max, 'number', 'latency.max exists')\n      t.type(result.latency.p2_5, 'number', 'latency.p2_5 (2.5%) exists')\n      t.type(result.latency.p50, 'number', 'latency.p50 (50%) exists')\n      t.type(result.latency.p97_5, 'number', 'latency.p97_5 (97.5%) exists')\n      t.type(result.latency.p99, 'number', 'latency.p99 (99%) exists')\n\n      t.ok(result.throughput, 'throughput exists')\n      t.ok(!Number.isNaN(result.throughput.average), 'throughput.average is not NaN')\n      t.type(result.throughput.average, 'number', 'throughput.average exists')\n      t.type(result.throughput.stddev, 'number', 'throughput.stddev exists')\n      t.type(result.throughput.min, 'number', 'throughput.min exists')\n      t.type(result.throughput.max, 'number', 'throughput.max exists')\n      t.ok(result.throughput.total >= result.throughput.average * 2 / 100 * 95, 'throughput.total exists')\n      t.type(result.throughput.p1, 'number', 'throughput.p1 (1%) exists')\n      t.type(result.throughput.p2_5, 'number', 'throughput.p2_5 (2.5%) exists')\n      t.type(result.throughput.p50, 'number', 'throughput.p50 (50%) exists')\n      t.type(result.throughput.p97_5, 'number', 'throughput.p97_5 (97.5%) exists')\n\n      t.end()\n    })\n  })\n}\n\ntest('run should not modify default options', (t) => {\n  const origin = Object.assign({}, defaultOptions)\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    duration: 2\n  }, function (err, result) {\n    t.error(err)\n    t.same(defaultOptions, origin, 'calling run function does not modify default options')\n    t.end()\n  })\n})\n\ntest('run will exclude non 2xx stats from latency and throughput averages if excludeErrorStats is true', (t) => {\n  const server = helper.startServer({ statusCode: 404 })\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 2,\n    duration: 2,\n    excludeErrorStats: true\n  }, (err, result) => {\n    t.error(err)\n\n    t.equal(result['1xx'], 0, '1xx codes')\n    t.equal(result['2xx'], 0, '2xx codes')\n    t.equal(result['3xx'], 0, '3xx codes')\n    t.equal(result['4xx'], result.requests.total, '4xx codes')\n    t.equal(result['5xx'], 0, '5xx codes')\n    t.equal(result.non2xx, result.requests.total, 'non 2xx codes')\n\n    t.ok(result.latency, 'latency exists')\n    t.equal(result.latency.average, 0, 'latency.average should be 0')\n    t.equal(result.latency.stddev, 0, 'latency.stddev should be 0')\n    t.equal(result.latency.min, 0, 'latency.min should be 0')\n    t.equal(result.latency.max, 0, 'latency.max should be 0')\n    t.equal(result.latency.p1, 0, 'latency.p1 (1%) should be 0')\n    t.equal(result.latency.p2_5, 0, 'latency.p2_5 (2.5%) should be 0')\n    t.equal(result.latency.p50, 0, 'latency.p50 (50%) should be 0')\n    t.equal(result.latency.p97_5, 0, 'latency.p97_5 (97.5%) should be 0')\n    t.equal(result.latency.p99, 0, 'latency.p99 (99%) should be 0')\n\n    t.ok(result.throughput, 'throughput exists')\n    t.equal(result.throughput.average, 0, 'throughput.average should be 0')\n    t.equal(result.throughput.stddev, 0, 'throughput.stddev should be 0')\n    t.equal(result.throughput.min, 0, 'throughput.min should be 0')\n    t.equal(result.throughput.max, 0, 'throughput.max should be 0')\n    t.equal(result.throughput.total, 0, 'throughput.total should be 0')\n    t.equal(result.throughput.p1, 0, 'throughput.p1 (1%) should be 0')\n    t.equal(result.throughput.p2_5, 0, 'throughput.p2_5 (2.5%) should be 0')\n    t.equal(result.throughput.p50, 0, 'throughput.p50 (50%) should be 0')\n    t.equal(result.throughput.p97_5, 0, 'throughput.p97_5 (97.5%) should be 0')\n\n    t.end()\n  })\n})\n\ntest('tracker will emit reqError with error message on timeout', (t) => {\n  t.plan(2)\n\n  const server = helper.startTimeoutServer()\n\n  const tracker = initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 1,\n    duration: 5,\n    timeout: 2,\n    bailout: 1,\n    excludeErrorStats: true\n  })\n\n  tracker.once('reqError', (err) => {\n    t.type(err, Error, 'reqError should pass an Error to listener')\n    t.equal(err.message, 'request timed out', 'error should indicate timeout')\n    tracker.stop()\n  })\n})\n\ntest('tracker will emit reqError with error message on error', (t) => {\n  t.plan(1)\n\n  const server = helper.startSocketDestroyingServer()\n\n  const tracker = initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 10,\n    duration: 15,\n    method: 'POST',\n    body: 'hello',\n    excludeErrorStats: true\n  })\n\n  tracker.once('reqError', (err) => {\n    console.log(err)\n    t.type(err, Error, 'reqError should pass an Error to listener')\n    tracker.stop()\n  })\n})\n\ntest('tracker will emit reqMismatch when body does not match expectBody', (t) => {\n  t.plan(2)\n\n  const responseBody = 'hello world'\n  const server = helper.startServer({ body: responseBody })\n\n  const expectBody = 'goodbye world'\n\n  const tracker = initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 10,\n    duration: 15,\n    method: 'GET',\n    body: 'hello',\n    expectBody\n  })\n\n  tracker.once('reqMismatch', (bodyStr) => {\n    t.equal(bodyStr, responseBody)\n    t.not(bodyStr, expectBody)\n    tracker.stop()\n  })\n})\n\ntest('tracker will emit tick with current counter value', (t) => {\n  t.plan(1)\n\n  const server = helper.startSocketDestroyingServer()\n\n  const tracker = initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 10,\n    duration: 10\n  })\n\n  tracker.once('tick', (counter) => {\n    t.type(counter, 'object')\n    tracker.stop()\n  })\n})\n\ntest('throw if connections is greater than amount', (t) => {\n  t.plan(1)\n\n  const server = helper.startSocketDestroyingServer()\n\n  t.throws(function () {\n    initJob({\n      url: `http://localhost:${server.address().port}`,\n      connections: 10,\n      amount: 1,\n      excludeErrorStats: true\n    }, () => {})\n  })\n})\n\ntest('run promise', (t) => {\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    duration: 2,\n    title: 'title321'\n  }).then(result => {\n    t.ok(result.duration >= 2, 'duration is at least 2s')\n    t.equal(result.title, 'title321', 'title should be what was passed in')\n    t.equal(result.connections, 2, 'connections is the same')\n    t.equal(result.pipelining, 1, 'pipelining is the default')\n\n    t.ok(result.latency, 'latency exists')\n    t.type(result.latency.average, 'number', 'latency.average exists')\n    t.type(result.latency.stddev, 'number', 'latency.stddev exists')\n    t.ok(result.latency.min >= 0, 'latency.min exists')\n    t.type(result.latency.max, 'number', 'latency.max exists')\n    t.type(result.latency.p2_5, 'number', 'latency.p2_5 (2.5%) exists')\n    t.type(result.latency.p50, 'number', 'latency.p50 (50%) exists')\n    t.type(result.latency.p97_5, 'number', 'latency.p97_5 (97.5%) exists')\n    t.type(result.latency.p99, 'number', 'latency.p99 (99%) exists')\n\n    t.ok(result.requests, 'requests exists')\n    t.type(result.requests.average, 'number', 'requests.average exists')\n    t.type(result.requests.stddev, 'number', 'requests.stddev exists')\n    t.type(result.requests.min, 'number', 'requests.min exists')\n    t.type(result.requests.max, 'number', 'requests.max exists')\n    t.ok(result.requests.total >= result.requests.average * 2 / 100 * 95, 'requests.total exists')\n    t.type(result.requests.sent, 'number', 'sent exists')\n    t.ok(result.requests.sent >= result.requests.total, 'total requests made should be more than or equal to completed requests total')\n    t.type(result.requests.p1, 'number', 'requests.p1 (1%) exists')\n    t.type(result.requests.p2_5, 'number', 'requests.p2_5 (2.5%) exists')\n    t.type(result.requests.p50, 'number', 'requests.p50 (50%) exists')\n    t.type(result.requests.p97_5, 'number', 'requests.p97_5 (97.5%) exists')\n\n    t.ok(result.throughput, 'throughput exists')\n    t.type(result.throughput.average, 'number', 'throughput.average exists')\n    t.type(result.throughput.stddev, 'number', 'throughput.stddev exists')\n    t.type(result.throughput.min, 'number', 'throughput.min exists')\n    t.type(result.throughput.max, 'number', 'throughput.max exists')\n    t.ok(result.throughput.total >= result.throughput.average * 2 / 100 * 95, 'throughput.total exists')\n    t.type(result.throughput.p1, 'number', 'throughput.p1 (1%) exists')\n    t.type(result.throughput.p2_5, 'number', 'throughput.p2_5 (2.5%) exists')\n    t.type(result.throughput.p50, 'number', 'throughput.p50 (50%) exists')\n    t.type(result.throughput.p97_5, 'number', 'throughput.p97_5 (97.5%) exists')\n\n    t.ok(result.start, 'start time exists')\n    t.ok(result.finish, 'finish time exists')\n\n    t.equal(result.errors, 0, 'no errors')\n    t.equal(result.resets, 0, 'no resets')\n\n    t.equal(result['1xx'], 0, '1xx codes')\n    t.equal(result['2xx'], result.requests.total, '2xx codes')\n    t.equal(result['3xx'], 0, '3xx codes')\n    t.equal(result['4xx'], 0, '4xx codes')\n    t.equal(result['5xx'], 0, '5xx codes')\n    t.equal(result.non2xx, 0, 'non 2xx codes')\n\n    t.end()\n  })\n})\n\ntest('should throw if duration is not a number nor a string', t => {\n  t.plan(1)\n  const server = helper.startServer()\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    duration: ['foobar'],\n    title: 'title321'\n  })\n    .then((result) => {\n      t.fail()\n    })\n    .catch((err) => {\n      t.equal(err.message, 'duration entered was in an invalid format')\n    })\n})\n\ntest('should emit error', t => {\n  t.plan(1)\n  const server = helper.startServer()\n  const tracker = initJob({\n    url: `http://unknownhost:${server.address().port}`,\n    connections: 1,\n    timeout: 100,\n    forever: true,\n    form: {\n      param1: {\n        type: 'string',\n        value: null // this will trigger an error\n      }\n    }\n  })\n\n  tracker.once('error', (error) => {\n    t.equal(error.message, 'A \\'type\\' key with value \\'text\\' or \\'file\\' should be specified')\n    t.end()\n  })\n})\n\ntest('should throw if timeout is less than zero', t => {\n  t.plan(1)\n  const server = helper.startServer()\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    timeout: -1,\n    title: 'title321'\n  })\n    .then((result) => {\n      t.fail()\n    })\n    .catch((err) => {\n      t.equal(err.message, 'timeout must be greater than 0')\n    })\n})\n\ntest('should handle duration in string format', t => {\n  t.plan(1)\n  const server = helper.startServer()\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    duration: '1',\n    title: 'title321'\n  }).then((result) => {\n    t.pass()\n  })\n})\n\ntest('should count resets', t => {\n  t.plan(1)\n  const server = helper.startServer()\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 1,\n    amount: 10,\n    requests: [\n      { method: 'GET' },\n      { method: 'PUT' },\n      {\n        method: 'POST',\n        // falsey result will reset\n        setupRequest: () => {}\n      }\n    ]\n  }).then((result) => {\n    t.equal(result.resets, 4)\n    t.end()\n  })\n})\n\ntest('should get onResponse callback invoked even when there is no body', async t => {\n  t.plan(4)\n  const server = helper.startServer({ responses: [{ statusCode: 200, body: 'ok' }, { statusCode: 204 }] })\n\n  await initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 1,\n    amount: 2,\n    requests: [\n      {\n        method: 'GET',\n        onResponse (status, body) {\n          t.same(status, 200)\n          t.same(body, 'ok')\n        }\n      },\n      {\n        method: 'GET',\n        onResponse (status, body) {\n          t.same(status, 204)\n          t.same(body, '')\n        }\n      }\n    ]\n  })\n\n  t.end()\n})\n\ntest('should use request from HAR', (t) => {\n  t.plan(6)\n  const server = helper.startServer()\n  const url = `http://localhost:${server.address().port}`\n  const har = helper.customizeHAR('./fixtures/httpbin-get.json', 'https://httpbin.org', url)\n\n  initJob({\n    url,\n    duration: 1,\n    har\n  }, (err, res) => {\n    t.error(err)\n    t.ok(res, 'results should exist')\n    t.equal(res.errors, 0)\n    t.equal(res.timeouts, 0)\n    t.ok(res['2xx'] > 0)\n    t.equal(res.url, url)\n    t.end()\n  })\n})\n\ntest('should use extend headers of HAR requests', (t) => {\n  t.plan(6 + 2) // header check done as many times as sent requests\n  const server = helper.startServer()\n  const url = `http://localhost:${server.address().port}`\n  const har = helper.customizeHAR('./fixtures/httpbin-simple-get.json', 'https://httpbin.org', url)\n\n  initJob({\n    url,\n    connections: 1,\n    amount: 2,\n    headers: { 'X-CUSTOM': 'my-own-value' },\n    har\n  }, (err, res) => {\n    t.error(err)\n    t.ok(res, 'results should exist')\n    t.equal(res.errors, 0)\n    t.equal(res.timeouts, 0)\n    t.ok(res['2xx'] > 0)\n    t.equal(res.url, url)\n    t.end()\n  }).on('response', (client) => {\n    t.equal(client.requestIterator.currentRequest.headers['X-CUSTOM'], 'my-own-value', 'X-CUSTOM was not sent to server')\n  })\n})\n\ntest('should not override method or body of HAR requests', (t) => {\n  t.plan(6 + 4) // method and body checks done as many times as sent requests\n  const server = helper.startServer()\n  const url = `http://localhost:${server.address().port}`\n  const har = helper.customizeHAR('./fixtures/httpbin-simple-get.json', 'https://httpbin.org', url)\n\n  initJob({\n    url,\n    connections: 1,\n    amount: 2,\n    method: 'POST',\n    body: 'my-custom-body',\n    har\n  }, (err, res) => {\n    t.error(err)\n    t.ok(res, 'results should exist')\n    t.equal(res.errors, 0)\n    t.equal(res.timeouts, 0)\n    t.ok(res['2xx'] > 0)\n    t.equal(res.url, url)\n    t.end()\n  }).on('response', (client) => {\n    t.equal(client.requestIterator.currentRequest.method, 'GET', 'Method was not mean to be overidden')\n    t.equal(client.requestIterator.currentRequest.body, undefined, 'Body was not mean to be overidden')\n  })\n})\n\ntest('should ignore HAR requests targetting a different domain', (t) => {\n  t.plan(6)\n  const server = helper.startServer()\n  const url = `http://localhost:${server.address().port}`\n  const har = helper.customizeHAR('./fixtures/multi-domains.json', 'https://httpbin.org', url)\n\n  initJob({\n    url,\n    connections: 1,\n    amount: 4,\n    har\n  }, (err, res) => {\n    t.error(err)\n    t.ok(res, 'results should exist')\n    t.equal(res.errors, 0)\n    t.equal(res.timeouts, 0)\n    // if the github request is fired, it'll fail with 4xx status\n    t.equal(res['2xx'], 4)\n    t.equal(res.url, url)\n    t.end()\n  })\n})\n\ntest('should throw on invalid HAR', (t) => {\n  t.plan(1)\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 1,\n    amount: 4,\n    har: {\n      log: {\n        version: '1.2',\n        creator: {\n          name: 'Firefox',\n          version: '80.0.1'\n        },\n        pages: [\n          {\n            startedDateTime: '2020-09-28T16:43:28.987+02:00',\n            id: 'page_1',\n            title: 'mcollina/autocannon: fast HTTP/1.1 benchmarking tool written in Node.js',\n            pageTimings: {\n              onContentLoad: 1234,\n              onLoad: 1952\n            }\n          }\n        ]\n      }\n    }\n  }, (err, res) => {\n    t.match(err, /Could not parse HAR content: no entries found/)\n    t.end()\n  })\n})\n\ntest('should run when no callback is passed in', (t) => {\n  t.plan(1)\n\n  const tracker = initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 1,\n    duration: 1\n  })\n  t.resolveMatch(tracker, { connections: 1 }, 'The main tracker should resolve')\n})\n\ntest('Should run a warmup if one is passed in', (t) => {\n  t.plan(1)\n\n  const tracker = initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 1,\n    duration: 1,\n    warmup: {\n      connections: 1,\n      duration: 1\n    }\n  })\n  t.resolves(tracker, 'The main tracker should resolve')\n})\n\ntest('The warmup should not pollute the main result set', (t) => {\n  t.plan(3)\n\n  const tracker = initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 3,\n    duration: 1,\n    warmup: {\n      connections: 4,\n      duration: 2\n    }\n  })\n  tracker.then((result) => {\n    t.equal(result.connections, 3, 'connections should equal the main connections and not the warmup connections')\n    t.ok(result.duration >= 1, 'duration should equal the main duration and not the warmup duration')\n    t.type(result.warmup, 'object')\n  })\n})\n\ntest('should get headers passed from server onResponse callback', async t => {\n  t.plan(3)\n  const server = helper.startServer({ responses: [{ statusCode: 200, body: 'ok', headers: { 'set-cookie': 123 } }] })\n\n  await initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 1,\n    amount: 1,\n    requests: [\n      {\n        method: 'GET',\n        onResponse (status, body, context, headers) {\n          t.same(status, 200)\n          t.same(body, 'ok')\n          t.same(headers['set-cookie'], 123)\n        }\n      }\n    ]\n  })\n\n  t.end()\n})\n\ntest('should get multi-value headers passed from server onResponse callback', async t => {\n  t.plan(3)\n  const server = helper.startServer({ responses: [{ statusCode: 200, body: 'ok', headers: { 'set-cookie': [123, 456, 789] } }] })\n\n  await initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 1,\n    amount: 1,\n    requests: [\n      {\n        method: 'GET',\n        onResponse (status, body, context, headers) {\n          t.same(status, 200)\n          t.same(body, 'ok')\n          t.same(headers['set-cookie'], [123, 456, 789])\n        }\n      }\n    ]\n  })\n\n  t.end()\n})\n"
  },
  {
    "path": "test/runAmount.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst initJob = require('../lib/init')\nconst helper = require('./helper')\nconst timeoutServer = helper.startTimeoutServer()\nconst server = helper.startServer()\n\ntest('run should only send the expected number of requests', (t) => {\n  t.plan(10)\n\n  let done = false\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    duration: 1,\n    connections: 100,\n    amount: 50146\n  }, (err, res) => {\n    t.error(err)\n    t.equal(res.requests.total + res.timeouts, 50146, 'results should match the amount')\n    t.equal(res.requests.sent, 50146, 'totalRequests should match the amount')\n    done = true\n  })\n\n  setTimeout(() => {\n    t.notOk(done)\n  }, 1000)\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 2,\n    maxConnectionRequests: 10\n  }, (err, res) => {\n    t.error(err)\n    t.equal(res.requests.total, 20, 'results should match max connection requests * connections')\n    t.equal(res.requests.sent, 20, 'totalRequests should match the expected amount')\n  })\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 2,\n    maxOverallRequests: 10\n  }, (err, res) => {\n    t.error(err)\n    t.equal(res.requests.total, 10, 'results should match max overall requests')\n    t.equal(res.requests.sent, 10, 'totalRequests should match the expected amount')\n  })\n})\n\ntest('should shutdown after all amounts timeout', (t) => {\n  t.plan(5)\n\n  initJob({\n    url: `http://localhost:${timeoutServer.address().port}`,\n    amount: 10,\n    timeout: 2,\n    connections: 10\n  }, (err, res) => {\n    t.error(err)\n    t.equal(res.errors, 10)\n    t.equal(res.timeouts, 10)\n    t.equal(res.requests.sent, 10, 'totalRequests should match the expected amount')\n    t.equal(res.requests.total, 0, 'total completed requests should be 0')\n  })\n})\n\ntest('should reconnect twice to the server with a reset rate of 10 for 20 connections', (t) => {\n  t.plan(3)\n  const testServer = helper.startServer()\n\n  initJob({\n    url: 'localhost:' + testServer.address().port,\n    connections: 1,\n    amount: 20,\n    reconnectRate: 2\n  }, (err, res) => {\n    t.error(err)\n    t.equal(res.requests.sent, 20, 'totalRequests should match the expected amount')\n    t.equal(testServer.autocannonConnects, 10, 'should have connected to the server 10 times after dropping the connection every second request')\n    t.end()\n  })\n})\n"
  },
  {
    "path": "test/runMultiServer.test.js",
    "content": "const test = require('tap').test\nconst initJob = require('../lib/init')\nconst helper = require('./helper')\n\nconst server1 = helper.startServer({ body: 'from server1' })\nconst server2 = helper.startServer({ body: 'from server2' })\nconst server3 = helper.startServer({ body: 'from server3' })\n\ntest('should receive the message from different server', (t) => {\n  t.plan(3)\n\n  const instance = initJob({\n    url: [\n      server1,\n      server2,\n      server3\n    ].map(server => `http://localhost:${server.address().port}`),\n    duration: 1,\n    connections: 3\n  })\n\n  let receivedServer1 = false\n  let receivedServer2 = false\n  let receivedServer3 = false\n\n  instance.on('response', (client) => {\n    if (!receivedServer1 && client.parser.chunk.toString().includes('from server1')) {\n      receivedServer1 = true\n      t.pass()\n    }\n    if (!receivedServer2 && client.parser.chunk.toString().includes('from server2')) {\n      receivedServer2 = true\n      t.pass()\n    }\n    if (!receivedServer3 && client.parser.chunk.toString().includes('from server3')) {\n      receivedServer3 = true\n      t.pass()\n    }\n  })\n})\n"
  },
  {
    "path": "test/runMultipart.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst { tmpdir } = require('os')\nconst { join } = require('path')\nconst { writeFile } = require('fs')\nconst { promisify } = require('util')\nconst initJob = require('../lib/init')\nconst helper = require('./helper')\nconst writef = promisify(writeFile)\nconst { hasWorkerSupport } = require('../lib/util')\n\ntest('run should return an error with invalid form options', async t => {\n  const cases = [\n    {\n      name: 'invalid JSON',\n      value: 'u',\n      message: 'Invalid JSON or file where to get form data'\n    },\n    {\n      name: 'non existing JSON file',\n      value: 'nonexisting.json',\n      message: 'Invalid JSON or file where to get form data'\n    },\n    {\n      name: 'JSON options missing path key in file type',\n      value: '{ \"image\": { \"type\": \"file\" }}',\n      message: 'Missing key \\'path\\' in form object for key \\'image\\''\n    },\n    {\n      name: 'JS Object missing path key in file type',\n      value: { image: { type: 'file' } },\n      message: 'Missing key \\'path\\' in form object for key \\'image\\''\n    },\n    {\n      name: 'JSON options missing value in text type',\n      value: '{ \"image\": { \"type\": \"text\" }}',\n      message: 'Missing key \\'value\\' in form object for key \\'image\\''\n    },\n    {\n      name: 'JS Object missing value in text type',\n      value: { image: { type: 'text' } },\n      message: 'Missing key \\'value\\' in form object for key \\'image\\''\n    },\n    {\n      name: 'JSON options with not supported type',\n      value: '{ \"image\": { \"type\": \"random\" }}',\n      message: 'A \\'type\\' key with value \\'text\\' or \\'file\\' should be specified'\n    },\n    {\n      name: 'JS Object with not supported type',\n      value: { image: { type: 'random' } },\n      message: 'A \\'type\\' key with value \\'text\\' or \\'file\\' should be specified'\n    }\n  ]\n\n  const server = helper.startMultipartServer()\n  t.teardown(() => server.close())\n\n  for (const c of cases) {\n    t.test(c.name, async t => {\n      const [err] = await new Promise((resolve) => {\n        initJob({\n          url: 'http://localhost:' + server.address().port,\n          connections: 1,\n          amount: 1,\n          form: c.value\n        }, (err, res) => {\n          resolve([err, res])\n        })\n      })\n      await t.test('error', t => {\n        t.not(null, err)\n        t.equal(c.message, err.message, `mismatching error message ${err.message}`)\n        t.end()\n      })\n    })\n  }\n})\n\ntest('run should take form options as a JSON string or a JS Object', async t => {\n  const form = {\n    image: {\n      type: 'file',\n      path: require.resolve('./j5.jpeg')\n    },\n    name: {\n      type: 'text',\n      value: 'j5'\n    }\n  }\n  const string = JSON.stringify(form)\n  const temp = tmpdir()\n  const jsonFile = join(temp, 'multipart.json')\n\n  await writef(jsonFile, string, 'utf8')\n\n  const cases = [\n    {\n      name: 'from string',\n      value: string\n    },\n    {\n      name: 'from json file',\n      value: jsonFile\n    },\n    {\n      name: 'from JS Object',\n      value: form\n    }\n  ]\n  const allCases = [...cases, ...cases.map(c => ({ ...c, workers: true }))]\n\n  for (const c of allCases) {\n    t.test(c.name, { skip: c.workers && !hasWorkerSupport }, async t => {\n      const server = helper.startMultipartServer(null, payload => {\n        t.equal('j5', payload.name)\n        t.equal('j5.jpeg', payload.image.filename)\n      })\n      t.teardown(() => server.close())\n      const [err, res] = await new Promise((resolve) => {\n        initJob({\n          url: 'http://localhost:' + server.address().port,\n          connections: 1,\n          amount: 1,\n          form: c.value,\n          workers: c.workers ? 1 : undefined // use only one worker coz we're checking for 1 req\n        }, (err, res) => {\n          resolve([err, res])\n        })\n      })\n      await t.test('result', t => {\n        t.equal(null, err)\n        t.equal(0, res.errors, 'result should not have errors')\n        t.equal(1, res['2xx'], 'result status code should be 2xx')\n        t.equal(0, res.non2xx, 'result status code should be 2xx')\n        t.end()\n      })\n    })\n  }\n})\n\ntest('run should use a custom method if `options.method` is passed', t => {\n  const server = helper.startMultipartServer(null, payload => {\n    t.equal('j5', payload.name)\n    t.equal('j5.jpeg', payload.image.filename)\n  })\n  t.teardown(() => server.close())\n\n  const form = {\n    image: {\n      type: 'file',\n      path: require.resolve('./j5.jpeg')\n    },\n    name: {\n      type: 'text',\n      value: 'j5'\n    }\n  }\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    method: 'PUT',\n    connections: 1,\n    amount: 1,\n    form\n  }, (err, res) => {\n    t.equal(null, err)\n    t.equal(0, res.errors, 'result should not have errors')\n    t.equal(1, res['2xx'], 'result status code should be 2xx')\n    t.equal(0, res.non2xx, 'result status code should be 2xx')\n    t.end()\n  })\n})\n\ntest('run should set filename', t => {\n  const server = helper.startMultipartServer(null, payload => {\n    t.equal('j5', payload.name)\n    t.equal('j5.jpeg', payload.image.filename)\n  })\n  t.teardown(() => server.close())\n\n  const form = {\n    image: {\n      type: 'file',\n      path: require.resolve('./j5.jpeg')\n    },\n    name: {\n      type: 'text',\n      value: 'j5'\n    }\n  }\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    method: 'POST',\n    connections: 1,\n    amount: 1,\n    form\n  }, (err, res) => {\n    t.equal(null, err)\n    t.equal(0, res.errors, 'result should not have errors')\n    t.equal(1, res['2xx'], 'result status code should be 2xx')\n    t.equal(0, res.non2xx, 'result status code should be 2xx')\n    t.end()\n  })\n})\n\ntest('run should allow overriding filename', t => {\n  const server = helper.startMultipartServer(null, payload => {\n    t.equal('j5', payload.name)\n    t.equal('testfilename.jpeg', payload.image.filename)\n  })\n  t.teardown(() => server.close())\n\n  const form = {\n    image: {\n      type: 'file',\n      path: require.resolve('./j5.jpeg'),\n      options: {\n        filename: 'testfilename.jpeg'\n      }\n    },\n    name: {\n      type: 'text',\n      value: 'j5'\n    }\n  }\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    method: 'POST',\n    connections: 1,\n    amount: 1,\n    form\n  }, (err, res) => {\n    t.equal(null, err)\n    t.equal(0, res.errors, 'result should not have errors')\n    t.equal(1, res['2xx'], 'result status code should be 2xx')\n    t.equal(0, res.non2xx, 'result status code should be 2xx')\n    t.end()\n  })\n})\n\ntest('run should allow overriding filename with file path', t => {\n  const server = helper.startMultipartServer({ preservePath: true }, payload => {\n    t.equal('j5', payload.name)\n    t.equal('some/path/testfilename.jpeg', payload.image.filename)\n  })\n  t.teardown(() => server.close())\n\n  const form = {\n    image: {\n      type: 'file',\n      path: require.resolve('./j5.jpeg'),\n      options: {\n        filepath: 'some/path/testfilename.jpeg'\n      }\n    },\n    name: {\n      type: 'text',\n      value: 'j5'\n    }\n  }\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    method: 'POST',\n    connections: 1,\n    amount: 1,\n    form\n  }, (err, res) => {\n    t.equal(null, err)\n    t.equal(0, res.errors, 'result should not have errors')\n    t.equal(1, res['2xx'], 'result status code should be 2xx')\n    t.equal(0, res.non2xx, 'result status code should be 2xx')\n    t.end()\n  })\n})\n"
  },
  {
    "path": "test/runRate.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst initJob = require('../lib/init')\nconst helper = require('./helper')\nconst server = helper.startServer()\n\ntest('run should only send the expected number of requests per second - scenario 1', (t) => {\n  t.plan(3)\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 2,\n    overallRate: 10,\n    amount: 40,\n    sampleInt: 1000\n  }, (err, res) => {\n    t.error(err)\n\n    t.equal(Math.floor(res.duration), 4, 'should have take 4 seconds to send 10 requests per seconds')\n    t.equal(res.requests.average, 10, 'should have sent 10 requests per second on average')\n  })\n})\n\ntest('run should only send the expected number of requests per second - scenario 2', (t) => {\n  t.plan(3)\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 2,\n    connectionRate: 10,\n    amount: 40,\n    sampleInt: 1000\n  }, (err, res) => {\n    t.error(err)\n    t.equal(Math.floor(res.duration), 2, 'should have taken 2 seconds to send 10 requests per connection with 2 connections')\n    t.equal(res.requests.average, 20, 'should have sent 20 requests per second on average with two connections')\n  })\n})\n\ntest('run should only send the expected number of requests per second - scenario 3', (t) => {\n  t.plan(3)\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 15,\n    overallRate: 10,\n    amount: 40,\n    sampleInt: 1000\n  }, (err, res) => {\n    t.error(err)\n    t.equal(Math.floor(res.duration), 4, 'should have take 4 seconds to send 10 requests per seconds')\n    t.equal(res.requests.average, 10, 'should have sent 10 requests per second on average')\n  })\n})\n\ntest('run should compensate for coordinated omission when the expected number of requests per second is too high', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 100,\n    connectionRate: 1000,\n    duration: 1\n  }, (err, res) => {\n    t.error(err)\n    t.not(res.latency.totalCount, res.requests.total, 'should have recorded additionnal latencies')\n  })\n})\n\ntest('run should not compensate for coordinated omission when this feature is disabled', (t) => {\n  t.plan(2)\n\n  initJob({\n    url: `http://localhost:${server.address().port}`,\n    connections: 100,\n    connectionRate: 1000,\n    ignoreCoordinatedOmission: true,\n    duration: 1\n  }, (err, res) => {\n    t.error(err)\n    t.equal(res.latency.totalCount, res.requests.total, 'should not have recorded additionnal latencies')\n  })\n})\n"
  },
  {
    "path": "test/sampleInt.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst validate = require('../lib/validate')\nconst parseArguments = require('../autocannon').parseArguments\nconst initJob = require('../lib/init')\nconst printResult = require('../lib/printResult')\n\ntest('validate should return an error', (t) => {\n  t.plan(2)\n\n  const args = {\n    sampleInt: 'hello',\n    url: 'https://github.com/mcollina/autocannon'\n  }\n\n  const result = validate(args)\n\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'sample interval entered was in an invalid format')\n})\n\ntest('validate should return an error', (t) => {\n  t.plan(2)\n\n  const args = {\n    sampleInt: -1,\n    url: 'https://github.com/mcollina/autocannon'\n  }\n\n  const result = validate(args)\n\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'sample interval can not be less than 0')\n})\n\ntest('validate should not return an error', (t) => {\n  t.plan(2)\n\n  const args = {\n    sampleInt: 2,\n    url: 'https://github.com/mcollina/autocannon'\n  }\n\n  const result = validate(args)\n\n  t.ok(!(result instanceof Error))\n  t.equal(result.sampleInt, 2)\n})\n\ntest('parseArguments should accept value in ms (2000)', (t) => {\n  t.plan(1)\n\n  const args = [\n    '-L', 2000,\n    'https://github.com/mcollina/autocannon'\n  ]\n\n  const result = parseArguments(args)\n\n  t.equal(result.sampleInt, 2000)\n})\n\ntest('run should return sampleInt == 2000 & samples == 3', (t) => {\n  t.plan(2)\n\n  initJob({\n    duration: 6,\n    sampleInt: 2000,\n    url: 'https://github.com/mcollina/autocannon'\n  }, (err, res) => {\n    if (err) {\n      console.err(err)\n    }\n    t.equal(res.sampleInt, 2000)\n    t.equal(res.samples, 3)\n  })\n})\n\ntest('printResult should print the sample interval (2) & the total samples (3)', (t) => {\n  t.plan(2)\n\n  const result = {\n    duration: 6,\n    sampleInt: 2000,\n    samples: 3,\n    url: 'https://github.com/mcollina/autocannon',\n    latency: {},\n    requests: {},\n    throughput: {\n      average: 3319,\n      mean: 3319,\n      stddev: 0,\n      min: 3318,\n      max: 3318,\n      total: 3318,\n      p0_001: 3319,\n      p0_01: 3319,\n      p0_1: 3319,\n      p1: 3319,\n      p2_5: 3319,\n      p10: 3319,\n      p25: 3319,\n      p50: 3319,\n      p75: 3319,\n      p90: 3319,\n      p97_5: 3319,\n      p99: 3319,\n      p99_9: 3319,\n      p99_99: 3319,\n      p99_999: 3319\n    }\n  }\n\n  const output = printResult(result, {})\n\n  t.ok(output.includes('Req/Bytes counts sampled every 2 seconds.'))\n  t.ok(output.includes('# of samples: 3'))\n})\n"
  },
  {
    "path": "test/serial/autocannon.test.js",
    "content": "'use strict'\n\nconst childProcess = require('child_process')\nconst fs = require('fs')\nconst path = require('path')\nconst test = require('tap').test\n\nconst { promisify } = require('util')\n\nconst exec = promisify(childProcess.exec).bind(childProcess)\nconst proxyquire = require('proxyquire')\n\nconst baseDir = path.join(__dirname, '..', '..')\n\ntest('should print error if url.URL is not a function', t => {\n  t.plan(2)\n\n  const _error = console.error\n  const _exit = process.exit\n\n  process.exit = (code) => {\n    t.equal(code, 1)\n    process.exit = _exit\n    t.end()\n  }\n  console.error = (obj) => {\n    t.equal(\n      obj,\n      'autocannon requires the WHATWG URL API, but it is not available. Please upgrade to Node 6.13+.'\n    )\n    console.error = _error\n  }\n  proxyquire('../..', {\n    url: {\n      URL: null\n    }\n  })\n})\n\ntest('should print version if invoked with --version', async t => {\n  t.plan(1)\n  const res = await exec(`node ${baseDir}/autocannon.js --version`)\n  t.ok(res.stdout.match(/autocannon v(\\d+\\.\\d+\\.\\d+)/))\n})\n\ntest('should print help if invoked with --help', async t => {\n  t.plan(1)\n  const help = fs.readFileSync(path.join(baseDir, 'help.txt'), 'utf8')\n  const res = await exec(`node ${baseDir}/autocannon.js --help`)\n  t.same(res.stderr.trim(), help.trim()) // console.error adds \\n at the end of print\n})\n\ntest('should print help if no url is provided', async t => {\n  t.plan(1)\n  const help = fs.readFileSync(path.join(baseDir, 'help.txt'), 'utf8')\n  const res = await exec(`node ${baseDir}/autocannon.js`)\n  t.same(res.stderr.trim(), help.trim()) // console.error adds \\n at the end of print\n})\n\ntest('start should console an error when a promise is caught', (t) => {\n  const Autocannon = t.mock('../../autocannon', {\n    '../../lib/init': () => new Promise((resolve, reject) => {\n      reject(new Error('Test Error'))\n    })\n  })\n\n  t.plan(1)\n\n  const _error = console.error\n  console.error = (obj) => {\n    t.equal(\n      obj,\n      'Test Error'\n    )\n    console.error = _error\n  }\n\n  Autocannon.start(\n    Autocannon.parseArguments([\n      '-d', '1',\n      '-c', '1',\n      'http://localhost/foo/bar'\n    ])\n  )\n})\n\ntest('start should console an error when one is thrown without a promise', (t) => {\n  const Autocannon = t.mock('../../autocannon', {\n    '../../lib/init': () => { throw new Error('Test Error') }\n  })\n\n  t.plan(1)\n\n  const _error = console.error\n  console.error = (obj) => {\n    t.equal(\n      obj,\n      'Test Error'\n    )\n    console.error = _error\n  }\n\n  Autocannon.start(\n    Autocannon.parseArguments([\n      '-d', '1',\n      '-c', '1',\n      '--forever',\n      'http://localhost/foo/bar'\n    ])\n  )\n})\n\ntest('start should console an error when --on-port is used without async hooks', (t) => {\n  const Autocannon = t.mock('../../autocannon', {\n    'has-async-hooks': () => false,\n    child_process: {\n      spawn: () => {}\n    },\n    '../../lib/init': () => {},\n    net: {\n      createServer: () => ({\n        listen: () => {},\n        on: () => {}\n      })\n    }\n  })\n\n  t.plan(2)\n\n  const _exit = process.exit\n  process.exit = (code) => {\n    t.equal(code, 1)\n    process.exit = _exit\n    t.end()\n  }\n  const _error = console.error\n  console.error = (obj) => {\n    t.equal(\n      obj,\n      'The --on-port flag requires the async_hooks builtin module, but it is not available. Please upgrade to Node 8.1+.'\n    )\n    console.error = _error\n  }\n\n  Autocannon.start(\n    Autocannon.parseArguments([\n      '-d', '1',\n      '-c', '1',\n      '--on-port',\n      'http://localhost/foo/bar'\n    ])\n  )\n})\n\ntest('When there is a port, createChannel should try to unlink the socketPath', (t) => {\n  const Autocannon = t.mock('../../autocannon', {\n    'has-async-hooks': () => true,\n    child_process: {\n      spawn: () => {}\n    },\n    '../../lib/init': () => {},\n    net: {\n      createServer: () => ({\n        listen: () => {},\n        on: (eventName, cb) => {\n          t.equal(eventName, 'close')\n          cb()\n        }\n      })\n    }\n  })\n\n  t.plan(1)\n\n  Autocannon.start(\n    Autocannon.parseArguments([\n      '-d', '1',\n      '-c', '1',\n      '--on-port',\n      'http://localhost/foo/bar'\n    ])\n  )\n})\n\ntest('createChannel should try to unlink the socketPath', (t) => {\n  const Autocannon = t.mock('../../autocannon', {\n    'has-async-hooks': () => true,\n    child_process: {\n      spawn: () => {}\n    },\n    '../../lib/init': () => {},\n    net: {\n      createServer: () => ({\n        listen: () => {},\n        on: (_, cb) => {\n          t.equal(_, 'close')\n          cb()\n        }\n      })\n    }\n  })\n\n  t.plan(1)\n\n  Autocannon.start(\n    Autocannon.parseArguments([\n      '-d', '1',\n      '-c', '1',\n      '--on-port',\n      'http://localhost/foo/bar'\n    ])\n  )\n})\n"
  },
  {
    "path": "test/serial/run.test.js",
    "content": "'use strict'\nconst test = require('tap').test\nconst initJob = require('../../lib/init')\n\ntest('should log error on connection error', t => {\n  t.plan(1)\n  console.error = function (obj) {\n    t.type(obj, Error)\n    console.error = () => {}\n  }\n  initJob({\n    url: 'http://unknownhost',\n    connections: 2,\n    duration: 5,\n    title: 'title321',\n    debug: true\n  })\n})\n"
  },
  {
    "path": "test/serial/tap-parallel-not-ok",
    "content": ""
  },
  {
    "path": "test/serial/wasm.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst initJob = require('../../lib/init')\nconst helper = require('../helper')\n\ntest('should clean up HdrHistogram WASM memory at each run', async (t) => {\n  const server = helper.startServer()\n  const runTwentyTimes = (resolve, reject, numberOfRuns = 0) => {\n    initJob({\n      url: 'http://localhost:' + server.address().port,\n      connections: 1,\n      amount: 1\n    }, (result) => {\n      // should get error \" url or socketPath option required\"\n      // we can ignore this error, we just want run() to execute\n      // and to instantiate new WASM histograms\n      if (numberOfRuns < 20) {\n        runTwentyTimes(resolve, reject, ++numberOfRuns)\n      } else {\n        resolve()\n      }\n    })\n  }\n  const lotsOfRuns = []\n  for (let index = 0; index < 50; index++) {\n    lotsOfRuns.push(new Promise(runTwentyTimes))\n  }\n\n  await Promise.all(lotsOfRuns)\n\n  // if the process has not crashed, we are good \\o/\n  t.end()\n})\n"
  },
  {
    "path": "test/subargAliases.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst generateSubArgAliases = require('../lib/subargAliases')\n\ntest('generateSubArgAliases Should generate warmup aliases', (t) => {\n  t.plan(4)\n\n  const args = {\n    connections: 1,\n    duration: 2,\n    warmup: {\n      c: 3,\n      d: 4\n    }\n  }\n\n  const result = generateSubArgAliases(args)\n\n  t.equal(result.connections, 1)\n  t.equal(result.duration, 2)\n  t.equal(result.warmup.connections, 3)\n  t.equal(result.warmup.duration, 4)\n})\n\ntest('generateSubArgAliases should not process aliases that are not defined in subargAliases.js', (t) => {\n  t.plan(5)\n\n  const args = {\n    connections: 1,\n    warmup: {\n      c: 3,\n      T: 'A title'\n    }\n  }\n\n  const result = generateSubArgAliases(args)\n\n  t.equal(result.connections, 1)\n  t.equal(result.warmup.connections, 3)\n  t.equal(result.warmup.c, 3)\n  t.equal(result.warmup.T, 'A title')\n  t.equal(Object.keys(result.warmup).length, 3)\n})\n"
  },
  {
    "path": "test/tap-parallel-ok",
    "content": ""
  },
  {
    "path": "test/targetProcess.js",
    "content": "const server = require('./helper').startServer()\n\nserver.ref()\n"
  },
  {
    "path": "test/url.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\n// const { ofUrl, checkUrl } = require('../lib/url')\nconst { ofURL, checkURL } = require('../lib/url')\n\ntest('checkURL should return true if a populated string is passed', (t) => {\n  t.plan(1)\n\n  const result = checkURL('foo')\n  t.ok(result)\n})\n\ntest('checkURL should return false if an empty string is passed', (t) => {\n  t.plan(1)\n\n  const result = checkURL('')\n  t.notOk(result)\n})\n\ntest('checkURL should return true if a populated array is passed', (t) => {\n  t.plan(1)\n\n  const result = checkURL(['foo'])\n  t.ok(result)\n})\n\ntest('checkURL should return false if an empty array is passed', (t) => {\n  t.plan(1)\n\n  const result = checkURL([])\n  t.notOk(result)\n})\n\ntest('ofUrl should return the array if the passed in url is an array', (t) => {\n  t.plan(1)\n\n  const result = ofURL(['foo', 'bar'])\n  t.same(result, ['foo', 'bar'])\n})\n\ntest('When ofUrl is passed a string ofUrl should return an object containing a map function that accepts an url', (t) => {\n  t.plan(1)\n\n  const result = ofURL('foo', false)\n  const mappedResult = result.map((url) => url)\n  t.same(mappedResult, 'foo')\n})\n\ntest('When ofUrl is passed a string and asArray=true ofUrl should return an object containing a map function that returns an array', (t) => {\n  t.plan(1)\n\n  const result = ofURL('foo', true)\n  const mappedResult = result.map((url) => url)\n  t.same(mappedResult, ['foo'])\n})\n\ntest('ofUrl Should throw an error when passed an invalid url type', (t) => {\n  t.plan(1)\n\n  t.throws(() => ofURL(123), 'url should only be a string or an array of string')\n})\n"
  },
  {
    "path": "test/utils/has-worker-support.js",
    "content": "'use strict'\n\nconst semver = require('semver')\n\nmodule.exports = semver.gte(process.versions.node, '11.7.0')\n"
  },
  {
    "path": "test/utils/on-response.js",
    "content": "'use strict'\n\nmodule.exports = (status, body, context) => {\n  context.foo = 'bar=baz'\n}\n"
  },
  {
    "path": "test/utils/setup-client.js",
    "content": "'use strict'\n\nmodule.exports = (client) => {\n  client.setHeaders({ custom: 'my-header' })\n}\n"
  },
  {
    "path": "test/utils/setup-request.js",
    "content": "'use strict'\n\nmodule.exports = (req, context) => ({\n  ...req,\n  path: `/test-123?some=thing&${context.foo}`\n})\n"
  },
  {
    "path": "test/utils/verify-body.js",
    "content": "module.exports = (body) => {\n  return false\n}\n"
  },
  {
    "path": "test/validate.test.js",
    "content": "'use strict'\n\nconst test = require('tap').test\nconst validateOpts = require('../lib/validate')\nconst helper = require('./helper')\nconst { hasWorkerSupport } = require('../lib/util')\n\ntest('validateOpts should not return an error with only an url passed in', (t) => {\n  t.plan(1)\n\n  const result = validateOpts({ url: 'http://localhost' })\n  t.ok(!(result instanceof Error))\n})\n\ntest('validateOpts should return an error when workers option is present and hasWorkerSupport is false', (t) => {\n  const validateOpts = t.mock('../lib/validate', {\n    '../lib/util': { hasWorkerSupport: false }\n  })\n  t.plan(2)\n\n  const result = validateOpts({ workers: 1 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'Please use node >= 11.7.0 for workers support')\n})\n\ntest('validateOpts should return an error when bailout is less than 1', (t) => {\n  const validateOpts = t.mock('../lib/validate', {\n    '../lib/util': { hasWorkerSupport: false }\n  })\n  t.plan(2)\n\n  const result = validateOpts({ bailout: 0 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'bailout threshold can not be less than 1')\n})\n\ntest('validateOpts should return an error when connectionRate is less than 1', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ connectionRate: 0 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'connectionRate can not be less than 1')\n})\n\ntest('validateOpts should return an error when overallRate is less than 1', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ overallRate: 0 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'bailout overallRate can not be less than 1')\n})\n\ntest('validateOpts should return an error when amount is less than 1', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ amount: 0 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'amount can not be less than 1')\n})\n\ntest('validateOpts should return an error when maxConnectionRequests is less than 1', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ url: 'http://localhost', maxConnectionRequests: 0 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'maxConnectionRequests can not be less than 1')\n})\n\ntest('validateOpts should return an error when maxOverallRequests is less than 1', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ maxOverallRequests: 0 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'maxOverallRequests can not be less than 1')\n})\n\ntest('validateOpts should return an error when requests does not contain a valid setupRequest function', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ url: 'http://localhost', requests: [{ setupRequest: 123 }] })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'Invalid option setupRequest, please provide a function (or file path when in workers mode)')\n})\n\ntest('validateOpts should return an error when requests does not contain a valid onResponse function', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ url: 'http://localhost', requests: [{ onResponse: 123 }] })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'Invalid option onResponse, please provide a function (or file path when in workers mode)')\n})\n\ntest('validateOpts should return an error when setupClient is not a valid function', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ url: 'http://localhost', setupClient: 123 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'Invalid option setupClient, please provide a function (or file path when in workers mode)')\n})\n\ntest('validateOpts should return an error if neither url or socket path are provided', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'url or socketPath option required')\n})\n\ntest('validateOpts should convert a duration that is a string representation of a number into a number', (t) => {\n  t.plan(1)\n\n  const result = validateOpts({ url: 'http://localhost', duration: '100' })\n  t.equal(result.duration, 100)\n})\n\ntest('validateOpts should convert a duration that is a timestring into a number', (t) => {\n  t.plan(1)\n\n  const result = validateOpts({ url: 'http://localhost', duration: '2 weeks' })\n  t.equal(result.duration, 1209600)\n})\n\ntest('validateOpts should return an error if duration is in an invalid format', (t) => {\n  t.plan(1)\n\n  const result = validateOpts({ url: 'http://localhost', duration: '2 dsweeks' })\n  t.ok(result instanceof Error)\n})\n\ntest('validateOpts should return an error if duration less than 0', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ url: 'http://localhost', duration: -1 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'duration can not be less than 0')\n})\n\ntest('validateOpts should return an error if expectBody is used in conjunction with requests', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ url: 'http://localhost', expectBody: 'foo', requests: [] })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'expectBody cannot be used in conjunction with requests')\n})\n\ntest('validateOpts should parse a multipart form correctly', (t) => {\n  t.plan(1)\n\n  const result = validateOpts({ url: 'http://localhost', form: '{ \"field 1\": { \"type\": \"text\", \"value\": \"a text value\"} }' })\n  t.ok(result.form)\n})\n\ntest('validateOpts should return an error if a multipart form is incorrectly formatted', (t) => {\n  t.plan(1)\n\n  const result = validateOpts({ url: 'http://localhost', form: 'invalid form' })\n  t.ok(result instanceof Error)\n})\n\ntest('validateOpts should parse a HAR request successfully', (t) => {\n  t.plan(1)\n\n  const har = helper.customizeHAR('./fixtures/httpbin-get.json', 'https://httpbin.org', 'http://localhost')\n  const result = validateOpts({ url: 'http://localhost', har })\n  t.ok(result.har)\n})\n\ntest('validateOpts should return an error if a HAR request is unsuccessful', (t) => {\n  t.plan(1)\n\n  const result = validateOpts({ url: 'http://localhost', har: 'invalid har' })\n  t.ok(result instanceof Error)\n})\n\ntest('validateOpts should return an error when connections is less than 1', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ url: 'http://localhost', connections: 0 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'connections can not be less than 1')\n})\n\ntest('validateOpts should return an error when ignoreCoordinatedOmission used without connectionRate or overallRate', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({\n    url: 'http://localhost',\n    ignoreCoordinatedOmission: true\n  })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'ignoreCoordinatedOmission makes no sense without connectionRate or overallRate')\n})\n\ntest('validateOpts is successful when ignoreCoordinatedOmission is used with connectionRate', (t) => {\n  t.plan(1)\n\n  const result = validateOpts({\n    url: 'http://localhost',\n    ignoreCoordinatedOmission: true,\n    connectionRate: 1\n  })\n  t.ok(result.ignoreCoordinatedOmission)\n})\n\ntest('validateOpts is successful when ignoreCoordinatedOmission is used with overallRate', (t) => {\n  t.plan(1)\n\n  const result = validateOpts({\n    url: 'http://localhost',\n    ignoreCoordinatedOmission: true,\n    overallRate: 1\n  })\n  t.ok(result.ignoreCoordinatedOmission)\n})\n\ntest('validateOpts should return an error when forever is used with cbPassedIn', (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ url: 'http://localhost', forever: true }, () => {})\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'should not use the callback parameter when the `forever` option is set to true. Use the `done` event on this event emitter')\n})\n\ntest('validateOpts should return an error when forever is used with workers', { skip: !hasWorkerSupport }, (t) => {\n  t.plan(2)\n\n  const result = validateOpts({ url: 'http://localhost', forever: true, workers: 2 })\n  t.ok(result instanceof Error)\n  t.equal(result.message, 'Using `forever` option isn\\'t currently supported with workers')\n})\n\ntest('validateOpts should not set render options by default', (t) => {\n  t.plan(3)\n\n  const result = validateOpts({ url: 'http://localhost' })\n  t.equal(result.renderProgressBar, undefined)\n  t.equal(result.renderResultsTable, undefined)\n  t.equal(result.renderLatencyTable, undefined)\n})\n\ntest('validateOpts should disable render options when json is true', (t) => {\n  t.plan(3)\n\n  const result = validateOpts({ url: 'http://localhost', json: true })\n  t.equal(result.renderProgressBar, false)\n  t.equal(result.renderResultsTable, false)\n  t.equal(result.renderLatencyTable, false)\n})\n"
  },
  {
    "path": "test/workers.test.js",
    "content": "'use strict'\n\nconst path = require('path')\nconst fs = require('fs')\nconst test = require('tap').test\nconst http = require('http')\nconst initJob = require('../lib/init')\nconst { hasWorkerSupport } = require('../lib/util')\nconst helper = require('./helper')\nconst httpsServer = helper.startHttpsServer()\n\ntest('returns error when no worker support was found', (t) => {\n  const server = helper.startServer()\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 3,\n    workers: 3,\n    amount: 6,\n    title: 'with-workers'\n  }, function (err, result) {\n    if (hasWorkerSupport) {\n      t.error(err)\n    } else {\n      t.equal(err.message, 'Please use node >= 11.7.0 for workers support')\n    }\n\n    t.end()\n  })\n})\n\ntest('init with workers', { skip: !hasWorkerSupport }, (t) => {\n  const server = helper.startServer()\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 3,\n    workers: 3,\n    amount: 6,\n    title: 'with-workers'\n  }, function (err, result) {\n    t.error(err)\n\n    t.ok(result.workers === 3, 'correct worker count')\n    t.equal(result.title, 'with-workers', 'title should be what was passed in')\n    t.equal(result.connections, 3, 'connections is the same')\n    t.equal(result.pipelining, 1, 'pipelining is the default')\n\n    t.ok(result.latency, 'latency exists')\n    t.type(result.latency.average, 'number', 'latency.average exists')\n    t.type(result.latency.stddev, 'number', 'latency.stddev exists')\n    t.ok(result.latency.min >= 0, 'latency.min exists')\n    t.type(result.latency.max, 'number', 'latency.max exists')\n    t.type(result.latency.p2_5, 'number', 'latency.p2_5 (2.5%) exists')\n    t.type(result.latency.p50, 'number', 'latency.p50 (50%) exists')\n    t.type(result.latency.p97_5, 'number', 'latency.p97_5 (97.5%) exists')\n    t.type(result.latency.p99, 'number', 'latency.p99 (99%) exists')\n\n    t.ok(result.requests, 'requests exists')\n    t.type(result.requests.average, 'number', 'requests.average exists')\n    t.type(result.requests.stddev, 'number', 'requests.stddev exists')\n    t.type(result.requests.min, 'number', 'requests.min exists')\n    t.type(result.requests.max, 'number', 'requests.max exists')\n    t.ok(result.requests.total === 6, 'requests.total exists')\n    t.type(result.requests.sent, 'number', 'sent exists')\n    t.ok(result.requests.sent >= result.requests.total, 'total requests made should be more than or equal to completed requests total')\n    t.type(result.requests.p1, 'number', 'requests.p1 (1%) exists')\n    t.type(result.requests.p2_5, 'number', 'requests.p2_5 (2.5%) exists')\n    t.type(result.requests.p50, 'number', 'requests.p50 (50%) exists')\n    t.type(result.requests.p97_5, 'number', 'requests.p97_5 (97.5%) exists')\n\n    t.ok(result.throughput, 'throughput exists')\n    t.type(result.throughput.average, 'number', 'throughput.average exists')\n    t.type(result.throughput.stddev, 'number', 'throughput.stddev exists')\n    t.type(result.throughput.min, 'number', 'throughput.min exists')\n    t.type(result.throughput.max, 'number', 'throughput.max exists')\n    t.type(result.throughput.total, 'number', 'throughput.total exists')\n    t.type(result.throughput.p1, 'number', 'throughput.p1 (1%) exists')\n    t.type(result.throughput.p2_5, 'number', 'throughput.p2_5 (2.5%) exists')\n    t.type(result.throughput.p50, 'number', 'throughput.p50 (50%) exists')\n    t.type(result.throughput.p97_5, 'number', 'throughput.p97_5 (97.5%) exists')\n\n    t.ok(result.start, 'start time exists')\n    t.ok(result.finish, 'finish time exists')\n\n    t.equal(result.errors, 0, 'no errors')\n    t.equal(result.mismatches, 0, 'no mismatches')\n    t.equal(result.resets, 0, 'no resets')\n\n    t.equal(result['1xx'], 0, '1xx codes')\n    t.equal(result['2xx'], result.requests.total, '2xx codes')\n    t.equal(result['3xx'], 0, '3xx codes')\n    t.equal(result['4xx'], 0, '4xx codes')\n    t.equal(result['5xx'], 0, '5xx codes')\n    t.equal(result.non2xx, 0, 'non 2xx codes')\n\n    t.end()\n  })\n})\n\ntest('setupRequest and onResponse work with workers', { skip: !hasWorkerSupport }, (t) => {\n  const server = http.createServer((req, res) => {\n    // it's not easy to assert things within setupRequest and onResponse\n    // when in workers mode. So, we set something in onResponse and use in the\n    // next Request and make sure it exist or we return 404.\n    if (req.method === 'GET' && req.url !== '/test-123?some=thing&bar=baz') {\n      res.statusCode = 404\n      res.end('NOT OK')\n      return\n    }\n\n    res.end('OK')\n  })\n  server.listen(0)\n  server.unref()\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    amount: 4,\n    workers: 1,\n    requests: [\n      {\n        method: 'PUT',\n        onResponse: path.join(__dirname, './utils/on-response')\n      },\n      {\n        method: 'GET',\n        setupRequest: path.join(__dirname, './utils/setup-request')\n      }\n    ]\n  }, function (err, result) {\n    t.error(err)\n\n    t.equal(4, result['2xx'], 'should have 4 ok requests')\n    t.equal(0, result['4xx'], 'should not have any 404s')\n    t.end()\n  })\n})\n\ntest('verifyBody work with workers', { skip: !hasWorkerSupport }, (t) => {\n  const server = http.createServer((req, res) => {\n    // it's not easy to assert things within setupRequest and onResponse\n    // when in workers mode. So, we set something in onResponse and use in the\n    // next Request and make sure it exist or we return 404.\n    if (req.method === 'GET' && req.url !== '/test-123?some=thing&bar=baz') {\n      res.statusCode = 404\n      res.end('NOT OK')\n      return\n    }\n\n    res.end('OK')\n  })\n  server.listen(0)\n  server.unref()\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    amount: 4,\n    workers: 1,\n    verifyBody: path.join(__dirname, './utils/verify-body')\n  }, function (err, result) {\n    t.error(err)\n\n    t.equal(4, result.mismatches, 'should have 4 mismatches requests')\n    t.end()\n  })\n})\n\ntest('setupClient works with workers', { skip: !hasWorkerSupport }, (t) => {\n  const server = http.createServer((req, res) => {\n    if (req.headers.custom !== 'my-header') {\n      res.statusCode = 404\n      res.end('NOT OK')\n      return\n    }\n    res.end('OK')\n  })\n  server.listen(0)\n  server.unref()\n\n  initJob({\n    url: 'http://localhost:' + server.address().port,\n    connections: 2,\n    amount: 2,\n    workers: 1,\n    setupClient: path.join(__dirname, './utils/setup-client')\n  }, function (err, result) {\n    t.error(err)\n\n    t.equal(2, result['2xx'], 'should have 2 ok requests')\n    t.equal(0, result['4xx'], 'should not have any 404s')\n    t.end()\n  })\n})\n\ntest('tlsOptions using pfx work as intended in workers', { skip: !hasWorkerSupport }, (t) => {\n  initJob({\n    url: 'https://localhost:' + httpsServer.address().port,\n    connections: 1,\n    amount: 1,\n    workers: 2,\n    tlsOptions: {\n      pfx: fs.readFileSync(path.join(__dirname, '/keystore.pkcs12')),\n      passphrase: 'test'\n    }\n  }, function (err, result) {\n    t.error(err)\n    t.ok(result, 'requests are ok')\n    t.end()\n  })\n})\n"
  }
]