UNPKG

alm

Version:

The best IDE for TypeScript

269 lines (268 loc) 9.68 kB
"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;