Repository: mcollina/autocannon
Pull request #441 (2022-04-11 19:09:59)

chore(deps): bump actions/setup-node from 3.0.0 to 3.1.1
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3.0.0 to 3.1.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/actions/setup-node/releases">actions/setup-node's releases</a>.</em></p>
<blockquote>
<h2>Update actions/cache version to 2.0.2</h2>
<p>In scope of this release we updated <code>actions/cache</code> package as the new version contains fixes related to GHES 3.5 (<a href="https://github-redirect.dependabot.com/actions/setup-node/pull/460">actions/setup-node#460</a>)</p>
<h2>Add caching support on GHES 3.5</h2>
<p>In scope of this release we added <a href="https://github-redirect.dependabot.com/actions/setup-node/pull/452">support for caching from GHES 3.5</a> and fixed download issue for files &gt; 2GB during restore. Besides, we updated <code>actions/cache</code> dependency to 2.0.0 version.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/actions/setup-node/commit/56337c425554a6be30cdef71bf441f15be286854"><code>56337c4</code></a> Updated <code>@​actions/cache</code> (<a href="https://github-redirect.dependabot.com/actions/setup-node/issues/460">#460</a>)</li>
<li><a href="https://github.com/actions/setup-node/commit/76d3665dc084409ba9e6c467009338cf86e9dd5f"><code>76d3665</code></a> Convert CONDUCT to CODE_OF_CONDUCT.md (<a href="https://github-redirect.dependabot.com/actions/setup-node/issues/391">#391</a>)</li>
<li><a href="https://github.com/actions/setup-node/commit/5b52f097d36d4b0b2f94ed6de710023fbb8b2236"><code>5b52f09</code></a> Bump <code>actions/checkout@v3</code> within advanced usage examples documentation (<a href="https://github-redirect.dependabot.com/actions/setup-node/issues/456">#456</a>)</li>
<li><a href="https://github.com/actions/setup-node/commit/75716637dc8cd06e88621003df6357fdcee67772"><code>7571663</code></a> Bump to <code>actions/checkout@v3</code> (<a href="https://github-redirect.dependabot.com/actions/setup-node/issues/437">#437</a>)</li>
<li><a href="https://github.com/actions/setup-node/commit/546fd45825c7f5d63e19e4595c08c16d66bcbad9"><code>546fd45</code></a> Updated documentation/workflows to use <code>actions/setup-node@v3</code> (<a href="https://github-redirect.dependabot.com/actions/setup-node/issues/433">#433</a>)</li>
<li><a href="https://github.com/actions/setup-node/commit/bacd6b4b3ac3127b28a1e1920c23bf1c2cadbb85"><code>bacd6b4</code></a> Caching on GHES (<a href="https://github-redirect.dependabot.com/actions/setup-node/issues/452">#452</a>)</li>
<li><a href="https://github.com/actions/setup-node/commit/bed538bd04a773f1616efba082915a7638d8eb5f"><code>bed538b</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/actions/setup-node/issues/448">#448</a> from actions/dependabot/npm_and_yarn/minimist-1.2.6</li>
<li><a href="https://github.com/actions/setup-node/commit/f3e93d1b9dbf0ec44d17b09dea3d0446dd7d25d7"><code>f3e93d1</code></a> Bump minimist from 1.2.5 to 1.2.6</li>
<li><a href="https://github.com/actions/setup-node/commit/ed960c9656f8a2da28b0133d2bba0c7b6f2728a1"><code>ed960c9</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/actions/setup-node/issues/440">#440</a> from actions/joshmgross/service-codeowners</li>
<li><a href="https://github.com/actions/setup-node/commit/77f43a440004389e5ec966078985f6a3153df1da"><code>77f43a4</code></a> Update CODEOWNERS to actions-service</li>
<li>See full diff in <a href="https://github.com/actions/setup-node/compare/v3.0.0...v3.1.1">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/setup-node&package-manager=github_actions&previous-version=3.0.0&new-version=3.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 3736650..bdfd589 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -5,6 +5,8 @@ on: [ push, pull_request ]
 jobs:
   test:
     runs-on: ${{ matrix.os }}
+    permissions:
+      contents: read
     strategy:
       matrix:
         os: [ubuntu-latest, windows-latest, macos-latest]
@@ -13,7 +15,7 @@ jobs:
     steps:
     - uses: actions/checkout@v3
     - name: Use Node.js ${{ matrix.node-version }}
-      uses: actions/setup-node@v3.0.0
+      uses: actions/setup-node@v3.1.1
       with:
         node-version: ${{ matrix.node-version }}
     - run: node --version
diff --git a/.gitignore b/.gitignore
index 933e16f..4727591 100644
--- a/.gitignore
+++ b/.gitignore
@@ -134,4 +134,5 @@ build/Release
 
 profile-*
 
-.devcontainer
\ No newline at end of file
+.devcontainer
+.history
\ No newline at end of file
diff --git a/README.md b/README.md
index f666d78..44d6ec5 100644
--- a/README.md
+++ b/README.md
@@ -263,6 +263,7 @@ autocannon({
   // ...
   workers: 4,
   setupClient: '/full/path/to/setup-client.js',
+  verifyBody: '/full/path/to/verify-body.js'
   requests: [
     {
       // ...
@@ -299,6 +300,7 @@ Start autocannon against the given target.
     * `headers`: An `Object` containing the headers of the request. _OPTIONAL_ default: `{}`.
     * `initialContext`: An object that you'd like to initialize your context with. Check out [an example of initializing context](./samples/init-context.js). _OPTIONAL_
     * `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).
+    * `verifyBody`: A `Function` which will be passed the `Client` object for each connection to be made. This can be used to customise the validation rules for response body. This function will take precedence over the `expectBody` you pass in here. 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 instead (Check out the [workers](#workers) section for more details).
     * `maxConnectionRequests`: A `Number` stating the max requests to make per connection. `amount` takes precedence if both are set. _OPTIONAL_
     * `maxOverallRequests`: A `Number` stating the max requests to make overall. Can't be less than `connections`. `maxConnectionRequests` takes precedence if both are set. _OPTIONAL_
     * `connectionRate`: A `Number` stating the rate of requests to make per second from each individual connection. No rate limiting by default. _OPTIONAL_
diff --git a/autocannon.js b/autocannon.js
index f4ddd3a..1f1bcfe 100755
--- a/autocannon.js
+++ b/autocannon.js
@@ -246,7 +246,7 @@ function start (argv) {
       const url = new URL(argv.url, `http://localhost:${port}`).href
       const opts = Object.assign({}, argv, {
         onPort: false,
-        url: url
+        url
       })
       const tracker = initJob(opts, () => {
         proc.kill('SIGINT')
diff --git a/lib/httpClient.js b/lib/httpClient.js
index 95dc77e..9ce4a81 100644
--- a/lib/httpClient.js
+++ b/lib/httpClient.js
@@ -91,15 +91,18 @@ function Client (opts) {
       return this._resetConnection()
     }
     if (!this.destroyed) {
-      if (this.opts.expectBody && this.opts.expectBody !== resp.body) {
-        return this.emit('mismatch', resp.body)
-      }
-
       this.requestIterator.recordBody(resp.req, resp.headers.statusCode, resp.body, resp.headers.headers)
 
       this.emit('response', resp.headers.statusCode, resp.bytes, resp.duration, this.rate)
 
       this._doRequest()
+
+      const isFn = typeof this.opts.verifyBody === 'function'
+      if (isFn && !this.opts.verifyBody(resp.body)) {
+        return this.emit('mismatch', resp.body)
+      } else if (!isFn && this.opts.expectBody && this.opts.expectBody !== resp.body) {
+        return this.emit('mismatch', resp.body)
+      }
     }
   }
 
diff --git a/lib/parseHAR.js b/lib/parseHAR.js
index 774edcf..265600b 100644
--- a/lib/parseHAR.js
+++ b/lib/parseHAR.js
@@ -1,7 +1,7 @@
 'use strict'
 
 // given we support node v8
-// eslint-disable-next-line node/no-deprecated-api
+// eslint-disable-next-line n/no-deprecated-api
 const { parse } = require('url')
 
 function parseHAR (har) {
diff --git a/lib/run.js b/lib/run.js
index 7e0ea2f..7111b7d 100644
--- a/lib/run.js
+++ b/lib/run.js
@@ -56,7 +56,7 @@ function run (opts, tracker, cb) {
 
   const urls = ofURL(opts.url, true).map(url => {
     if (url.indexOf('http') !== 0) url = 'http://' + url
-    url = URL.parse(url) // eslint-disable-line node/no-deprecated-api
+    url = URL.parse(url) // eslint-disable-line n/no-deprecated-api
 
     // copy over fields so that the client
     // performs the right HTTP requests
@@ -65,6 +65,7 @@ function run (opts, tracker, cb) {
     url.body = opts.form ? opts.form.getBuffer() : opts.body
     url.headers = opts.form ? Object.assign({}, opts.headers, opts.form.getHeaders()) : opts.headers
     url.setupClient = opts.setupClient
+    url.verifyBody = opts.verifyBody
     url.timeout = opts.timeout
     url.origin = `${url.protocol}//${url.host}`
     // only keep requests for that origin, or default to requests from options
@@ -123,12 +124,12 @@ function run (opts, tracker, cb) {
         totalRequests,
         totalBytes,
         samples,
-        errors: errors,
-        timeouts: timeouts,
-        mismatches: mismatches,
+        errors,
+        timeouts,
+        mismatches,
         non2xx: statusCodes[0] + statusCodes[2] + statusCodes[3] + statusCodes[4],
         statusCodeStats,
-        resets: resets,
+        resets,
         duration: Math.round((Date.now() - startTime) / 10) / 100,
         start: new Date(startTime),
         finish: new Date()
diff --git a/lib/validate.js b/lib/validate.js
index 642f728..93ac5ae 100644
--- a/lib/validate.js
+++ b/lib/validate.js
@@ -25,24 +25,26 @@ function safeRequire (path) {
 
 function defaultOpts (opts) {
   const setupClient = opts.workers ? opts.setupClient : safeRequire(opts.setupClient)
+  const verifyBody = opts.workers ? opts.verifyBody : safeRequire(opts.verifyBody)
 
   const requests = opts.requests
     ? opts.requests.map((r) => {
-        const setupRequest = opts.workers ? r.setupRequest : safeRequire(r.setupRequest)
-        const onResponse = opts.workers ? r.onResponse : safeRequire(r.onResponse)
-
-        return {
-          ...r,
-          ...(setupRequest ? { setupRequest } : undefined),
-          ...(onResponse ? { onResponse } : undefined)
-        }
-      })
+      const setupRequest = opts.workers ? r.setupRequest : safeRequire(r.setupRequest)
+      const onResponse = opts.workers ? r.onResponse : safeRequire(r.onResponse)
+
+      return {
+        ...r,
+        ...(setupRequest ? { setupRequest } : undefined),
+        ...(onResponse ? { onResponse } : undefined)
+      }
+    })
     : undefined
 
   return {
     ...defaultOptions,
     ...opts,
     ...(setupClient ? { setupClient } : undefined),
+    ...(verifyBody ? { verifyBody } : undefined),
     ...(requests ? { requests } : undefined)
   }
 }
@@ -78,6 +80,10 @@ module.exports = function validateOpts (opts, cbPassedIn) {
     return new Error('Invalid option setupClient, please provide a function (or file path when in workers mode)')
   }
 
+  if (!isValidFn(opts.verifyBody)) {
+    return new Error('Invalid option verifyBody, please provide a function (or file path when in workers mode)')
+  }
+
   if (!checkURL(opts.url) && !opts.socketPath) {
     return new Error('url or socketPath option required')
   }
diff --git a/lib/worker.js b/lib/worker.js
index 9c8953c..4f1b09b 100644
--- a/lib/worker.js
+++ b/lib/worker.js
@@ -30,12 +30,13 @@ function runTracker (opts, cb) {
     ...opts,
     ...(opts.form ? { form: multipart(opts.form) } : undefined),
     ...(opts.setupClient ? { setupClient: require(opts.setupClient) } : undefined),
+    ...(opts.verifyBody ? { verifyBody: require(opts.verifyBody) } : undefined),
     requests: opts.requests
       ? opts.requests.map(r => ({
-          ...r,
-          ...(r.setupRequest ? { setupRequest: require(r.setupRequest) } : undefined),
-          ...(r.onResponse ? { onResponse: require(r.onResponse) } : undefined)
-        }))
+        ...r,
+        ...(r.setupRequest ? { setupRequest: require(r.setupRequest) } : undefined),
+        ...(r.onResponse ? { onResponse: require(r.onResponse) } : undefined)
+      }))
       : undefined,
     histograms: {
       requests: createHist('requests'),
diff --git a/package.json b/package.json
index c8b0d8d..a6978da 100644
--- a/package.json
+++ b/package.json
@@ -44,7 +44,7 @@
     "proxyquire": "^2.1.3",
     "sinon": "^13.0.0",
     "split2": "^4.0.0",
-    "standard": "^16.0.3",
+    "standard": "^17.0.0",
     "tap": "^16.0.0"
   },
   "dependencies": {
diff --git a/samples/bench-multi-url.js b/samples/bench-multi-url.js
index 1bd1afb..fbc5953 100644
--- a/samples/bench-multi-url.js
+++ b/samples/bench-multi-url.js
@@ -25,7 +25,7 @@ function startBench () {
   // same with run the follow command in cli
   // autocannon -d 10 -c 2 http://localhost:xxxx http://localhost:yyyy
   autocannon({
-    url: url,
+    url,
     // connection number should n times of the number of server
     connections: 2,
     duration: 10,
diff --git a/samples/customise-individual-connection.js b/samples/customise-individual-connection.js
index 8b9722f..b191719 100644
--- a/samples/customise-individual-connection.js
+++ b/samples/customise-individual-connection.js
@@ -15,10 +15,10 @@ function startBench () {
   const url = 'http://localhost:' + server.address().port
 
   autocannon({
-    url: url,
+    url,
     connections: 1000,
     duration: 10,
-    setupClient: setupClient
+    setupClient
   }, finishedBench)
 
   let connection = 0
diff --git a/samples/customise-verifyBody-workers.js b/samples/customise-verifyBody-workers.js
new file mode 100644
index 0000000..6d76ed3
--- /dev/null
+++ b/samples/customise-verifyBody-workers.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const http = require('http')
+const path = require('path')
+const autocannon = require('../autocannon')
+
+const server = http.createServer(handle)
+
+server.listen(0, startBench)
+
+function handle (req, res) {
+  res.end('hello world')
+}
+
+function startBench () {
+  const url = 'http://localhost:' + server.address().port
+
+  autocannon({
+    url: url,
+    connections: 1000,
+    duration: 10,
+    workers: 2,
+    verifyBody: path.join(__dirname, 'helpers', 'verify-body')
+  }, finishedBench)
+
+  function finishedBench (err, res) {
+    console.log('finished bench', err, res)
+  }
+}
diff --git a/samples/customise-verifyBody.js b/samples/customise-verifyBody.js
new file mode 100644
index 0000000..d5c5230
--- /dev/null
+++ b/samples/customise-verifyBody.js
@@ -0,0 +1,31 @@
+'use strict'
+
+const http = require('http')
+const autocannon = require('../autocannon')
+
+const server = http.createServer(handle)
+
+server.listen(0, startBench)
+
+function handle (req, res) {
+  res.end('hello world')
+}
+
+function startBench () {
+  const url = 'http://localhost:' + server.address().port
+
+  autocannon({
+    url: url,
+    connections: 1000,
+    duration: 10,
+    verifyBody: verifyBody
+  }, finishedBench)
+
+  function verifyBody (body) {
+    return body.indexOf('<html>') > -1
+  }
+
+  function finishedBench (err, res) {
+    console.log('finished bench', err, res)
+  }
+}
diff --git a/samples/helpers/verify-body.js b/samples/helpers/verify-body.js
new file mode 100644
index 0000000..ae9eab8
--- /dev/null
+++ b/samples/helpers/verify-body.js
@@ -0,0 +1,5 @@
+'use strict'
+
+module.exports = (body) => {
+  return true
+}
diff --git a/samples/init-context.js b/samples/init-context.js
index b1c27ca..a4807dc 100644
--- a/samples/init-context.js
+++ b/samples/init-context.js
@@ -19,7 +19,7 @@ function startBench () {
   const url = 'http://localhost:' + server.address().port
 
   autocannon({
-    url: url,
+    url,
     connections: 1,
     amount: 1,
     initialContext: { user: { firstName: 'Salman' } },
diff --git a/samples/modifying-request.js b/samples/modifying-request.js
index 7495082..974217e 100644
--- a/samples/modifying-request.js
+++ b/samples/modifying-request.js
@@ -15,7 +15,7 @@ function startBench () {
   const url = 'http://localhost:' + server.address().port
 
   const instance = autocannon({
-    url: url,
+    url,
     connections: 1000,
     duration: 10
   }, finishedBench)
diff --git a/samples/request-context-workers.js b/samples/request-context-workers.js
index 7dea271..e842c5e 100644
--- a/samples/request-context-workers.js
+++ b/samples/request-context-workers.js
@@ -20,7 +20,7 @@ function startBench () {
   const url = 'http://localhost:' + server.address().port
 
   autocannon({
-    url: url,
+    url,
     duration: 2,
     workers: 2,
     requests: [
diff --git a/samples/request-context.js b/samples/request-context.js
index fab7d74..88b58f2 100644
--- a/samples/request-context.js
+++ b/samples/request-context.js
@@ -19,7 +19,7 @@ function startBench () {
   const url = 'http://localhost:' + server.address().port
 
   autocannon({
-    url: url,
+    url,
     requests: [
       {
         // let's create a new user
diff --git a/samples/requests-sample.js b/samples/requests-sample.js
index 190981a..6d4bc17 100644
--- a/samples/requests-sample.js
+++ b/samples/requests-sample.js
@@ -15,7 +15,7 @@ function startBench () {
   const url = 'http://localhost:' + server.address().port
 
   autocannon({
-    url: url,
+    url,
     connections: 1000,
     duration: 10,
     headers: {
diff --git a/samples/using-id-replacement.js b/samples/using-id-replacement.js
index e92fcfe..c7f8c9e 100644
--- a/samples/using-id-replacement.js
+++ b/samples/using-id-replacement.js
@@ -15,7 +15,7 @@ function startBench () {
   const url = 'http://localhost:' + server.address().port
 
   autocannon({
-    url: url,
+    url,
     connections: 1000,
     duration: 10,
     requests: [


Test patch:
diff --git a/test/httpRequestBuilder.test.js b/test/httpRequestBuilder.test.js
index 76245fd..f030e59 100644
--- a/test/httpRequestBuilder.test.js
+++ b/test/httpRequestBuilder.test.js
@@ -65,7 +65,7 @@ test('request builder should accept all valid standard http methods', (t) => {
 
     const build = RequestBuilder(opts)
 
-    t.doesNotThrow(() => build({ method: method }), `${method} should be usable by the request builded`)
+    t.doesNotThrow(() => build({ method }), `${method} should be usable by the request builded`)
   })
   t.end()
 })
diff --git a/test/run.test.js b/test/run.test.js
index ad33d8f..8154d45 100644
--- a/test/run.test.js
+++ b/test/run.test.js
@@ -327,6 +327,42 @@ test('run should produce 0 mismatches with expectBody set and matches', (t) => {
   })
 })
 
+test('run should produce count of mismatches with verifyBody set', (t) => {
+  t.plan(2)
+
+  initJob({
+    url: 'http://localhost:' + server.address().port,
+    verifyBody: function () {
+      return false
+    },
+    maxOverallRequests: 10,
+    timeout: 100
+  }, function (err, result) {
+    t.error(err)
+    t.equal(result.mismatches, 10)
+    t.end()
+  })
+})
+
+test('run should produce 0 mismatches with verifyBody set and return true', (t) => {
+  t.plan(2)
+
+  const responseBody = 'hello dave'
+  const server = helper.startServer({ body: responseBody })
+
+  initJob({
+    url: 'http://localhost:' + server.address().port,
+    verifyBody: function (body) {
+      return body.indexOf('hello') > -1
+    },
+    maxOverallRequests: 10
+  }, function (err, result) {
+    t.error(err)
+    t.equal(result.mismatches, 0)
+    t.end()
+  })
+})
+
 test('run should accept a unix socket/windows pipe', (t) => {
   t.plan(11)
 
diff --git a/test/utils/verify-body.js b/test/utils/verify-body.js
new file mode 100644
index 0000000..e1cdc8f
--- /dev/null
+++ b/test/utils/verify-body.js
@@ -0,0 +1,3 @@
+module.exports = (body) => {
+  return false
+}
diff --git a/test/workers.test.js b/test/workers.test.js
index 8f0671b..6fcdec3 100644
--- a/test/workers.test.js
+++ b/test/workers.test.js
@@ -136,6 +136,36 @@ test('setupRequest and onResponse work with workers', { skip: !hasWorkerSupport
   })
 })
 
+test('verifyBody work with workers', { skip: !hasWorkerSupport }, (t) => {
+  const server = http.createServer((req, res) => {
+    // it's not easy to assert things within setupRequest and onResponse
+    // when in workers mode. So, we set something in onResponse and use in the
+    // next Request and make sure it exist or we return 404.
+    if (req.method === 'GET' && req.url !== '/test-123?some=thing&bar=baz') {
+      res.statusCode = 404
+      res.end('NOT OK')
+      return
+    }
+
+    res.end('OK')
+  })
+  server.listen(0)
+  server.unref()
+
+  initJob({
+    url: 'http://localhost:' + server.address().port,
+    connections: 2,
+    amount: 4,
+    workers: 1,
+    verifyBody: path.join(__dirname, './utils/verify-body')
+  }, function (err, result) {
+    t.error(err)
+
+    t.equal(4, result.mismatches, 'should have 4 mismatches requests')
+    t.end()
+  })
+})
+
 test('setupClient works with workers', { skip: !hasWorkerSupport }, (t) => {
   const server = http.createServer((req, res) => {
     if (req.headers.custom !== 'my-header') {