artillery
Version:
Flexible and powerful toolkit for load and functional testing
151 lines (122 loc) • 4 kB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
;
const async = require('async');
const _ = require('lodash');
const WebSocket = require('ws');
const debug = require('debug')('ws');
const engineUtil = require('./engine_util');
const template = engineUtil.template;
module.exports = WSEngine;
function WSEngine(script) {
this.config = script.config;
}
WSEngine.prototype.createScenario = function(scenarioSpec, ee) {
var self = this;
let tasks = _.map(scenarioSpec.flow, function(rs) {
if (rs.think) {
return engineUtil.createThink(rs, _.get(self.config, 'defaults.think', {}));
}
return self.step(rs, ee);
});
return self.compile(tasks, scenarioSpec.flow, ee);
};
WSEngine.prototype.step = function (requestSpec, ee) {
let self = this;
if (requestSpec.loop) {
let steps = _.map(requestSpec.loop, function(rs) {
return self.step(rs, ee);
});
return engineUtil.createLoopWithCount(
requestSpec.count || -1,
steps,
{
loopValue: requestSpec.loopValue || '$loopCount',
overValues: requestSpec.over,
whileTrue: self.config.processor ?
self.config.processor[requestSpec.whileTrue] : undefined
});
}
if (requestSpec.think) {
return engineUtil.createThink(requestSpec, _.get(self.config, 'defaults.think', {}));
}
if (requestSpec.function) {
return function(context, callback) {
let processFunc = self.config.processor[requestSpec.function];
if (processFunc) {
processFunc(context, ee, function () {
return callback(null, context);
});
}
}
}
let f = function(context, callback) {
ee.emit('request');
let startedAt = process.hrtime();
let payload = template(requestSpec.send, context);
if (typeof payload === 'object') {
payload = JSON.stringify(payload);
} else {
payload = payload.toString();
}
debug('WS send: %s', payload);
context.ws.send(payload, function(err) {
if (err) {
debug(err);
ee.emit('error', err);
} else {
let endedAt = process.hrtime(startedAt);
let delta = (endedAt[0] * 1e9) + endedAt[1];
ee.emit('response', delta, 0, context._uid);
}
return callback(err, context);
});
};
return f;
};
WSEngine.prototype.compile = function (tasks, scenarioSpec, ee) {
let config = this.config;
return function scenario(initialContext, callback) {
function zero(callback) {
let tls = config.tls || {};
let options = _.extend(tls, config.ws);
let subprotocols = _.get(config, 'ws.subprotocols', []);
const headers = _.get(config, 'ws.headers', {});
const subprotocolHeader = _.find(headers, (value, headerName) => {
return headerName.toLowerCase() === 'sec-websocket-protocol';
});
if (typeof subprotocolHeader !== 'undefined') {
// NOTE: subprotocols defined via config.ws.subprotocols take precedence:
subprotocols = subprotocols.concat(subprotocolHeader.split(',').map(s => s.trim()));
}
ee.emit('started');
let ws = new WebSocket(config.target, subprotocols, options);
ws.on('open', function() {
initialContext.ws = ws;
return callback(null, initialContext);
});
ws.once('error', function(err) {
debug(err);
ee.emit('error', err.message || err.code);
return callback(err, {});
});
}
initialContext._successCount = 0;
let steps = _.flatten([
zero,
tasks
]);
async.waterfall(
steps,
function scenarioWaterfallCb(err, context) {
if (err) {
debug(err);
}
if (context && context.ws) {
context.ws.close();
}
return callback(err, context);
});
};
};