/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
var Spawn = require('child_process').spawn;
var Http = require("http");
var Fs = require("fs");
var TIMEOUT = 15000;
var parseURL = function (url)
{
url = url.replace(/[a-zA-Z]*:\/\//, '');
var path = url.split('/')[1] || '';
url = url.split('/')[0];
var port = url.split(':')[1];
url = url.split(':')[0];
port = Number(port) || 8083;
var host = url || undefined;
return {
host: host,
port: port,
path: '/' + path
};
};
var remote = module.exports.remote = function (url, argv) {
var params = parseURL(url);
if (!params.host) { throw new Error("For the client, the hostname is required"); }
params.headers = { args: [ JSON.stringify(argv) ] };
params.method = 'POST';
return function (fileName, callback) {
var out = [];
out.push('Testing Remotely');
var req = Http.request(params, function(res) {
if (res.statusCode !== 200) {
out.push('STATUS: ' + res.statusCode);
out.push('HEADERS: ' + JSON.stringify(res.headers));
}
res.setEncoding('utf8');
var body = '';
res.on('data', function (chunk) { body += String(chunk); });
res.on('end', function () {
var ret = JSON.parse(body);
if (ret.returnCode !== 0) {
output.push(ret.stdout);
}
out.push(ret.stderr);
callback(out.join('\n'), (ret.returnCode !== 0));
});
});
Fs.createReadStream(fileName).pipe(req);
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
};
};
var client = function (url, fileName, argv) {
var params = parseURL(url);
if (!params.host) { throw new Error("For the client, the hostname is required"); }
params.headers = { args: [ JSON.stringify(argv) ] };
params.method = 'POST';
var req = Http.request(params, function(res) {
if (res.statusCode !== 200) {
console.log('STATUS: ' + res.statusCode);
console.log('HEADERS: ' + JSON.stringify(res.headers));
}
res.setEncoding('utf8');
var body = '';
res.on('data', function (chunk) { body += String(chunk); });
res.on('end', function () {
var ret = JSON.parse(body);
process.stdout.write(ret.stdout);
process.stderr.write(ret.stderr);
process.exit(ret.returnCode);
});
});
Fs.createReadStream(fileName).pipe(req);
req.on('error', function(e) {
console.log('problem with request: ' + e.message);
});
};
/// Server
var spawnProc = function(file, args, callback, timeoutMilliseconds) {
var child = Spawn(file, args);
var out = '', err = '';
var to = setTimeout(function() {
child.kill('SIGKILL');
err += "TIMEOUT\n";
callback(1000, out, err);
}, timeoutMilliseconds);
child.stdout.on('data', function (data) { out += String(data); });
child.stderr.on('data', function (data) { err += String(data); });
child.on('close', function (code) {
clearTimeout(to);
callback(code, out, err);
});
child.on('error', function(err) {
clearTimeout(to);
callback(1, '', err.stack);
});
};
var local = module.exports.local = function (argv) {
return function (fileName, callback) {
var output = [];
spawnProc(fileName, argv, function (code, out, err) {
if (code !== 0) {
output.push(out);
}
output.push(err);
callback(output.join('\n'), (code !== 0));
}, TIMEOUT);
};
};
var send = function (response, content) {
response.writeHeader(200, {"Content-Type": "text/plain"});
response.write(content);
response.end();
};
var runTest = function (fileName, args, response, timeoutMilliseconds) {
Fs.chmodSync(fileName, '755');
setTimeout(function () {
spawnProc(fileName, args, function(code, out, err) {
send(response, JSON.stringify({ returnCode: code, stdout: out, stderr: err }));
//Fs.unlink(fileName);
}, timeoutMilliseconds);
}, 100);
};
var server = function (url, tempDir) {
var params = parseURL(url);
tempDir = tempDir || '/tmp';
console.log("Serving http://" + (params.host || '') + ':' + params.port + params.path);
Http.createServer(function(request, response) {
if (request.method === 'POST' && request.url === params.path) {
var args = JSON.parse(request.headers.args);
var fileName = tempDir + "/test-" + String(Math.random()).substring(2) + ".exe";
request.pipe(Fs.createWriteStream(fileName));
request.on("end", function() {
runTest(fileName, args, response, TIMEOUT);
});
} else {
response.end();
}
}).listen(params.port, params.host);
};
/// Main
var usage = function ()
{
console.log(
"Usage:\n" +
" TestRunner server [http://][][:][/] [tempDir]\n" +
" bind to (default port is 8083) and allow remote execution by anyone\n" +
" providing . will be used for temporary files (default is /tmp)\n" +
" http:// is optional, you can put https:// or penispenispenis:// or whatever you\n" +
" want but only http is supported.\n" +
" To bind to all interfaces, you can omit the hostname, eg: :3333/supersecret\n" +
" and to have the default port, you can provide only the password eg: /secret\n" +
" or the hostname and password, eg: mycomputer/secret\n" +
" The server will print a line explaining exactly what hostname/port/pass it is\n" +
" serving.\n" +
"\n" +
" TestRunner client [http://][][:][/] [argv1, " +
"[argv2, ...]]\n" +
" connect to the specified URL and upload to be run with\n" +
" arguments ...\n" +
" the URL has the same semantics as that for the server except the hostname must be\n" +
" specified.");
};
var main = function (argv)
{
var cli = argv.indexOf("client");
if (cli !== -1) {
argv.splice(0,cli+1);
var cliUrl = argv.shift();
var fileName = argv.shift();
return client(cliUrl, fileName, argv);
}
var serv = argv.indexOf("server");
if (serv !== -1) {
argv.splice(0,serv+1);
var servUrl = argv.shift();
var tempDir = argv.shift();
return server(servUrl, tempDir);
}
return usage();
};
if (!module.parent) {
main(process.argv);
}