You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
440 lines
14 KiB
440 lines
14 KiB
/**
|
|
* Node.js application file that sends tests state updates to IntelliJ.
|
|
*/
|
|
|
|
|
|
function println(stream, message) {
|
|
var done = stream.write(message + '\n');
|
|
|
|
// this is the first time stdout got backed up
|
|
if (!done && !stream.pendingWrite) {
|
|
stream.pendingWrite = true;
|
|
|
|
// magic sauce: keep node alive until stdout has flushed
|
|
stream.once('drain', function () {
|
|
stream.draining = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
function printlnToStdErr(message) {
|
|
println(process.stderr, message);
|
|
}
|
|
|
|
function printlnToStdOut(message) {
|
|
println(process.stdout, message);
|
|
}
|
|
|
|
function extend(target, patch) {
|
|
if (arguments.length > 2) {
|
|
for (var a = 1; a < arguments.length; a++) {
|
|
extend(target, arguments[a]);
|
|
}
|
|
} else {
|
|
for (var i in patch) {
|
|
if (patch.hasOwnProperty(i)) {
|
|
target[i] = patch[i];
|
|
}
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
|
|
var tcManager = (function() {
|
|
|
|
var escaper = (function() {
|
|
|
|
function escapseChar(ch) {
|
|
switch (ch) {
|
|
case '\n': return 'n';
|
|
case '\r': return 'r';
|
|
case '\u0085': return 'x'; // next-line character
|
|
case '\u2028': return 'l'; // line-separator character
|
|
case '\u2029': return 'p'; // paragraph-separator character
|
|
case '|': return '|';
|
|
case '\'': return '\'';
|
|
case '[': return '[';
|
|
case ']': return ']';
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
return {
|
|
escapeStr: function(str) {
|
|
var strLength = str.length;
|
|
var out = "";
|
|
var ch, escapedChar;
|
|
for (var i = 0; i < strLength; i++) {
|
|
ch = str.charAt(i);
|
|
escapedChar = escapseChar(ch);
|
|
if (escapedChar !== 0) {
|
|
out += '|';
|
|
out += escapedChar;
|
|
} else {
|
|
out += ch;
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
}
|
|
}());
|
|
|
|
function createTCMessage(messageName, attrs) {
|
|
var out = "##teamcity[" + messageName + " ";
|
|
for (var key in attrs) {
|
|
if (attrs.hasOwnProperty(key)) {
|
|
var value = attrs[key];
|
|
var escapedKey = escaper.escapeStr(key);
|
|
var escapedValue = escaper.escapeStr(value);
|
|
out += " " + escapedKey + "='" + escapedValue + "'";
|
|
}
|
|
}
|
|
out += "]";
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Returns a string that is a result of join of all strings from a testNameList array with a dot character.
|
|
* @param testNameList {Array}
|
|
*/
|
|
function testNameList2String(testNameList) {
|
|
return testNameList.join('.');
|
|
}
|
|
|
|
function createNameObj(testNameList) {
|
|
return {name: testNameList2String(testNameList)};
|
|
}
|
|
|
|
function createTestLocationData(relativeModuleName, testNameList) {
|
|
function encode(str) {
|
|
return str.replace(/:/g, '::').replace(/,/g, ':,');
|
|
}
|
|
var a = [relativeModuleName].concat(testNameList ? testNameList : []);
|
|
var path = a.map(encode).join(',');
|
|
return "nodeunit://" + path;
|
|
}
|
|
|
|
return {
|
|
/**
|
|
* @param moduleName {String}
|
|
* @param relativeModulePath {String}
|
|
* @param testSuiteNameList {Array?}
|
|
*/
|
|
createTestSuiteStartedMessage: function(moduleName, relativeModulePath, testSuiteNameList) {
|
|
var testNodeNameList = testSuiteNameList;
|
|
if (!testNodeNameList) {
|
|
testNodeNameList = [moduleName];
|
|
}
|
|
var attrs = createNameObj(testNodeNameList);
|
|
attrs.locationHint = createTestLocationData(relativeModulePath, testSuiteNameList);
|
|
return createTCMessage("testSuiteStarted", attrs);
|
|
},
|
|
/**
|
|
* @param testSuiteNameList {Array}
|
|
*/
|
|
createTestSuiteFinishedMessage: function(testSuiteNameList) {
|
|
return createTCMessage("testSuiteFinished", createNameObj(testSuiteNameList));
|
|
},
|
|
/**
|
|
* @param relativeModulePath {String}
|
|
* @param testNameList {Array}
|
|
*/
|
|
createTestStartedMessage: function(relativeModulePath, testNameList) {
|
|
var attrs = createNameObj(testNameList);
|
|
attrs.locationHint = createTestLocationData(relativeModulePath, testNameList);
|
|
return createTCMessage("testStarted", attrs);
|
|
},
|
|
createTestFinishedMessage: function(testNameList, durationMillis) {
|
|
var attrs = createNameObj(testNameList);
|
|
if (typeof durationMillis === 'number') {
|
|
attrs.duration = String(durationMillis);
|
|
}
|
|
return createTCMessage("testFinished", attrs);
|
|
},
|
|
createTestFailedMessage: function(testNameList, extraProperties) {
|
|
var attributes = createNameObj(testNameList);
|
|
attributes = extend(attributes, extraProperties);
|
|
return createTCMessage("testFailed", attributes);
|
|
}
|
|
};
|
|
}());
|
|
|
|
function run(files) {
|
|
|
|
function getSuiteNameListByTest(testNameList) {
|
|
return testNameList.slice(0, testNameList.length - 1);
|
|
}
|
|
|
|
/**
|
|
* Finishes test suites and starts new ones.
|
|
*
|
|
* @param nextStartedTestSuiteNameList {Array}
|
|
*/
|
|
function handleTestSuiteStart(nextStartedTestSuiteNameList) {
|
|
var lastStartedTestSuiteNameList = testRunState.currentTestSuiteNameList;
|
|
var minLength = Math.min(lastStartedTestSuiteNameList.length, nextStartedTestSuiteNameList.length);
|
|
var i;
|
|
var message;
|
|
var nameList;
|
|
for (i = 0; i < minLength; i++) {
|
|
if (lastStartedTestSuiteNameList[i] !== nextStartedTestSuiteNameList[i]) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
nameList = lastStartedTestSuiteNameList.slice();
|
|
while (nameList.length > i) {
|
|
message = tcManager.createTestSuiteFinishedMessage(nameList);
|
|
printlnToStdOut(message);
|
|
nameList.pop();
|
|
}
|
|
while (nameList.length < nextStartedTestSuiteNameList.length) {
|
|
nameList.push(nextStartedTestSuiteNameList[nameList.length]);
|
|
message = tcManager.createTestSuiteStartedMessage(
|
|
testRunState.moduleName,
|
|
testRunState.relativeModulePath,
|
|
nameList
|
|
);
|
|
printlnToStdOut(message);
|
|
}
|
|
testRunState.currentTestSuiteNameList = nextStartedTestSuiteNameList;
|
|
}
|
|
|
|
var testRunState = {
|
|
moduleName: undefined,
|
|
currentTestSuiteNameList: []
|
|
};
|
|
|
|
var opts = {
|
|
moduleStart: function (absoluteFileName) {
|
|
testRunState.moduleName = path.basename(absoluteFileName);
|
|
testRunState.relativeModulePath = path.relative(process.cwd(), absoluteFileName);
|
|
var message = tcManager.createTestSuiteStartedMessage(
|
|
testRunState.moduleName,
|
|
testRunState.relativeModulePath,
|
|
null
|
|
);
|
|
printlnToStdOut(message);
|
|
},
|
|
moduleDone: function (absoluteFileName, assertions) {
|
|
var moduleName = path.basename(absoluteFileName);
|
|
handleTestSuiteStart([]);
|
|
testRunState.moduleName = undefined;
|
|
testRunState.relativeModulePath = undefined;
|
|
var message = tcManager.createTestSuiteFinishedMessage([moduleName]);
|
|
printlnToStdOut(message);
|
|
},
|
|
testStart: function (testNameList) {
|
|
handleTestSuiteStart(getSuiteNameListByTest(testNameList));
|
|
var message = tcManager.createTestStartedMessage(testRunState.relativeModulePath, testNameList);
|
|
printlnToStdOut(message);
|
|
},
|
|
testDone: function (testNameList, assertions) {
|
|
|
|
function createTestFailedMessage() {
|
|
var failAttrs = {};
|
|
if (!assertions.failures()) {
|
|
return null;
|
|
}
|
|
assertions.forEach(function (a) {
|
|
if (a.failed()) {
|
|
a = nodeunit_utils.betterErrors(a);
|
|
failAttrs.message = "";
|
|
var er = a.error;
|
|
if (er instanceof AssertionError && a.message) {
|
|
failAttrs.message = a.message;
|
|
}
|
|
if (typeof er.actual !== "undefined" && typeof er.expected != "undefined") {
|
|
var actualStr = stringify(er.actual)
|
|
, expectedStr = stringify(er.expected);
|
|
if (actualStr != null && expectedStr != null) {
|
|
failAttrs.actual = actualStr;
|
|
failAttrs.expected = expectedStr;
|
|
}
|
|
}
|
|
if (typeof er.expectedFilePath === 'string') {
|
|
failAttrs.expectedFile = er.expectedFilePath;
|
|
}
|
|
if (typeof er.actualFilePath === 'string') {
|
|
failAttrs.actualFile = er.actualFilePath;
|
|
}
|
|
failAttrs.details = er.stack.toString();
|
|
}
|
|
});
|
|
return tcManager.createTestFailedMessage(testNameList, failAttrs);
|
|
}
|
|
|
|
var testFailedMessage = createTestFailedMessage();
|
|
if (!!testFailedMessage) {
|
|
printlnToStdOut(testFailedMessage);
|
|
}
|
|
var testFinishedMessage = tcManager.createTestFinishedMessage(testNameList, assertions.duration);
|
|
printlnToStdOut(testFinishedMessage);
|
|
},
|
|
log: function() {
|
|
|
|
},
|
|
done: function (assertions) {
|
|
}
|
|
};
|
|
|
|
function collectAbsoluteTestFilePaths(relativeFilePaths) {
|
|
|
|
function collectDirs(absoluteDirPath, dirList) {
|
|
dirList.push(absoluteDirPath);
|
|
fs.readdirSync(absoluteDirPath).forEach(function (entityName) {
|
|
var absoluteChildPath = path.join(absoluteDirPath, entityName);
|
|
if (fs.statSync(absoluteChildPath).isDirectory()) {
|
|
collectDirs(absoluteChildPath, dirList);
|
|
}
|
|
});
|
|
}
|
|
|
|
var files = [];
|
|
|
|
relativeFilePaths.forEach(function (relativeFilePath) {
|
|
var absoluteFilePath = path.join(process.cwd(), relativeFilePath);
|
|
if (fs.statSync(absoluteFilePath).isDirectory()) {
|
|
var absoluteDirPaths = [];
|
|
collectDirs(absoluteFilePath, absoluteDirPaths);
|
|
Array.prototype.push.apply(files, absoluteDirPaths);
|
|
} else {
|
|
files.push(absoluteFilePath);
|
|
}
|
|
});
|
|
return files;
|
|
}
|
|
|
|
function testIt(absolutePaths) {
|
|
function resolveAsyncModule() {
|
|
return require(path.join(nodeunitModulePath, 'deps/async'));
|
|
}
|
|
var all_assertions = [];
|
|
var types = nodeunit.types;
|
|
var async = resolveAsyncModule();
|
|
var options = types.options(opts);
|
|
var start = new Date().getTime();
|
|
|
|
if (!absolutePaths.length) {
|
|
options.done(types.assertionList(all_assertions));
|
|
return;
|
|
}
|
|
|
|
nodeunit_utils.modulePaths(absolutePaths, function (err, files) {
|
|
if (err) throw err;
|
|
async.concatSeries(
|
|
files,
|
|
function (file, cb) {
|
|
nodeunit.runModule(file, require(file), options, cb);
|
|
},
|
|
function (err, all_assertions) {
|
|
var end = new Date().getTime();
|
|
nodeunit.done();
|
|
options.done(types.assertionList(all_assertions, end - start));
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
if (files && files.length) {
|
|
var absolutePaths = collectAbsoluteTestFilePaths(files);
|
|
testIt(absolutePaths);
|
|
}
|
|
}
|
|
|
|
var path = require('path'),
|
|
fs = require('fs'),
|
|
nodeunit,
|
|
nodeunit_utils,
|
|
AssertionError,
|
|
appArgs = (process.ARGV || process.argv).slice(2),
|
|
nodeunitModulePath = appArgs[0];
|
|
|
|
try {
|
|
nodeunit = require(nodeunitModulePath);
|
|
}
|
|
catch (e) {
|
|
printlnToStdErr("Cannot find nodeunit module in '" + nodeunitModulePath + "' directory.");
|
|
printlnToStdErr("There are two options for installing nodeunit:");
|
|
printlnToStdErr("1. Clone / download nodeunit from https://github.com/caolan/nodeunit, then:");
|
|
printlnToStdErr(" make && sudo make install");
|
|
printlnToStdErr("2. Install via npm:");
|
|
printlnToStdErr(" npm install nodeunit");
|
|
printlnToStdErr("");
|
|
|
|
printlnToStdErr("Make sure 'Nodeunit module' field in Nodeunit RunConfiguration points to a valid nodeunit module.");
|
|
}
|
|
|
|
nodeunit_utils = nodeunit.utils;
|
|
AssertionError = nodeunit.assert.AssertionError;
|
|
|
|
run(appArgs.slice(1));
|
|
|
|
function stringify(value) {
|
|
if (value === null) {
|
|
return 'null';
|
|
}
|
|
if (typeof value === 'undefined') {
|
|
return 'undefined';
|
|
}
|
|
if (isString(value)) {
|
|
return '"' + value.toString() + '"';
|
|
}
|
|
var normalizedValue = deepCopyAndNormalize(value);
|
|
if (normalizedValue instanceof RegExp) {
|
|
return normalizedValue.toString();
|
|
}
|
|
if (normalizedValue === undefined) {
|
|
return 'undefined';
|
|
}
|
|
return JSON.stringify(normalizedValue, null, 2);
|
|
}
|
|
|
|
var toString = {}.toString;
|
|
|
|
function isString(value) {
|
|
return typeof value === 'string' || toString.call(value) === '[object String]';
|
|
}
|
|
|
|
function isObject(val) {
|
|
return val === Object(val);
|
|
}
|
|
|
|
function deepCopyAndNormalize(value) {
|
|
var cache = [];
|
|
return (function stringify(value) {
|
|
if (value == null) {
|
|
return value;
|
|
}
|
|
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'string') {
|
|
return value;
|
|
}
|
|
if (value instanceof RegExp) {
|
|
return value;
|
|
}
|
|
|
|
if (cache.indexOf(value) !== -1) {
|
|
return '[Circular reference found] Truncated by IDE';
|
|
}
|
|
cache.push(value);
|
|
|
|
if (Array.isArray(value)) {
|
|
return value.map(function (element) {
|
|
return stringify(element);
|
|
});
|
|
}
|
|
|
|
if (isObject(value)) {
|
|
var keys = Object.keys(value);
|
|
keys.sort();
|
|
var ret = {};
|
|
keys.forEach(function (key) {
|
|
ret[key] = stringify(value[key]);
|
|
});
|
|
return ret;
|
|
}
|
|
|
|
return value;
|
|
})(value);
|
|
}
|