stryker
Version:
The extendable JavaScript mutation testing framework
184 lines • 10.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var os_1 = require("os");
var test_runner_1 = require("stryker-api/test_runner");
var Sandbox_1 = require("../Sandbox");
var CoverageInstrumenterTranspiler_1 = require("../transpiler/CoverageInstrumenterTranspiler");
var SourceMapper_1 = require("../transpiler/SourceMapper");
var coverageHooks_1 = require("../transpiler/coverageHooks");
var plugin_1 = require("stryker-api/plugin");
var di_1 = require("../di");
// The initial run might take a while.
// For example: angular-bootstrap takes up to 45 seconds.
// Lets take 5 minutes just to be sure
var INITIAL_RUN_TIMEOUT = 60 * 1000 * 5;
var INITIAL_TEST_RUN_MARKER = 'Initial test run';
var InitialTestExecutor = /** @class */ (function () {
function InitialTestExecutor(options, log, inputFiles, testFramework, timer, loggingContext, transpiler) {
this.options = options;
this.log = log;
this.inputFiles = inputFiles;
this.testFramework = testFramework;
this.timer = timer;
this.loggingContext = loggingContext;
this.transpiler = transpiler;
}
InitialTestExecutor.prototype.run = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var transpiledFiles, sourceMapper, _a, coverageMaps, instrumentedFiles, _b, runResult, grossTimeMS, timing;
return tslib_1.__generator(this, function (_c) {
switch (_c.label) {
case 0:
this.log.info('Starting initial test run. This may take a while.');
return [4 /*yield*/, this.transpiler.transpile(this.inputFiles.files)];
case 1:
transpiledFiles = _c.sent();
sourceMapper = SourceMapper_1.default.create(transpiledFiles, this.options);
return [4 /*yield*/, this.annotateForCodeCoverage(transpiledFiles, sourceMapper)];
case 2:
_a = _c.sent(), coverageMaps = _a.coverageMaps, instrumentedFiles = _a.instrumentedFiles;
this.logTranspileResult(instrumentedFiles);
return [4 /*yield*/, this.runInSandbox(instrumentedFiles)];
case 3:
_b = _c.sent(), runResult = _b.runResult, grossTimeMS = _b.grossTimeMS;
timing = this.calculateTiming(grossTimeMS, runResult.tests);
this.validateResult(runResult, timing);
return [2 /*return*/, {
coverageMaps: coverageMaps,
overheadTimeMS: timing.overhead,
runResult: runResult,
sourceMapper: sourceMapper
}];
}
});
});
};
InitialTestExecutor.prototype.runInSandbox = function (files) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var sandbox, runResult, grossTimeMS;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, Sandbox_1.default.create(this.options, 0, files, this.testFramework, 0, this.loggingContext)];
case 1:
sandbox = _a.sent();
this.timer.mark(INITIAL_TEST_RUN_MARKER);
return [4 /*yield*/, sandbox.run(INITIAL_RUN_TIMEOUT, this.getCollectCoverageHooksIfNeeded())];
case 2:
runResult = _a.sent();
grossTimeMS = this.timer.elapsedMs(INITIAL_TEST_RUN_MARKER);
return [4 /*yield*/, sandbox.dispose()];
case 3:
_a.sent();
return [2 /*return*/, { runResult: runResult, grossTimeMS: grossTimeMS }];
}
});
});
};
InitialTestExecutor.prototype.annotateForCodeCoverage = function (files, sourceMapper) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var filesToInstrument, coverageInstrumenterTranspiler, instrumentedFiles;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
filesToInstrument = this.inputFiles.filesToMutate.map(function (mutateFile) { return sourceMapper.transpiledFileNameFor(mutateFile.name); });
coverageInstrumenterTranspiler = new CoverageInstrumenterTranspiler_1.default(this.options, filesToInstrument);
return [4 /*yield*/, coverageInstrumenterTranspiler.transpile(files)];
case 1:
instrumentedFiles = _a.sent();
return [2 /*return*/, { coverageMaps: coverageInstrumenterTranspiler.fileCoverageMaps, instrumentedFiles: instrumentedFiles }];
}
});
});
};
InitialTestExecutor.prototype.validateResult = function (runResult, timing) {
switch (runResult.status) {
case test_runner_1.RunStatus.Complete:
var failedTests = this.filterOutFailedTests(runResult);
if (failedTests.length) {
this.logFailedTestsInInitialRun(failedTests);
throw new Error('There were failed tests in the initial test run.');
}
if (runResult.tests.length === 0) {
this.log.warn('No tests were executed. Stryker will exit prematurely. Please check your configuration.');
return;
}
else {
this.logInitialTestRunSucceeded(runResult.tests, timing);
return;
}
case test_runner_1.RunStatus.Error:
this.logErrorsInInitialRun(runResult);
break;
case test_runner_1.RunStatus.Timeout:
this.logTimeoutInitialRun(runResult);
break;
}
throw new Error('Something went wrong in the initial test run');
};
/**
* Calculates the timing variables for the test run.
* grossTime = NetTime + overheadTime
*
* The overhead time is used to calculate exact timeout values during mutation testing.
* See timeoutMS setting in README for more information on this calculation
*/
InitialTestExecutor.prototype.calculateTiming = function (grossTimeMS, tests) {
var netTimeMS = tests.reduce(function (total, test) { return total + test.timeSpentMs; }, 0);
var overheadTimeMS = grossTimeMS - netTimeMS;
return {
net: netTimeMS,
overhead: overheadTimeMS < 0 ? 0 : overheadTimeMS
};
};
InitialTestExecutor.prototype.getCollectCoverageHooksIfNeeded = function () {
if (this.options.coverageAnalysis === 'perTest') {
if (this.testFramework) {
// Add piece of javascript to collect coverage per test results
this.log.debug("Adding test hooks for coverageAnalysis \"perTest\".");
return coverageHooks_1.coveragePerTestHooks(this.testFramework);
}
else {
this.log.warn('Cannot measure coverage results per test, there is no testFramework and thus no way of executing code right before and after each test.');
}
}
return undefined;
};
InitialTestExecutor.prototype.logTranspileResult = function (transpiledFiles) {
if (this.options.transpilers.length && this.log.isDebugEnabled()) {
this.log.debug("Transpiled files: " + JSON.stringify(transpiledFiles.map(function (f) { return "" + f.name; }), null, 2));
}
};
InitialTestExecutor.prototype.filterOutFailedTests = function (runResult) {
return runResult.tests.filter(function (testResult) { return testResult.status === test_runner_1.TestStatus.Failed; });
};
InitialTestExecutor.prototype.logInitialTestRunSucceeded = function (tests, timing) {
this.log.info('Initial test run succeeded. Ran %s tests in %s (net %s ms, overhead %s ms).', tests.length, this.timer.humanReadableElapsed(), timing.net, timing.overhead);
};
InitialTestExecutor.prototype.logFailedTestsInInitialRun = function (failedTests) {
var message = 'One or more tests failed in the initial test run:';
failedTests.forEach(function (test) {
message += os_1.EOL + "\t" + test.name;
if (test.failureMessages && test.failureMessages.length) {
message += os_1.EOL + "\t\t" + test.failureMessages.join(os_1.EOL + "\t\t");
}
});
this.log.error(message);
};
InitialTestExecutor.prototype.logErrorsInInitialRun = function (runResult) {
var message = 'One or more tests resulted in an error:';
if (runResult.errorMessages && runResult.errorMessages.length) {
runResult.errorMessages.forEach(function (error) { return message += os_1.EOL + "\t" + error; });
}
this.log.error(message);
};
InitialTestExecutor.prototype.logTimeoutInitialRun = function (runResult) {
var message = 'Initial test run timed out! Ran following tests before timeout:';
runResult.tests.forEach(function (test) { return message += os_1.EOL + "\t" + test.name + " (" + test_runner_1.TestStatus[test.status] + ")"; });
this.log.error(message);
};
InitialTestExecutor.inject = plugin_1.tokens(plugin_1.commonTokens.options, plugin_1.commonTokens.logger, di_1.coreTokens.inputFiles, di_1.coreTokens.testFramework, di_1.coreTokens.timer, di_1.coreTokens.loggingContext, di_1.coreTokens.transpiler);
return InitialTestExecutor;
}());
exports.default = InitialTestExecutor;
//# sourceMappingURL=InitialTestExecutor.js.map