loadtest 6.0.0 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -64
- package/bin/loadtest.js +3 -2
- package/lib/httpClient.js +21 -10
- package/lib/latency.js +18 -65
- package/lib/loadtest.js +44 -29
- package/lib/options.js +52 -100
- package/lib/result.js +68 -0
- package/lib/testserver.js +28 -23
- package/package.json +2 -2
- package/sample/request-generator.js +2 -2
- package/sample/request-generator.ts +2 -2
- package/test/body-generator.js +3 -2
- package/test/integration.js +40 -12
- package/test/latency.js +1 -1
- package/test/loadtest.js +43 -12
- package/test/request-generator.js +3 -2
- package/test/testserver.js +1 -0
package/lib/options.js
CHANGED
|
@@ -1,25 +1,18 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
1
|
import {readFile} from 'fs/promises'
|
|
4
2
|
import * as path from 'path'
|
|
5
3
|
import * as urlLib from 'url'
|
|
6
4
|
import {addHeaders} from '../lib/headers.js'
|
|
7
5
|
import {loadConfig} from '../lib/config.js'
|
|
8
6
|
|
|
7
|
+
const acceptedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'get', 'post', 'put', 'delete', 'patch'];
|
|
9
8
|
|
|
10
|
-
export function processOptions(options, callback) {
|
|
11
|
-
processOptionsAsync(options).then(result => callback(null, result)).catch(error => callback(error))
|
|
12
|
-
}
|
|
13
9
|
|
|
14
|
-
async function
|
|
10
|
+
export async function processOptions(options) {
|
|
11
|
+
const processed = {}
|
|
15
12
|
const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url)))
|
|
16
13
|
if (!options.url) {
|
|
17
14
|
throw new Error('Missing URL in options')
|
|
18
15
|
}
|
|
19
|
-
options.concurrency = options.concurrency || 1;
|
|
20
|
-
if (options.requestsPerSecond) {
|
|
21
|
-
options.requestsPerSecond = options.requestsPerSecond / options.concurrency;
|
|
22
|
-
}
|
|
23
16
|
if (!options.url.startsWith('http://') && !options.url.startsWith('https://') && !options.url.startsWith('ws://')) {
|
|
24
17
|
throw new Error(`Invalid URL ${options.url}, must be http://, https:// or ws://'`)
|
|
25
18
|
}
|
|
@@ -28,129 +21,88 @@ async function processOptionsAsync(options) {
|
|
|
28
21
|
throw new Error(`"requestsPerSecond" not supported for WebSockets`);
|
|
29
22
|
}
|
|
30
23
|
}
|
|
24
|
+
processed.url = options.url
|
|
31
25
|
const configuration = loadConfig();
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
processed.concurrency = options.concurrency || configuration.concurrency || 1
|
|
27
|
+
const rps = options.rps ? parseFloat(options.rps) : null
|
|
28
|
+
processed.requestsPerSecond = options.requestsPerSecond || rps || configuration.requestsPerSecond
|
|
29
|
+
processed.agentKeepAlive = options.keepalive || options.agent || options.agentKeepAlive || configuration.agentKeepAlive;
|
|
30
|
+
processed.indexParam = options.index || options.indexParam || configuration.indexParam;
|
|
31
|
+
processed.method = options.method || configuration.method || 'GET'
|
|
37
32
|
// Allow a post body string in options
|
|
38
33
|
// Ex -P '{"foo": "bar"}'
|
|
39
34
|
if (options.postBody) {
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
processed.method = 'POST';
|
|
36
|
+
processed.body = options.postBody;
|
|
42
37
|
}
|
|
43
38
|
if (options.postFile) {
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
processed.method = 'POST';
|
|
40
|
+
processed.body = await readBody(options.postFile, '-p');
|
|
46
41
|
}
|
|
47
42
|
if (options.data) {
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
if (options.method) {
|
|
51
|
-
const acceptedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'get', 'post', 'put', 'delete', 'patch'];
|
|
52
|
-
if (acceptedMethods.indexOf(options.method) === -1) {
|
|
53
|
-
options.method = 'GET';
|
|
54
|
-
}
|
|
43
|
+
processed.body = options.data
|
|
55
44
|
}
|
|
56
45
|
if (options.putFile) {
|
|
57
|
-
|
|
58
|
-
|
|
46
|
+
processed.method = 'PUT';
|
|
47
|
+
processed.body = await readBody(options.putFile, '-u');
|
|
59
48
|
}
|
|
60
49
|
if (options.patchBody) {
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
processed.method = 'PATCH';
|
|
51
|
+
processed.body = options.patchBody;
|
|
63
52
|
}
|
|
64
53
|
if (options.patchFile) {
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
processed.method = 'PATCH';
|
|
55
|
+
processed.body = await readBody(options.patchFile, '-a');
|
|
67
56
|
}
|
|
68
|
-
|
|
69
|
-
|
|
57
|
+
// sanity check
|
|
58
|
+
if (acceptedMethods.indexOf(processed.method) === -1) {
|
|
59
|
+
throw new Error(`Invalid method ${processed.method}`)
|
|
70
60
|
}
|
|
71
61
|
if (!options.body) {
|
|
72
62
|
if(configuration.body) {
|
|
73
|
-
|
|
63
|
+
processed.body = configuration.body;
|
|
74
64
|
} else if (configuration.file) {
|
|
75
|
-
|
|
65
|
+
processed.body = await readBody(configuration.file, 'configuration.request.file');
|
|
76
66
|
}
|
|
77
67
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
options.key = configuration.key;
|
|
68
|
+
if (options.key || configuration.key) {
|
|
69
|
+
processed.key = await readFile(options.key || configuration.key)
|
|
81
70
|
}
|
|
82
|
-
if (options.
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
if (!options.cert) {
|
|
86
|
-
options.cert = configuration.cert;
|
|
87
|
-
}
|
|
88
|
-
if (options.cert) {
|
|
89
|
-
options.cert = await readFile(options.cert);
|
|
71
|
+
if (options.cert || configuration.cert) {
|
|
72
|
+
processed.cert = await readFile(options.cert || configuration.cert);
|
|
90
73
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
defaultHeaders['accept'] = '*/*';
|
|
74
|
+
processed.headers = configuration.headers || {}
|
|
75
|
+
processed.headers['host'] = urlLib.parse(options.url).host;
|
|
76
|
+
processed.headers['user-agent'] = 'loadtest/' + packageJson.version;
|
|
77
|
+
processed.headers['accept'] = '*/*';
|
|
96
78
|
|
|
97
79
|
if (options.headers) {
|
|
98
|
-
addHeaders(options.headers,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
options.concurrency = configuration.concurrency;
|
|
116
|
-
}
|
|
117
|
-
if(!options.maxSeconds) {
|
|
118
|
-
options.maxSeconds = configuration.maxSeconds;
|
|
119
|
-
}
|
|
120
|
-
if(!options.timeout && configuration.timeout) {
|
|
121
|
-
options.timeout = configuration.timeout;
|
|
122
|
-
}
|
|
123
|
-
if(!options.contentType) {
|
|
124
|
-
options.contentType = configuration.contentType;
|
|
125
|
-
}
|
|
126
|
-
if(!options.cookies) {
|
|
127
|
-
options.cookies = configuration.cookies;
|
|
128
|
-
}
|
|
129
|
-
if(!options.secureProtocol) {
|
|
130
|
-
options.secureProtocol = configuration.secureProtocol;
|
|
131
|
-
}
|
|
132
|
-
if(!options.insecure) {
|
|
133
|
-
options.insecure = configuration.insecure;
|
|
134
|
-
}
|
|
135
|
-
if(!options.recover) {
|
|
136
|
-
options.recover = configuration.recover;
|
|
137
|
-
}
|
|
138
|
-
if(!options.proxy) {
|
|
139
|
-
options.proxy = configuration.proxy;
|
|
140
|
-
}
|
|
80
|
+
addHeaders(options.headers, processed.headers);
|
|
81
|
+
}
|
|
82
|
+
processed.requestGenerator = options.requestGenerator || configuration.requestGenerator
|
|
83
|
+
if (typeof processed.requestGenerator == 'string') {
|
|
84
|
+
processed.requestGenerator = await import(processed.requestGenerator)
|
|
85
|
+
}
|
|
86
|
+
processed.maxRequests = options.maxRequests || configuration.maxRequests
|
|
87
|
+
processed.maxSeconds = options.maxSeconds || configuration.maxSeconds
|
|
88
|
+
processed.cookies = options.cookies || configuration.cookies
|
|
89
|
+
processed.contentType = options.contentType || configuration.contentType
|
|
90
|
+
processed.timeout = options.timeout || configuration.timeout
|
|
91
|
+
processed.secureProtocol = options.secureProtocol || configuration.secureProtocol
|
|
92
|
+
processed.insecure = options.insecure || configuration.insecure
|
|
93
|
+
processed.recover = options.recover || configuration.recover
|
|
94
|
+
processed.proxy = options.proxy || configuration.proxy
|
|
95
|
+
processed.quiet = options.quiet || configuration.quiet
|
|
96
|
+
return processed
|
|
141
97
|
}
|
|
142
98
|
|
|
143
99
|
async function readBody(filename, option) {
|
|
144
100
|
if (typeof filename !== 'string') {
|
|
145
101
|
throw new Error(`Invalid file to open with ${option}: ${filename}`);
|
|
146
102
|
}
|
|
147
|
-
|
|
148
103
|
if (path.extname(filename) === '.js') {
|
|
149
104
|
return await import(new URL(filename, `file://${process.cwd()}/`))
|
|
150
105
|
}
|
|
151
|
-
|
|
152
|
-
const ret = await readFile(filename, {encoding: 'utf8'}).replace("\n", "");
|
|
153
|
-
|
|
154
|
-
return ret;
|
|
106
|
+
return await readFile(filename, {encoding: 'utf8'}).replace("\n", "");
|
|
155
107
|
}
|
|
156
108
|
|
package/lib/result.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result of a load test.
|
|
5
|
+
*/
|
|
6
|
+
export class Result {
|
|
7
|
+
constructor(options, latency) {
|
|
8
|
+
// options
|
|
9
|
+
this.url = options.url
|
|
10
|
+
this.maxRequests = options.maxRequests
|
|
11
|
+
this.maxSeconds = options.maxSeconds
|
|
12
|
+
this.concurrency = options.concurrency
|
|
13
|
+
this.agent = options.agentKeepAlive ? 'keepalive' : 'none';
|
|
14
|
+
this.requestsPerSecond = options.requestsPerSecond
|
|
15
|
+
// results
|
|
16
|
+
this.elapsedSeconds = latency.getElapsed(latency.initialTime) / 1000
|
|
17
|
+
const meanTime = latency.totalTime / latency.totalRequests
|
|
18
|
+
this.totalRequests = latency.totalRequests
|
|
19
|
+
this.totalErrors = latency.totalErrors
|
|
20
|
+
this.totalTimeSeconds = this.elapsedSeconds
|
|
21
|
+
this.rps = Math.round(latency.totalRequests / this.elapsedSeconds)
|
|
22
|
+
this.meanLatencyMs = Math.round(meanTime * 10) / 10
|
|
23
|
+
this.maxLatencyMs = latency.maxLatencyMs
|
|
24
|
+
this.minLatencyMs = latency.minLatencyMs
|
|
25
|
+
this.percentiles = latency.computePercentiles()
|
|
26
|
+
this.errorCodes = latency.errorCodes
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Show result of a load test.
|
|
31
|
+
*/
|
|
32
|
+
show() {
|
|
33
|
+
console.info('');
|
|
34
|
+
console.info('Target URL: %s', this.url);
|
|
35
|
+
if (this.maxRequests) {
|
|
36
|
+
console.info('Max requests: %s', this.maxRequests);
|
|
37
|
+
} else if (this.maxSeconds) {
|
|
38
|
+
console.info('Max time (s): %s', this.maxSeconds);
|
|
39
|
+
}
|
|
40
|
+
console.info('Concurrency level: %s', this.concurrency);
|
|
41
|
+
console.info('Agent: %s', this.agent);
|
|
42
|
+
if (this.requestsPerSecond) {
|
|
43
|
+
console.info('Requests per second: %s', this.requestsPerSecond);
|
|
44
|
+
}
|
|
45
|
+
console.info('');
|
|
46
|
+
console.info('Completed requests: %s', this.totalRequests);
|
|
47
|
+
console.info('Total errors: %s', this.totalErrors);
|
|
48
|
+
console.info('Total time: %s s', this.totalTimeSeconds);
|
|
49
|
+
console.info('Requests per second: %s', this.rps);
|
|
50
|
+
console.info('Mean latency: %s ms', this.meanLatencyMs);
|
|
51
|
+
console.info('');
|
|
52
|
+
console.info('Percentage of the requests served within a certain time');
|
|
53
|
+
|
|
54
|
+
Object.keys(this.percentiles).forEach(percentile => {
|
|
55
|
+
console.info(' %s% %s ms', percentile, this.percentiles[percentile]);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.info(' 100% %s ms (longest request)', this.maxLatencyMs);
|
|
59
|
+
if (this.totalErrors) {
|
|
60
|
+
console.info('');
|
|
61
|
+
Object.keys(this.errorCodes).forEach(errorCode => {
|
|
62
|
+
const padding = ' '.repeat(errorCode.length < 4 ? 4 - errorCode.length : 1);
|
|
63
|
+
console.info(' %s%s: %s errors', padding, errorCode, this.errorCodes[errorCode]);
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
package/lib/testserver.js
CHANGED
|
@@ -23,7 +23,7 @@ class TestServer {
|
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Start the server.
|
|
26
|
-
*
|
|
26
|
+
* The callback parameter will be called after the server has started.
|
|
27
27
|
*/
|
|
28
28
|
start(callback) {
|
|
29
29
|
if (this.options.socket) {
|
|
@@ -45,10 +45,8 @@ class TestServer {
|
|
|
45
45
|
return this.createError('Could not start server on port ' + this.port + ': ' + error, callback);
|
|
46
46
|
});
|
|
47
47
|
this.server.listen(this.port, () => {
|
|
48
|
-
console.info(`Listening on http://localhost:${this.port}/`)
|
|
49
|
-
|
|
50
|
-
callback();
|
|
51
|
-
}
|
|
48
|
+
if (!this.options.quiet) console.info(`Listening on http://localhost:${this.port}/`)
|
|
49
|
+
callback(null, this.server)
|
|
52
50
|
});
|
|
53
51
|
this.wsServer.on('request', request => {
|
|
54
52
|
// explicity omitting origin check here.
|
|
@@ -61,19 +59,16 @@ class TestServer {
|
|
|
61
59
|
}
|
|
62
60
|
});
|
|
63
61
|
connection.on('close', () => {
|
|
64
|
-
console.info('Peer %s disconnected', connection.remoteAddress);
|
|
62
|
+
if (!this.options.quiet) console.info('Peer %s disconnected', connection.remoteAddress);
|
|
65
63
|
});
|
|
66
64
|
});
|
|
67
|
-
return this.server
|
|
65
|
+
return this.server
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
/**
|
|
71
69
|
* Log an error, or send to the callback if present.
|
|
72
70
|
*/
|
|
73
71
|
createError(message, callback) {
|
|
74
|
-
if (!callback) {
|
|
75
|
-
return console.error(message);
|
|
76
|
-
}
|
|
77
72
|
callback(message);
|
|
78
73
|
}
|
|
79
74
|
|
|
@@ -106,7 +101,7 @@ class TestServer {
|
|
|
106
101
|
*/
|
|
107
102
|
socketListen(socket) {
|
|
108
103
|
socket.on('error', error => {
|
|
109
|
-
console.error('socket error: %s', error);
|
|
104
|
+
if (!this.options.quiet) console.error('socket error: %s', error);
|
|
110
105
|
socket.end();
|
|
111
106
|
});
|
|
112
107
|
socket.on('data', data => this.readData(data));
|
|
@@ -116,16 +111,16 @@ class TestServer {
|
|
|
116
111
|
* Read some data off the socket.
|
|
117
112
|
*/
|
|
118
113
|
readData(data) {
|
|
119
|
-
console.info('data: %s', data);
|
|
114
|
+
if (!this.options.quiet) console.info('data: %s', data);
|
|
120
115
|
}
|
|
121
116
|
|
|
122
117
|
/**
|
|
123
118
|
* Debug headers and other interesting information: POST body.
|
|
124
119
|
*/
|
|
125
120
|
debug(request) {
|
|
126
|
-
console.info('Headers for %s to %s: %s', request.method, request.url, util.inspect(request.headers));
|
|
121
|
+
if (!this.options.quiet) console.info('Headers for %s to %s: %s', request.method, request.url, util.inspect(request.headers));
|
|
127
122
|
if (request.body) {
|
|
128
|
-
console.info('Body: %s', request.body);
|
|
123
|
+
if (!this.options.quiet) console.info('Body: %s', request.body);
|
|
129
124
|
}
|
|
130
125
|
}
|
|
131
126
|
|
|
@@ -152,7 +147,7 @@ class TestServer {
|
|
|
152
147
|
}
|
|
153
148
|
const percent = parseInt(this.options.percent, 10);
|
|
154
149
|
if (!percent) {
|
|
155
|
-
console.error('Invalid error percent %s', this.options.percent);
|
|
150
|
+
if (!this.options.quiet) console.error('Invalid error percent %s', this.options.percent);
|
|
156
151
|
return false;
|
|
157
152
|
}
|
|
158
153
|
return (Math.random() < percent / 100);
|
|
@@ -160,16 +155,26 @@ class TestServer {
|
|
|
160
155
|
}
|
|
161
156
|
|
|
162
157
|
/**
|
|
163
|
-
* Start a test server.
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
158
|
+
* Start a test server. Parameters:
|
|
159
|
+
* - `options`, can contain:
|
|
160
|
+
* - port: the port to use, default 7357.
|
|
161
|
+
* - delay: wait the given milliseconds before answering.
|
|
162
|
+
* - quiet: do not log any messages.
|
|
163
|
+
* - percent: give an error (default 500) on some % of requests.
|
|
164
|
+
* - error: set an HTTP error code, default is 500.
|
|
165
|
+
* - `callback`: optional callback, called after the server has started.
|
|
166
|
+
* If not present will return a promise.
|
|
170
167
|
*/
|
|
171
168
|
export function startServer(options, callback) {
|
|
172
169
|
const server = new TestServer(options);
|
|
173
|
-
|
|
170
|
+
if (callback) {
|
|
171
|
+
return server.start(callback)
|
|
172
|
+
}
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
server.start((error, result) => {
|
|
175
|
+
if (error) return reject(error)
|
|
176
|
+
return resolve(result)
|
|
177
|
+
})
|
|
178
|
+
})
|
|
174
179
|
}
|
|
175
180
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loadtest",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Run load tests for your web application. Mostly ab-compatible interface, with an option to force requests per second. Includes an API for automated load testing.",
|
|
6
6
|
"homepage": "https://github.com/alexfernandez/loadtest",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"confinode": "^2.1.1",
|
|
20
20
|
"https-proxy-agent": "^2.2.1",
|
|
21
21
|
"stdio": "0.2.7",
|
|
22
|
-
"testing": "^3.0
|
|
22
|
+
"testing": "^3.1.0",
|
|
23
23
|
"websocket": "^1.0.34"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
@@ -25,11 +25,11 @@ const options = {
|
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
-
loadTest(options, (error,
|
|
28
|
+
loadTest(options, (error, result) => {
|
|
29
29
|
if (error) {
|
|
30
30
|
return console.error('Got an error: %s', error);
|
|
31
31
|
}
|
|
32
|
-
console.log(
|
|
32
|
+
console.log(result);
|
|
33
33
|
console.log('Tests run successfully');
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -25,9 +25,9 @@ const options: loadtest.LoadTestOptions = {
|
|
|
25
25
|
},
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
loadtest.loadTest(options, (error,
|
|
28
|
+
loadtest.loadTest(options, (error, result) => {
|
|
29
29
|
if (error) {
|
|
30
30
|
return console.error(`Got an error: ${error}`)
|
|
31
31
|
}
|
|
32
|
-
console.log("Tests run successfully", {
|
|
32
|
+
console.log("Tests run successfully", {result})
|
|
33
33
|
})
|
package/test/body-generator.js
CHANGED
|
@@ -6,16 +6,17 @@ const PORT = 10453;
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
function testBodyGenerator(callback) {
|
|
9
|
-
const server = startServer({port: PORT}, error => {
|
|
9
|
+
const server = startServer({port: PORT, quiet: true}, error => {
|
|
10
10
|
if (error) {
|
|
11
11
|
return callback('Could not start test server');
|
|
12
12
|
}
|
|
13
13
|
const options = {
|
|
14
14
|
url: 'http://localhost:' + PORT,
|
|
15
|
-
requestsPerSecond:
|
|
15
|
+
requestsPerSecond: 1000,
|
|
16
16
|
maxRequests: 100,
|
|
17
17
|
concurrency: 10,
|
|
18
18
|
postFile: 'sample/post-file.js',
|
|
19
|
+
quiet: true,
|
|
19
20
|
}
|
|
20
21
|
loadTest(options, (error, result) => {
|
|
21
22
|
if (error) {
|
package/test/integration.js
CHANGED
|
@@ -4,13 +4,17 @@ import {join} from 'path'
|
|
|
4
4
|
import {loadTest, startServer} from '../index.js'
|
|
5
5
|
|
|
6
6
|
const PORT = 10408;
|
|
7
|
+
const serverOptions = {
|
|
8
|
+
port: PORT,
|
|
9
|
+
quiet: true,
|
|
10
|
+
}
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
14
|
* Run an integration test.
|
|
11
15
|
*/
|
|
12
16
|
function testIntegration(callback) {
|
|
13
|
-
const server = startServer(
|
|
17
|
+
const server = startServer(serverOptions, error => {
|
|
14
18
|
if (error) {
|
|
15
19
|
return callback(error);
|
|
16
20
|
}
|
|
@@ -22,6 +26,7 @@ function testIntegration(callback) {
|
|
|
22
26
|
body: {
|
|
23
27
|
hi: 'there',
|
|
24
28
|
},
|
|
29
|
+
quiet: true,
|
|
25
30
|
};
|
|
26
31
|
loadTest(options, (error, result) => {
|
|
27
32
|
if (error) {
|
|
@@ -31,7 +36,7 @@ function testIntegration(callback) {
|
|
|
31
36
|
if (error) {
|
|
32
37
|
return callback(error);
|
|
33
38
|
}
|
|
34
|
-
return callback(null, 'Test
|
|
39
|
+
return callback(null, 'Test result: ' + JSON.stringify(result));
|
|
35
40
|
});
|
|
36
41
|
});
|
|
37
42
|
});
|
|
@@ -42,12 +47,13 @@ function testIntegration(callback) {
|
|
|
42
47
|
* Run an integration test using configuration file.
|
|
43
48
|
*/
|
|
44
49
|
function testIntegrationFile(callback) {
|
|
45
|
-
const server = startServer(
|
|
50
|
+
const server = startServer(serverOptions, error => {
|
|
46
51
|
if (error) {
|
|
47
52
|
return callback(error);
|
|
48
53
|
}
|
|
49
54
|
execFile('node',
|
|
50
|
-
[join('./', 'bin', 'loadtest.js'), `http://localhost:${PORT}/`,
|
|
55
|
+
[join('./', 'bin', 'loadtest.js'), `http://localhost:${PORT}/`,
|
|
56
|
+
'-n', '100', '--quiet'],
|
|
51
57
|
(error, stdout) => {
|
|
52
58
|
if (error) {
|
|
53
59
|
return callback(error);
|
|
@@ -56,7 +62,7 @@ function testIntegrationFile(callback) {
|
|
|
56
62
|
if (error) {
|
|
57
63
|
return callback(error);
|
|
58
64
|
}
|
|
59
|
-
return callback(null, 'Test
|
|
65
|
+
return callback(null, 'Test result: ' + stdout);
|
|
60
66
|
});
|
|
61
67
|
});
|
|
62
68
|
});
|
|
@@ -68,7 +74,7 @@ function testIntegrationFile(callback) {
|
|
|
68
74
|
* Run an integration test.
|
|
69
75
|
*/
|
|
70
76
|
function testWSIntegration(callback) {
|
|
71
|
-
const server = startServer(
|
|
77
|
+
const server = startServer(serverOptions, error => {
|
|
72
78
|
if (error) {
|
|
73
79
|
return callback(error);
|
|
74
80
|
}
|
|
@@ -85,6 +91,7 @@ function testWSIntegration(callback) {
|
|
|
85
91
|
type: 'ping',
|
|
86
92
|
hi: 'there',
|
|
87
93
|
},
|
|
94
|
+
quiet: true,
|
|
88
95
|
};
|
|
89
96
|
loadTest(options, (error, result) => {
|
|
90
97
|
if (error) {
|
|
@@ -94,7 +101,7 @@ function testWSIntegration(callback) {
|
|
|
94
101
|
if (error) {
|
|
95
102
|
return callback(error);
|
|
96
103
|
}
|
|
97
|
-
return callback(null, 'Test
|
|
104
|
+
return callback(null, 'Test result: ' + JSON.stringify(result));
|
|
98
105
|
});
|
|
99
106
|
});
|
|
100
107
|
});
|
|
@@ -105,17 +112,19 @@ function testWSIntegration(callback) {
|
|
|
105
112
|
*/
|
|
106
113
|
function testDelay(callback) {
|
|
107
114
|
const delay = 10;
|
|
108
|
-
|
|
115
|
+
const serverOptions = {
|
|
109
116
|
port: PORT + 1,
|
|
110
|
-
delay
|
|
117
|
+
delay,
|
|
118
|
+
quiet: true,
|
|
111
119
|
};
|
|
112
|
-
const server = startServer(
|
|
120
|
+
const server = startServer(serverOptions, error => {
|
|
113
121
|
if (error) {
|
|
114
122
|
return callback(error);
|
|
115
123
|
}
|
|
116
|
-
options = {
|
|
124
|
+
const options = {
|
|
117
125
|
url: 'http://localhost:' + (PORT + 1),
|
|
118
126
|
maxRequests: 10,
|
|
127
|
+
quiet: true,
|
|
119
128
|
};
|
|
120
129
|
loadTest(options, (error, result) => {
|
|
121
130
|
if (error) {
|
|
@@ -133,10 +142,29 @@ function testDelay(callback) {
|
|
|
133
142
|
});
|
|
134
143
|
}
|
|
135
144
|
|
|
145
|
+
async function testPromise() {
|
|
146
|
+
const server = await startServer(serverOptions)
|
|
147
|
+
const options = {
|
|
148
|
+
url: 'http://localhost:' + PORT,
|
|
149
|
+
maxRequests: 100,
|
|
150
|
+
concurrency: 10,
|
|
151
|
+
method: 'POST',
|
|
152
|
+
body: {
|
|
153
|
+
hi: 'there',
|
|
154
|
+
},
|
|
155
|
+
quiet: true,
|
|
156
|
+
};
|
|
157
|
+
const result = await loadTest(options)
|
|
158
|
+
await server.close()
|
|
159
|
+
return 'Test result: ' + JSON.stringify(result)
|
|
160
|
+
}
|
|
161
|
+
|
|
136
162
|
/**
|
|
137
163
|
* Run all tests.
|
|
138
164
|
*/
|
|
139
165
|
export function test(callback) {
|
|
140
|
-
testing.run([
|
|
166
|
+
testing.run([
|
|
167
|
+
testIntegration, testIntegrationFile, testDelay, testWSIntegration, testPromise,
|
|
168
|
+
], 4000, callback);
|
|
141
169
|
}
|
|
142
170
|
|
package/test/latency.js
CHANGED
|
@@ -49,7 +49,7 @@ function testLatencyPercentiles(callback) {
|
|
|
49
49
|
};
|
|
50
50
|
const latency = new Latency(options, error => {
|
|
51
51
|
testing.check(error, 'Error while testing latency percentiles', callback);
|
|
52
|
-
const percentiles = latency.
|
|
52
|
+
const percentiles = latency.getResult().percentiles;
|
|
53
53
|
|
|
54
54
|
Object.keys(percentiles).forEach(percentile => {
|
|
55
55
|
testing.assert(percentiles[percentile] !== false, 'Empty percentile for %s', percentile, callback);
|