stryker
Version:
The extendable JavaScript mutation testing framework
218 lines • 11.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var path = require("path");
var log4js_1 = require("log4js");
var mkdirp = require("mkdirp");
var test_runner_1 = require("stryker-api/test_runner");
var objectUtils_1 = require("./utils/objectUtils");
var ResilientTestRunnerFactory_1 = require("./test-runner/ResilientTestRunnerFactory");
var TempFolder_1 = require("./utils/TempFolder");
var fileUtils_1 = require("./utils/fileUtils");
var TestableMutant_1 = require("./TestableMutant");
var report_1 = require("stryker-api/report");
var Sandbox = /** @class */ (function () {
function Sandbox(options, index, files, testFramework, timeOverheadMS, loggingContext) {
var _this = this;
this.options = options;
this.index = index;
this.files = files;
this.testFramework = testFramework;
this.timeOverheadMS = timeOverheadMS;
this.loggingContext = loggingContext;
this.log = log4js_1.getLogger(Sandbox.name);
this.retrieveEarlyResult = function (transpiledMutant) {
if (transpiledMutant.transpileResult.error) {
if (_this.log.isDebugEnabled()) {
_this.log.debug("Transpile error occurred: \"" + transpiledMutant.transpileResult.error + "\" during transpiling of mutant " + transpiledMutant.mutant.toString());
}
var result = transpiledMutant.mutant.result(report_1.MutantStatus.TranspileError, []);
return result;
}
else if (!transpiledMutant.mutant.selectedTests.length) {
var result = transpiledMutant.mutant.result(report_1.MutantStatus.NoCoverage, []);
return result;
}
else if (!transpiledMutant.changedAnyTranspiledFiles) {
var result = transpiledMutant.mutant.result(report_1.MutantStatus.Survived, []);
return result;
}
else {
// No early result possible, need to run in the sandbox later
return null;
}
};
this.workingDirectory = TempFolder_1.TempFolder.instance().createRandomFolder('sandbox');
this.log.debug('Creating a sandbox for files in %s', this.workingDirectory);
}
Sandbox.prototype.initialize = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.fillSandbox()];
case 1:
_a.sent();
return [4 /*yield*/, this.symlinkNodeModulesIfNeeded()];
case 2:
_a.sent();
return [2 /*return*/, this.initializeTestRunner()];
}
});
});
};
Sandbox.create = function (options, index, files, testFramework, timeoutOverheadMS, loggingContext) {
var sandbox = new Sandbox(options, index, files, testFramework, timeoutOverheadMS, loggingContext);
return sandbox.initialize().then(function () { return sandbox; });
};
Sandbox.prototype.run = function (timeout, testHooks, mutatedFileName) {
return this.testRunner.run({ timeout: timeout, testHooks: testHooks, mutatedFileName: mutatedFileName });
};
Sandbox.prototype.dispose = function () {
return this.testRunner.dispose() || Promise.resolve();
};
Sandbox.prototype.runMutant = function (transpiledMutant) {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var earlyResult, mutantFiles, runResult;
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
earlyResult = this.retrieveEarlyResult(transpiledMutant);
if (!earlyResult) return [3 /*break*/, 1];
return [2 /*return*/, earlyResult];
case 1:
mutantFiles = transpiledMutant.transpileResult.outputFiles;
if (transpiledMutant.mutant.testSelectionResult === TestableMutant_1.TestSelectionResult.Failed) {
this.log.warn("Failed find coverage data for this mutant, running all tests. This might have an impact on performance: " + transpiledMutant.mutant.toString());
}
return [4 /*yield*/, Promise.all(mutantFiles.map(function (mutatedFile) { return _this.writeFileInSandbox(mutatedFile); }))];
case 2:
_a.sent();
return [4 /*yield*/, this.run(this.calculateTimeout(transpiledMutant.mutant), this.getFilterTestsHooks(transpiledMutant.mutant), this.fileMap[transpiledMutant.mutant.fileName])];
case 3:
runResult = _a.sent();
return [4 /*yield*/, this.reset(mutantFiles)];
case 4:
_a.sent();
return [2 /*return*/, this.collectMutantResult(transpiledMutant.mutant, runResult)];
}
});
});
};
Sandbox.prototype.collectMutantResult = function (mutant, runResult) {
var status = this.determineMutantState(runResult);
var testNames = runResult.tests
.filter(function (t) { return t.status !== test_runner_1.TestStatus.Skipped; })
.map(function (t) { return t.name; });
if (this.log.isDebugEnabled() && status === report_1.MutantStatus.RuntimeError) {
var error = runResult.errorMessages ? runResult.errorMessages.toString() : '(undefined)';
this.log.debug('A runtime error occurred: %s during execution of mutant: %s', error, mutant.toString());
}
return mutant.result(status, testNames);
};
Sandbox.prototype.determineMutantState = function (runResult) {
switch (runResult.status) {
case test_runner_1.RunStatus.Timeout:
return report_1.MutantStatus.TimedOut;
case test_runner_1.RunStatus.Error:
return report_1.MutantStatus.RuntimeError;
case test_runner_1.RunStatus.Complete:
if (runResult.tests.some(function (t) { return t.status === test_runner_1.TestStatus.Failed; })) {
return report_1.MutantStatus.Killed;
}
else {
return report_1.MutantStatus.Survived;
}
}
};
Sandbox.prototype.reset = function (mutatedFiles) {
var _this = this;
var originalFiles = this.files.filter(function (originalFile) { return mutatedFiles.some(function (mutatedFile) { return mutatedFile.name === originalFile.name; }); });
return Promise.all(originalFiles.map(function (file) { return fileUtils_1.writeFile(_this.fileMap[file.name], file.content); }));
};
Sandbox.prototype.writeFileInSandbox = function (file) {
var fileNameInSandbox = this.fileMap[file.name];
return fileUtils_1.writeFile(fileNameInSandbox, file.content);
};
Sandbox.prototype.fillSandbox = function () {
var _this = this;
this.fileMap = Object.create(null);
var copyPromises = this.files
.map(function (file) { return _this.fillFile(file); });
return Promise.all(copyPromises);
};
Sandbox.prototype.symlinkNodeModulesIfNeeded = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var basePath, nodeModules_1;
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!this.options.symlinkNodeModules) return [3 /*break*/, 4];
basePath = process.cwd();
return [4 /*yield*/, fileUtils_1.findNodeModules(basePath)];
case 1:
nodeModules_1 = _a.sent();
if (!nodeModules_1) return [3 /*break*/, 3];
return [4 /*yield*/, fileUtils_1.symlinkJunction(nodeModules_1, path.join(this.workingDirectory, 'node_modules'))
.catch(function (error) {
if (error.code === 'EEXIST') {
_this.log.warn(objectUtils_1.normalizeWhiteSpaces("Could not symlink \"" + nodeModules_1 + "\" in sandbox directory,\n it is already created in the sandbox. Please remove the node_modules from your sandbox files.\n Alternatively, set `symlinkNodeModules` to `false` to disable this warning."));
}
else {
_this.log.warn("Unexpected error while trying to symlink \"" + nodeModules_1 + "\" in sandbox directory.", error);
}
})];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
this.log.warn("Could not find a node_modules folder to symlink into the sandbox directory. Search \"" + basePath + "\" and its parent directories");
_a.label = 4;
case 4: return [2 /*return*/];
}
});
});
};
Sandbox.prototype.fillFile = function (file) {
var relativePath = path.relative(process.cwd(), file.name);
var folderName = path.join(this.workingDirectory, path.dirname(relativePath));
mkdirp.sync(folderName);
var targetFile = path.join(folderName, path.basename(relativePath));
this.fileMap[file.name] = targetFile;
return fileUtils_1.writeFile(targetFile, file.content);
};
Sandbox.prototype.initializeTestRunner = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
var fileNames;
var _this = this;
return tslib_1.__generator(this, function (_a) {
switch (_a.label) {
case 0:
fileNames = Object.keys(this.fileMap).map(function (sourceFileName) { return _this.fileMap[sourceFileName]; });
this.log.debug("Creating test runner %s", this.index);
this.testRunner = ResilientTestRunnerFactory_1.default.create(this.options, fileNames, this.workingDirectory, this.loggingContext);
return [4 /*yield*/, this.testRunner.init()];
case 1:
_a.sent();
return [2 /*return*/];
}
});
});
};
Sandbox.prototype.calculateTimeout = function (mutant) {
var baseTimeout = mutant.timeSpentScopedTests;
return (this.options.timeoutFactor * baseTimeout) + this.options.timeoutMS + this.timeOverheadMS;
};
Sandbox.prototype.getFilterTestsHooks = function (mutant) {
if (this.testFramework) {
return objectUtils_1.wrapInClosure(this.testFramework.filter(mutant.selectedTests));
}
else {
return undefined;
}
};
return Sandbox;
}());
exports.default = Sandbox;
//# sourceMappingURL=Sandbox.js.map