alm
Version:
The best IDE for TypeScript
269 lines (268 loc) • 9.68 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @module
*/
var types = require("../../../../common/types");
var cp = require("child_process");
var utils = require("../../../../common/utils");
var fsu = require("../../../utils/fsu");
var json = require("../../../../common/json");
var instrumenterCommon_1 = require("./instrumenterCommon");
/**
* Uses position of mocha to figure out the trailing stack after mocha
*/
var nodeModulesFolder = fsu.travelUpTheDirectoryTreeTillYouFind(__dirname, "node_modules");
exports.stackAfterMocha = function (stack) {
var index = stack.findIndex(function (s) { return s.filePath.startsWith(nodeModulesFolder); });
if (index === -1)
return stack;
return stack.slice(0, index);
};
var tsNodeCompilerOptions = JSON.stringify({
/**
* Keep getting "cannot write file" ts / ts-node errors otherwise
*/
allowJs: false,
/** Node's not quite there yet */
target: 'es5',
module: 'commonjs',
/** Hopefully prevent a few source map bugs */
sourceMap: true,
inlineSources: true,
});
/** Main utility function to execute a command */
var mochaExec = function (filePath) {
/** Find key paths */
var tsNodePath = nodeModulesFolder + "/ts-node";
var mochaPath = nodeModulesFolder + "/mocha/bin/_mocha";
/**
* We use the `.js` instrumenter because
* - `ts-node` is wired to ignore compiling `.ts` files in `node_modules`
* - alm will be inside `node_modules` when users install it
* - Fortunately `.js` files will exist after deployment.
*/
var instrumentationPath = __dirname + '/mochaInstrumenter.js';
/** Execute this */
var toExec = [
mochaPath,
'--require', tsNodePath + "/register",
'--require', instrumentationPath,
'--reporter', 'json',
/**
* Without this we get `/test.ts` instead of `/full/path/to/test.ts`
* in the `error.stack` for failed tests
*/
'--full-trace',
/**
* NOTE: the location of `filePath` in args is used by the instrumenter
* -1
*/
filePath,
];
// console.log("TESTED Will Exec", toExec); // DEBUG
/** In this dir */
var cwd = utils.getDirectory(filePath);
/** With these compiler options */
var TS_NODE_COMPILER_OPTIONS = tsNodeCompilerOptions;
return new Promise(function (resolve, reject) {
var child = cp.spawn(process.execPath, toExec, {
cwd: cwd,
env: {
TS_NODE_COMPILER_OPTIONS: TS_NODE_COMPILER_OPTIONS,
/**
* Disable cache just because
*/
TS_NODE_CACHE: false,
/**
* disableWarnings as we don't want it to prevent us from running the js
*/
TS_NODE_DISABLE_WARNINGS: true,
}
});
var output = [];
child.stdout.on('data', function (data) {
output.push(data.toString());
});
child.stderr.on('data', function (data) {
console.log("MOCHA STDERR: " + data);
});
child.on('close', function (code) {
if (code !== 0) {
console.error('MOCHA / TS-NODE existed with code 1');
reject('error');
}
else {
resolve(parseMochaJSON({ output: output.join(''), filePath: filePath }));
}
});
});
};
/**
* Takes a file name and runs it with ts-node + mocha and
* returns its parsed test output
*/
function runTest(filePath) {
return mochaExec(filePath);
}
exports.runTest = runTest;
/**
* Convert MOCHA json output to our test result format
* http://mochajs.org/#json
*/
function parseMochaJSON(cfg) {
// console.log(cfg.output); // DEBUG
var output = json.parse(cfg.output).data;
// console.log(cfg.output) // DEBUG
var stats = output.stats;
// console.log(output.stats); // DEBUG
var tests = output.tests || [];
var suites = [];
var testResults = [];
var instrumentationData = instrumenterCommon_1.readAndDeleteDataFile(cfg.filePath);
/**
* PLAN
* Collect first level suites
* Collect second level suites
*/
/**
* First collect all the suite names
* Becuase they go like:
* a
* test
* test
*
* a b
* test
* test
*
* test
* test
* k
* test
*
* k u
* test
*
* We only need to keep the *current* suite and add to that.
*/
var suiteMap = Object.create(null);
var suiteExists = function (description) { return !!suiteMap[description]; };
var getOrCreateSuite = function (description, suitePositions) {
/** If already created return */
if (suiteExists(description))
return suiteMap[description];
/**
* Otherwise create
*/
var currentSuite = {
description: description,
/** Setup later when we have the *right* description */
testLogPosition: null,
suites: [],
tests: [],
stats: {
testCount: 0,
passCount: 0,
failCount: 0,
skipCount: 0,
durationMs: 0,
}
};
/** Add to suite map for faster lookup */
suiteMap[description] = currentSuite;
/**
* Add to suites
* If the last test spec name is same as the start of this one then its a sub spec ;)
*/
if (suites.length && (description.startsWith(suites[suites.length - 1].description))) {
var lastSuite = suites[suites.length - 1];
currentSuite.description = currentSuite.description.substr(lastSuite.description.length).trim();
lastSuite.suites.push(currentSuite);
}
else {
suites.push(currentSuite);
}
/**
* Fixup the test log position now that we have the right description
*/
var matchInSuitePosition = suitePositions.find(function (s) { return s.title == currentSuite.description; });
if (!matchInSuitePosition) {
/**
* if there no test in the outer position then the description can still be wrong
* aka `outerSuite innerSuite` (instead of `innerSuite`)
* In this case just match against some suitePosition that has a title starting with the description
**/
matchInSuitePosition = suitePositions.find(function (s) { return currentSuite.description.endsWith(s.title); });
}
currentSuite.testLogPosition = matchInSuitePosition.testLogPosition;
/** Return */
return currentSuite;
};
tests.forEach(function (test) {
var suiteDescription = test.fullTitle.substr(0, test.fullTitle.length - test.title.length).trim();
var suite = getOrCreateSuite(suiteDescription, instrumentationData.suites);
var testStatus = function (test) {
if (test.duration == null) {
return types.TestStatus.Skipped;
}
if (!Object.keys(test.err).length) {
return types.TestStatus.Success;
}
return types.TestStatus.Fail;
};
var makeTestError = function (test, positionOfTestInFile) {
if (!Object.keys(test.err).length) {
return undefined;
}
var err = test.err;
var message = err.message;
var stack = exports.stackAfterMocha(instrumenterCommon_1.makeStack(err.stack));
/**
* Position
*/
var testLogPosition = instrumenterCommon_1.makeTestLogPositionFromMochaError(cfg.filePath, stack, positionOfTestInFile);
var testError = {
testLogPosition: testLogPosition,
message: message,
stack: stack
};
return testError;
};
var testLogPosition = instrumentationData.its.find(function (it) { return it.title === test.title; }).testLogPosition;
var testResult = {
description: test.title,
testLogPosition: testLogPosition,
status: testStatus(test),
durationMs: test.duration,
error: makeTestError(test, testLogPosition.lastPositionInFile)
};
/** Add to the suite */
suite.tests.push(testResult);
/** Update suite stats */
suite.stats = {
testCount: suite.stats.testCount + 1,
passCount: testResult.status === types.TestStatus.Success ? suite.stats.passCount + 1 : suite.stats.passCount,
failCount: testResult.status === types.TestStatus.Fail ? suite.stats.failCount + 1 : suite.stats.failCount,
skipCount: testResult.status === types.TestStatus.Skipped ? suite.stats.skipCount + 1 : suite.stats.skipCount,
durationMs: suite.stats.durationMs + (testResult.durationMs || 0),
};
/** Also add to the root module */
testResults.push(testResult);
});
var result = {
filePath: cfg.filePath,
suites: suites,
logs: instrumentationData.logs,
testResults: testResults,
stats: {
testCount: stats.tests,
passCount: stats.passes,
failCount: stats.failures,
skipCount: stats.pending,
durationMs: stats.duration,
}
};
return result;
}
exports.parseMochaJSON = parseMochaJSON;