Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add expectBody option and related tests #242

Merged
merged 3 commits into from Feb 1, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 5 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,8 @@ 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.
mcollina marked this conversation as resolved.
Show resolved Hide resolved
-v/--version
Print the version number.
-h/--help
Expand Down Expand Up @@ -242,6 +244,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 +313,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
2 changes: 2 additions & 0 deletions help.txt
Expand Up @@ -69,6 +69,8 @@ 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.
--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
14 changes: 14 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
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
57 changes: 57 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 errors')

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 errors')

t.equal(result['1xx'], 0, '1xx codes')
t.equal(result['2xx'], result.requests.total, '2xx codes')
Expand Down Expand Up @@ -261,6 +263,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 +472,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