Repository: mcollina/autocannon
Pull request #448 (2022-04-29 07:03:39)

build(deps-dev): bump standard from 16.0.4 to 17.0.0
closes #445 by making fixes standard 17 highlights


Patch:
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 3736650..7ab7bab 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]
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') {