-
-
Notifications
You must be signed in to change notification settings - Fork 332
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of github.com:mcollina/autocannon
- Loading branch information
Showing
12 changed files
with
1,736 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
'use strict' | ||
|
||
// given we support node v8 | ||
// eslint-disable-next-line node/no-deprecated-api | ||
const { parse } = require('url') | ||
|
||
function parseHAR (har) { | ||
const requestsPerOrigin = new Map() | ||
try { | ||
if (!har || typeof har !== 'object' || typeof har.log !== 'object' || !Array.isArray(har.log.entries) || !har.log.entries.length) { | ||
throw new Error('no entries found') | ||
} | ||
let i = 0 | ||
for (const entry of har.log.entries) { | ||
i++ | ||
if (!entry || typeof entry !== 'object' || !entry.request || typeof entry.request !== 'object') { | ||
throw new Error(`invalid request in entry #${i}`) | ||
} | ||
const { request: { method, url, headers: headerArray, postData } } = entry | ||
// turn headers array to headers object | ||
const headers = {} | ||
if (!Array.isArray(headerArray)) { | ||
throw new Error(`invalid headers array in entry #${i}`) | ||
} | ||
let j = 0 | ||
for (const header of headerArray) { | ||
j++ | ||
if (!header || typeof header !== 'object' || typeof header.name !== 'string' || typeof header.value !== 'string') { | ||
throw new Error(`invalid name or value in header #${j} of entry #${i}`) | ||
} | ||
const { name, value } = header | ||
headers[name] = value | ||
} | ||
const { path, hash, host, protocol } = parse(url) | ||
const origin = `${protocol}//${host}` | ||
|
||
let requests = requestsPerOrigin.get(origin) | ||
if (!requests) { | ||
requests = [] | ||
requestsPerOrigin.set(origin, requests) | ||
} | ||
const request = { | ||
origin, | ||
method, | ||
// only keep path & hash as our HttpClient will handle origin | ||
path: `${path}${hash || ''}`, | ||
headers | ||
} | ||
if (typeof postData === 'object' && typeof postData.text === 'string') { | ||
request.body = postData.text | ||
} | ||
requests.push(request) | ||
} | ||
} catch (err) { | ||
throw new Error(`Could not parse HAR content: ${err.message}`) | ||
} | ||
return requestsPerOrigin | ||
} | ||
|
||
exports.parseHAR = parseHAR |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,54 +1,198 @@ | ||
'use strict' | ||
|
||
const t = require('tap') | ||
const test = require('tap').test | ||
const split = require('split2') | ||
const path = require('path') | ||
const fs = require('fs') | ||
const os = require('os') | ||
const childProcess = require('child_process') | ||
const helper = require('./helper') | ||
|
||
const lines = [ | ||
/Running 1s test @ .*$/, | ||
/10 connections.*$/, | ||
/$/, | ||
/.*/, | ||
/Stat.*2\.5%.*50%.*97\.5%.*99%.*Avg.*Stdev.*Max.*$/, | ||
/.*/, | ||
/Latency.*$/, | ||
/$/, | ||
/.*/, | ||
/Stat.*1%.*2\.5%.*50%.*97\.5%.*Avg.*Stdev.*Min.*$/, | ||
/.*/, | ||
/Req\/Sec.*$/, | ||
/.*/, | ||
/Bytes\/Sec.*$/, | ||
/.*/, | ||
/$/, | ||
/Req\/Bytes counts sampled once per second.*$/, | ||
/$/, | ||
/.* requests in ([0-9]|\.)+s, .* read/ | ||
] | ||
|
||
t.plan(lines.length * 2) | ||
|
||
const server = helper.startServer() | ||
const url = 'http://localhost:' + server.address().port | ||
|
||
const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-d', '1', url], { | ||
cwd: __dirname, | ||
env: process.env, | ||
stdio: ['ignore', 'pipe', 'pipe'], | ||
detached: false | ||
test('should run benchmark against server', (t) => { | ||
const lines = [ | ||
/Running 1s test @ .*$/, | ||
/10 connections.*$/, | ||
/$/, | ||
/.*/, | ||
/Stat.*2\.5%.*50%.*97\.5%.*99%.*Avg.*Stdev.*Max.*$/, | ||
/.*/, | ||
/Latency.*$/, | ||
/$/, | ||
/.*/, | ||
/Stat.*1%.*2\.5%.*50%.*97\.5%.*Avg.*Stdev.*Min.*$/, | ||
/.*/, | ||
/Req\/Sec.*$/, | ||
/.*/, | ||
/Bytes\/Sec.*$/, | ||
/.*/, | ||
/$/, | ||
/Req\/Bytes counts sampled once per second.*$/, | ||
/$/, | ||
/.* requests in ([0-9]|\.)+s, .* read/ | ||
] | ||
|
||
t.plan(lines.length * 2) | ||
|
||
const server = helper.startServer() | ||
const url = 'http://localhost:' + server.address().port | ||
|
||
const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-d', '1', url], { | ||
cwd: __dirname, | ||
env: process.env, | ||
stdio: ['ignore', 'pipe', 'pipe'], | ||
detached: false | ||
}) | ||
|
||
t.tearDown(() => { | ||
child.kill() | ||
}) | ||
|
||
child | ||
.stderr | ||
.pipe(split()) | ||
.on('data', (line) => { | ||
const regexp = lines.shift() | ||
t.ok(regexp, 'we are expecting this line') | ||
t.ok(regexp.test(line), 'line matches ' + regexp) | ||
}) | ||
.on('end', t.end) | ||
}) | ||
|
||
t.tearDown(() => { | ||
child.kill() | ||
test('should parse HAR file and run requests', (t) => { | ||
const lines = [ | ||
/Running \d+ requests test @ .*$/, | ||
/1 connections.*$/, | ||
/$/, | ||
/.*/, | ||
/Stat.*2\.5%.*50%.*97\.5%.*99%.*Avg.*Stdev.*Max.*$/, | ||
/.*/, | ||
/Latency.*$/, | ||
/$/, | ||
/.*/, | ||
/Stat.*1%.*2\.5%.*50%.*97\.5%.*Avg.*Stdev.*Min.*$/, | ||
/.*/, | ||
/Req\/Sec.*$/, | ||
/.*/, | ||
/Bytes\/Sec.*$/, | ||
/.*/, | ||
/$/, | ||
/Req\/Bytes counts sampled once per second.*$/, | ||
/$/, | ||
/.* requests in ([0-9]|\.)+s, .* read/ | ||
] | ||
|
||
t.plan(lines.length) | ||
|
||
const server = helper.startServer() | ||
const url = `http://localhost:${server.address().port}` | ||
const harPath = path.join(os.tmpdir(), 'autocannon-test.har') | ||
const har = helper.customizeHAR('./fixtures/httpbin-simple-get.json', 'https://httpbin.org', url) | ||
fs.writeFileSync(harPath, JSON.stringify(har)) | ||
|
||
const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-a', 4, '-c', 1, '--har', harPath, url], { | ||
cwd: __dirname, | ||
env: process.env, | ||
stdio: ['ignore', 'pipe', 'pipe'], | ||
detached: false | ||
}) | ||
|
||
t.tearDown(() => { | ||
child.kill() | ||
}) | ||
|
||
child | ||
.stderr | ||
.pipe(split()) | ||
.on('data', (line) => { | ||
const regexp = lines.shift() | ||
t.ok(regexp.test(line), `"${line}" matches ${regexp}`) | ||
}) | ||
.on('end', t.end) | ||
}) | ||
|
||
child | ||
.stderr | ||
.pipe(split()) | ||
.on('data', (line) => { | ||
const regexp = lines.shift() | ||
t.ok(regexp, 'we are expecting this line') | ||
t.ok(regexp.test(line), 'line matches ' + regexp) | ||
test('should throw on unknown HAR file', (t) => { | ||
t.plan(1) | ||
|
||
const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-a', 4, '-c', 1, '--har', 'does not exist', 'http://localhost'], { | ||
cwd: __dirname, | ||
env: process.env, | ||
stdio: ['ignore', 'pipe', 'pipe'], | ||
detached: false | ||
}) | ||
|
||
t.tearDown(() => { | ||
child.kill() | ||
}) | ||
|
||
const lines = [] | ||
child | ||
.stderr | ||
.pipe(split()) | ||
.on('data', line => lines.push(line)) | ||
.on('end', () => { | ||
const output = lines.join('\n') | ||
t.ok(output.includes('Error: Failed to load HAR file content: ENOENT'), `Unexpected output:\n${output}`) | ||
t.end() | ||
}) | ||
}) | ||
|
||
test('should throw on invalid HAR file', (t) => { | ||
t.plan(1) | ||
|
||
const harPath = path.join(os.tmpdir(), 'autocannon-test.har') | ||
fs.writeFileSync(harPath, 'not valid JSON content') | ||
|
||
const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-a', 4, '-c', 1, '--har', harPath, 'http://localhost'], { | ||
cwd: __dirname, | ||
env: process.env, | ||
stdio: ['ignore', 'pipe', 'pipe'], | ||
detached: false | ||
}) | ||
|
||
t.tearDown(() => { | ||
child.kill() | ||
}) | ||
|
||
const lines = [] | ||
child | ||
.stderr | ||
.pipe(split()) | ||
.on('data', line => lines.push(line)) | ||
.on('end', () => { | ||
const output = lines.join('\n') | ||
t.ok(output.includes('Error: Failed to load HAR file content: Unexpected token'), `Unexpected output:\n${output}`) | ||
t.end() | ||
}) | ||
}) | ||
|
||
test('should write warning about unused HAR requests', (t) => { | ||
t.plan(1) | ||
|
||
const server = helper.startServer() | ||
const url = `http://localhost:${server.address().port}` | ||
const harPath = path.join(os.tmpdir(), 'autocannon-test.har') | ||
const har = helper.customizeHAR('./fixtures/multi-domains.json', 'https://httpbin.org', url) | ||
fs.writeFileSync(harPath, JSON.stringify(har)) | ||
|
||
const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-a', 4, '-c', 1, '--har', harPath, url], { | ||
cwd: __dirname, | ||
env: process.env, | ||
stdio: ['ignore', 'pipe', 'pipe'], | ||
detached: false | ||
}) | ||
|
||
t.tearDown(() => { | ||
child.kill() | ||
}) | ||
|
||
const lines = [] | ||
child | ||
.stderr | ||
.pipe(split()) | ||
.on('data', line => lines.push(line)) | ||
.on('end', () => { | ||
const output = lines.join('\n') | ||
t.ok(output.includes(`Warning: skipping requests to 'https://github.com' as the target is ${url}`), `Unexpected output:\n${output}`) | ||
t.end() | ||
}) | ||
}) |
Oops, something went wrong.