Skip to content

Commit

Permalink
Add expectBody option and related tests (#242)
Browse files Browse the repository at this point in the history
* Add expectBody option and related tests

* Add expectBody warning, error if used with requests

* Add expectBody warning to help output as well
  • Loading branch information
doesdev committed Feb 1, 2020
1 parent 02a9a71 commit c16a0e5
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 3 deletions.
7 changes: 6 additions & 1 deletion README.md
Expand Up @@ -64,7 +64,7 @@ Available options:
-T/--title TITLE
The title to place in the results for identification.
-b/--body BODY
The body of the request.
The body of the request.
Note: This option needs to be used with the '-H/--headers' option in some frameworks
-F/--form FORM
Upload a form (multipart/form-data). The form options can be a JSON string like
Expand Down Expand Up @@ -108,6 +108,9 @@ Available options:
Server name for the SNI (Server Name Indication) TLS extension.
-x/--excludeErrorStats
Exclude error statistics (non 2xx http responses) from the final latency and bytes per second averages. default: false.
-E/--expectBody EXPECTED
Ensure the body matches this value. If enabled, mismatches count towards bailout.
Enabling this option will slow down the load testing.
-v/--version
Print the version number.
-h/--help
Expand Down Expand Up @@ -242,6 +245,7 @@ Start autocannon against the given target.
* `forever`: A `Boolean` which allows you to setup an instance of autocannon that restarts indefinitely after emiting 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`
* `servername`: A `String` identifying the server name for the SNI (Server Name Indication) TLS extension. _OPTIONAL_ default: `undefined`.
* `excludeErrorStats`: A `Boolean` which allows you to disable tracking non 2xx code responses in latency and bytes per second calculations. _OPTIONAL_ default: `false`.
* `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_
* `cb`: The callback which is called on completion of a benchmark. Takes the following params. _OPTIONAL_.
* `err`: If there was an error encountered with the run.
* `results`: The results of the run.
Expand Down Expand Up @@ -310,6 +314,7 @@ The results object emitted by `done` and passed to the `autocannon()` callback h
* `duration`: The amount of time the test took, **in seconds**.
* `errors`: The number of connection errors (including timeouts) that occurred.
* `timeouts`: The number of connection timeouts that occurred.
* `mismatches`: The number of requests with a mismatched body.
* `start`: A Date object representing when the test started.
* `finish`: A Date object representing when the test ended.
* `connections`: The amount of connections used (value of `opts.connections`).
Expand Down
1 change: 1 addition & 0 deletions autocannon.js
Expand Up @@ -58,6 +58,7 @@ function parseArguments (argvs) {
idReplacement: 'I',
socketPath: 'S',
excludeErrorStats: 'x',
expectBody: 'E',
help: 'h'
},
default: {
Expand Down
3 changes: 3 additions & 0 deletions help.txt
Expand Up @@ -69,6 +69,9 @@ Available options:
Server name for the SNI (Server Name Indication) TLS extension.
-x/--excludeErrorStats
Exclude error statistics (non 2xx http responses) from the final latency and bytes per second averages. default: false.
-E/--expectBody EXPECTED
Ensure the body matches this value. If enabled, mismatches count towards bailout.
Enabling this option will slow down the load testing.
--debug
Print connection errors to stderr.
-v/--version
Expand Down
10 changes: 9 additions & 1 deletion lib/httpClient.js
Expand Up @@ -19,6 +19,7 @@ function Client (opts) {
this.opts.setupClient = this.opts.setupClient || noop
this.opts.pipelining = this.opts.pipelining || 1
this.opts.port = this.opts.port || 80
this.opts.expectBody = this.opts.expectBody || null
this.timeout = (this.opts.timeout || 10) * 1000
this.ipc = !!this.opts.socketPath
this.secure = this.opts.protocol === 'https:'
Expand Down Expand Up @@ -85,8 +86,15 @@ function Client (opts) {
this.resData[this.cer].headers = opts
}

this.parser[HTTPParser.kOnBody] = (body) => {
this.parser[HTTPParser.kOnBody] = (body, start, len) => {
this.emit('body', body)

if (this.opts.expectBody) {
const bodyString = '' + body.slice(start, start + len)
if (this.opts.expectBody !== bodyString) {
return this.emit('mismatch', bodyString)
}
}
}

this.parser[HTTPParser.kOnMessageComplete] = () => {
Expand Down
3 changes: 3 additions & 0 deletions lib/progressTracker.js
Expand Up @@ -124,6 +124,9 @@ function track (instance, opts) {
if (result.errors) {
logToStream(`${format(result.errors)} errors (${format(result.timeouts)} timeouts)`)
}
if (result.mismatches) {
logToStream(`${format(result.mismatches)} requests with mismatched body`)
}
})

function logToStream (msg) {
Expand Down
19 changes: 19 additions & 0 deletions lib/run.js
Expand Up @@ -88,6 +88,7 @@ function _run (opts, cb, tracker) {
let bytes = 0
let errors = 0
let timeouts = 0
let mismatches = 0
let totalBytes = 0
let totalRequests = 0
let totalCompletedRequests = 0
Expand Down Expand Up @@ -123,6 +124,7 @@ function _run (opts, cb, tracker) {
url.idReplacement = opts.idReplacement
url.socketPath = opts.socketPath
url.servername = opts.servername
url.expectBody = opts.expectBody

let clients = []
initialiseClients(clients)
Expand Down Expand Up @@ -165,6 +167,7 @@ function _run (opts, cb, tracker) {
throughput: addPercentiles(throughput, histAsObj(throughput, totalBytes)),
errors: errors,
timeouts: timeouts,
mismatches: mismatches,
duration: Math.round((Date.now() - startTime) / 10) / 100,
start: new Date(startTime),
finish: new Date(),
Expand All @@ -190,6 +193,7 @@ function _run (opts, cb, tracker) {
}, opts.duration * 1000)
errors = 0
timeouts = 0
mismatches = 0
totalBytes = 0
totalRequests = 0
totalCompletedRequests = 0
Expand Down Expand Up @@ -229,6 +233,7 @@ function _run (opts, cb, tracker) {
const client = new Client(url)
client.on('response', onResponse)
client.on('connError', onError)
client.on('mismatch', onExpectMismatch)
client.on('timeout', onTimeout)
client.on('request', () => { totalRequests++ })
client.on('done', onDone)
Expand Down Expand Up @@ -259,6 +264,15 @@ function _run (opts, cb, tracker) {
if (opts.bailout && errors >= opts.bailout) stop = true
}

function onExpectMismatch (bpdyStr) {
for (let i = 0; i < opts.pipelining; i++) {
tracker.emit('reqMismatch', bpdyStr)
}

mismatches++
if (opts.bailout && mismatches >= opts.bailout) stop = true
}

// treat a timeout as a special type of error
function onTimeout () {
const error = new Error('request timed out')
Expand Down Expand Up @@ -302,6 +316,11 @@ function _run (opts, cb, tracker) {
return true
}

if (opts.expectBody && opts.requests !== DefaultOptions.requests) {
errorCb(new Error('expectBody cannot be used in conjunction with requests'))
return true
}

if (lessThanOneError(opts.connections, 'connections')) return true
if (lessThanOneError(opts.pipelining, 'pipelining factor')) return true
if (greaterThanZeroError(opts.timeout, 'timeout')) return true
Expand Down
2 changes: 1 addition & 1 deletion test/helper.js
Expand Up @@ -20,7 +20,7 @@ function startServer (opts) {

function handle (req, res) {
res.statusCode = statusCode
res.end('hello world')
res.end(opts.body || 'hello world')
}

server.unref()
Expand Down
71 changes: 71 additions & 0 deletions test/run.test.js
Expand Up @@ -60,6 +60,7 @@ test('run', (t) => {
t.ok(result.finish, 'finish time exists')

t.equal(result.errors, 0, 'no errors')
t.equal(result.mismatches, 0, 'no mismatches')

t.equal(result['1xx'], 0, '1xx codes')
t.equal(result['2xx'], result.requests.total, '2xx codes')
Expand Down Expand Up @@ -123,6 +124,7 @@ test('tracker.stop()', (t) => {
t.ok(result.finish, 'finish time exists')

t.equal(result.errors, 0, 'no errors')
t.equal(result.mismatches, 0, 'no mismatches')

t.equal(result['1xx'], 0, '1xx codes')
t.equal(result['2xx'], result.requests.total, '2xx codes')
Expand Down Expand Up @@ -228,6 +230,20 @@ test('run should callback with an error after a bailout', (t) => {
})
})

test('run should callback with an error using expectBody and requests', (t) => {
t.plan(2)

run({
url: 'http://localhost:' + server.address().port,
requests: [{ body: 'something' }],
expectBody: 'hello'
}, function (err, result) {
t.ok(err, 'expectBody used with requests should cause an error')
t.notOk(result, 'results should not exist')
t.end()
})
})

test('run should allow users to enter timestrings to be used for duration', (t) => {
t.plan(3)

Expand Down Expand Up @@ -261,6 +277,37 @@ test('run should recognise valid urls without http at the start', (t) => {
})
})

test('run should produce count of mismatches with expectBody set', (t) => {
t.plan(2)

run({
url: 'http://localhost:' + server.address().port,
expectBody: 'body will not be this',
maxOverallRequests: 10
}, function (err, result) {
t.error(err)
t.equal(result.mismatches, 10)
t.end()
})
})

test('run should produce 0 mismatches with expectBody set and matches', (t) => {
t.plan(2)

const responseBody = 'hello dave'
const server = helper.startServer({ body: responseBody })

run({
url: 'http://localhost:' + server.address().port,
expectBody: responseBody,
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)

Expand Down Expand Up @@ -439,6 +486,30 @@ test('tracker will emit reqError with error message on error', (t) => {
})
})

test('tracker will emit reqMismatch when body does not match expectBody', (t) => {
t.plan(2)

const responseBody = 'hello world'
const server = helper.startServer({ body: responseBody })

const expectBody = 'goodbye world'

const tracker = run({
url: `http://localhost:${server.address().port}`,
connections: 10,
duration: 15,
method: 'GET',
body: 'hello',
expectBody
})

tracker.once('reqMismatch', (bodyStr) => {
t.equal(bodyStr, responseBody)
t.notEqual(bodyStr, expectBody)
tracker.stop()
})
})

test('tracker will emit tick with current counter value', (t) => {
t.plan(1)

Expand Down

0 comments on commit c16a0e5

Please sign in to comment.