/**
* @module digdug/TestingBotTunnel
*/
var fs = require('fs');
var ioQuery = require('dojo/io-query');
var os = require('os');
var pathUtil = require('path');
var request = require('dojo/request');
var Tunnel = require('./Tunnel');
var urlUtil = require('url');
var util = require('./util');
/**
* A TestingBot tunnel.
*
* @constructor module:digdug/TestingBotTunnel
* @extends module:digdug/Tunnel
*/
function TestingBotTunnel() {
this.apiKey = process.env.TESTINGBOT_KEY;
this.apiSecret = process.env.TESTINGBOT_SECRET;
this.fastFailDomains = [];
Tunnel.apply(this, arguments);
}
var _super = Tunnel.prototype;
TestingBotTunnel.prototype = util.mixin(Object.create(_super), /** @lends module:digdug/TestingBotTunnel# */ {
constructor: TestingBotTunnel,
/**
* The TestingBot API key.
*
* @type {string}
* @default the value of the TESTINGBOT_API_KEY environment variable
*/
apiKey: null,
/**
* The TestingBot API secret.
*
* @type {string}
* @default the value of the TESTINGBOT_API_SECRET environment variable
*/
apiSecret: null,
directory: pathUtil.join(__dirname, 'testingbot'),
executable: 'java',
/**
* A list of regular expressions corresponding to domains whose connections should fail immediately if the VM
* attempts to make a connection to them.
*
* @type {string[]}
*/
fastFailDomains: null,
/**
* A filename where additional logs from the tunnel should be output.
*
* @type {string}
*/
logFile: null,
port: 4445,
url: 'https://testingbot.com/downloads/testingbot-tunnel.zip',
/**
* Whether or not to use rabbIT compression for the tunnel connection.
*
* @type {boolean}
* @default
*/
useCompression: false,
/**
* Whether or not to use the default local Jetty proxy for the tunnel.
*
* @type {boolean}
* @default
*/
useJettyProxy: true,
/**
* Whether or not to use the default remote Squid proxy for the VM.
*
* @type {boolean}
* @default
*/
useSquidProxy: true,
/**
* Whether or not to re-encrypt data encrypted by self-signed certificates.
*
* @type {boolean}
* @default
*/
useSsl: false,
/**
* The URL of a service that provides a list of environments supported by TestingBot.
*/
environmentUrl: 'https://api.testingbot.com/v1/browsers',
get auth() {
return (this.apiKey || '') + ':' + (this.apiSecret || '');
},
get isDownloaded() {
return util.fileExists(pathUtil.join(this.directory, 'testingbot-tunnel/testingbot-tunnel.jar'));
},
_makeArgs: function (readyFile) {
var args = [
'-jar', 'testingbot-tunnel/testingbot-tunnel.jar',
this.apiKey,
this.apiSecret,
'-P', this.port,
'-f', readyFile
];
this.fastFailDomains.length && args.push('-F', this.fastFailDomains.join(','));
this.logFile && args.push('-l', this.logFile);
this.useJettyProxy || args.push('-x');
this.useSquidProxy || args.push('-q');
this.useCompression && args.push('-b');
this.useSsl && args.push('-s');
this.verbose && args.push('-d');
if (this.proxy) {
var proxy = urlUtil.parse(this.proxy);
proxy.hostname && args.unshift('-Dhttp.proxyHost=', proxy.hostname);
proxy.port && args.unshift('-Dhttp.proxyPort=', proxy.port);
}
return args;
},
sendJobState: function (jobId, data) {
var payload = {};
data.success != null && (payload['test[success]'] = data.success ? 1 : 0);
data.status && (payload['test[status_message]'] = data.status);
data.name && (payload['test[name]'] = data.name);
data.extra && (payload['test[extra]'] = JSON.stringify(data.extra));
data.tags && data.tags.length && (payload.groups = data.tags.join(','));
payload = ioQuery.objectToQuery(payload);
return request.put('https://api.testingbot.com/v1/tests/' + jobId, {
data: payload,
handleAs: 'text',
headers: {
'Content-Length': Buffer.byteLength(payload, 'utf8'),
'Content-Type': 'application/x-www-form-urlencoded'
},
password: this.apiSecret,
user: this.apiKey,
proxy: this.proxy
}).then(function (response) {
if (response.data) {
var data = JSON.parse(response.data);
if (data.error) {
throw new Error(data.error);
}
else if (!data.success) {
throw new Error('Job data failed to save.');
}
else if (response.statusCode !== 200) {
throw new Error('Server reported ' + response.statusCode + ' with: ' + response.data);
}
}
else {
throw new Error('Server reported ' + response.statusCode + ' with no other data.');
}
});
},
_start: function () {
var readyFile = pathUtil.join(os.tmpdir(), 'testingbot-' + Date.now());
var child = this._makeChild(readyFile);
var childProcess = child.process;
var dfd = child.deferred;
// Polling API is used because we are only watching for one file, so efficiency is not a big deal, and the
// `fs.watch` API has extra restrictions which are best avoided
fs.watchFile(readyFile, { persistent: false, interval: 1007 }, function (current, previous) {
if (Number(current.mtime) === Number(previous.mtime)) {
// readyFile hasn't been modified, so ignore the event
return;
}
fs.unwatchFile(readyFile);
dfd.resolve();
});
var self = this;
var lastMessage;
this._handles.push(
util.on(childProcess.stderr, 'data', function (data) {
data.split('\n').forEach(function (message) {
if (message.indexOf('INFO: ') === 0) {
message = message.slice('INFO: '.length);
// the tunnel produces a lot of repeating messages during setup when the status is pending;
// deduplicate them for sanity
if (
message !== lastMessage &&
message.indexOf('>> [') === -1 &&
message.indexOf('<< [') === -1
) {
self.emit('status', message);
lastMessage = message;
}
}
else if (message.indexOf('SEVERE: ') === 0) {
dfd.reject(message);
}
});
})
);
return child;
},
/**
* Attempt to normalize a TestingBot described environment with the standard Selenium capabilities
*
* TestingBot returns a list of environments that looks like:
*
* {
* "selenium_name": "Chrome36",
* "name": "googlechrome",
* "platform": "CAPITAN",
* "version":"36"
* }
*
* @param {Object} environment a TestingBot environment descriptor
* @returns a normalized descriptor
* @private
*/
_normalizeEnvironment: function (environment) {
var browserMap = {
googlechrome: 'chrome',
iexplore: 'internet explorer'
};
var platform = environment.platform;
var browserName = browserMap[environment.name] || environment.name;
var version = environment.version;
return {
platform: platform,
browserName: browserName,
version: version,
descriptor: environment,
intern: {
platform: platform,
browserName: browserName,
version: version
}
};
}
});
module.exports = TestingBotTunnel;