UNPKG

8.63 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const path = require("path");
4const report_1 = require("@stryker-mutator/api/report");
5const test_runner_1 = require("@stryker-mutator/api/test_runner");
6const util_1 = require("@stryker-mutator/util");
7const log4js_1 = require("log4js");
8const mkdirp = require("mkdirp");
9const ResilientTestRunnerFactory_1 = require("./test-runner/ResilientTestRunnerFactory");
10const TestableMutant_1 = require("./TestableMutant");
11const fileUtils_1 = require("./utils/fileUtils");
12const objectUtils_1 = require("./utils/objectUtils");
13class Sandbox {
14 constructor(options, index, files, testFramework, timeOverheadMS, loggingContext, temporaryDirectory) {
15 this.options = options;
16 this.index = index;
17 this.files = files;
18 this.testFramework = testFramework;
19 this.timeOverheadMS = timeOverheadMS;
20 this.loggingContext = loggingContext;
21 this.log = log4js_1.getLogger(Sandbox.name);
22 this.retrieveEarlyResult = (transpiledMutant) => {
23 if (transpiledMutant.transpileResult.error) {
24 if (this.log.isDebugEnabled()) {
25 this.log.debug(`Transpile error occurred: "${transpiledMutant.transpileResult.error}" during transpiling of mutant ${transpiledMutant.mutant.toString()}`);
26 }
27 const result = transpiledMutant.mutant.createResult(report_1.MutantStatus.TranspileError, []);
28 return result;
29 }
30 else if (!transpiledMutant.mutant.runAllTests && !transpiledMutant.mutant.selectedTests.length) {
31 const result = transpiledMutant.mutant.createResult(report_1.MutantStatus.NoCoverage, []);
32 return result;
33 }
34 else if (!transpiledMutant.changedAnyTranspiledFiles) {
35 const result = transpiledMutant.mutant.createResult(report_1.MutantStatus.Survived, []);
36 return result;
37 }
38 else {
39 // No early result possible, need to run in the sandbox later
40 return null;
41 }
42 };
43 this.workingDirectory = temporaryDirectory.createRandomDirectory('sandbox');
44 this.log.debug('Creating a sandbox for files in %s', this.workingDirectory);
45 }
46 async initialize() {
47 await this.fillSandbox();
48 await this.symlinkNodeModulesIfNeeded();
49 return this.initializeTestRunner();
50 }
51 static create(options, index, files, testFramework, timeoutOverheadMS, loggingContext, temporaryDirectory) {
52 const sandbox = new Sandbox(options, index, files, testFramework, timeoutOverheadMS, loggingContext, temporaryDirectory);
53 return sandbox.initialize().then(() => sandbox);
54 }
55 run(timeout, testHooks, mutatedFileName) {
56 return this.testRunner.run({ timeout, testHooks, mutatedFileName });
57 }
58 dispose() {
59 return this.testRunner.dispose() || Promise.resolve();
60 }
61 async runMutant(transpiledMutant) {
62 const earlyResult = this.retrieveEarlyResult(transpiledMutant);
63 if (earlyResult) {
64 return earlyResult;
65 }
66 else {
67 const mutantFiles = transpiledMutant.transpileResult.outputFiles;
68 if (transpiledMutant.mutant.testSelectionResult === TestableMutant_1.TestSelectionResult.Failed) {
69 this.log.warn(`Failed find coverage data for this mutant, running all tests. This might have an impact on performance: ${transpiledMutant.mutant.toString()}`);
70 }
71 await Promise.all(mutantFiles.map((mutatedFile) => this.writeFileInSandbox(mutatedFile)));
72 const runResult = await this.run(this.calculateTimeout(transpiledMutant.mutant), this.getFilterTestsHooks(transpiledMutant.mutant), this.fileMap[transpiledMutant.mutant.fileName]);
73 await this.reset(mutantFiles);
74 return this.collectMutantResult(transpiledMutant.mutant, runResult);
75 }
76 }
77 collectMutantResult(mutant, runResult) {
78 const status = this.determineMutantState(runResult);
79 const testNames = runResult.tests.filter((t) => t.status !== test_runner_1.TestStatus.Skipped).map((t) => t.name);
80 if (this.log.isDebugEnabled() && status === report_1.MutantStatus.RuntimeError) {
81 const error = runResult.errorMessages ? runResult.errorMessages.toString() : '(undefined)';
82 this.log.debug('A runtime error occurred: %s during execution of mutant: %s', error, mutant.toString());
83 }
84 return mutant.createResult(status, testNames);
85 }
86 determineMutantState(runResult) {
87 switch (runResult.status) {
88 case test_runner_1.RunStatus.Timeout:
89 return report_1.MutantStatus.TimedOut;
90 case test_runner_1.RunStatus.Error:
91 return report_1.MutantStatus.RuntimeError;
92 case test_runner_1.RunStatus.Complete:
93 if (runResult.tests.some((t) => t.status === test_runner_1.TestStatus.Failed)) {
94 return report_1.MutantStatus.Killed;
95 }
96 else {
97 return report_1.MutantStatus.Survived;
98 }
99 }
100 }
101 reset(mutatedFiles) {
102 const originalFiles = this.files.filter((originalFile) => mutatedFiles.some((mutatedFile) => mutatedFile.name === originalFile.name));
103 return Promise.all(originalFiles.map((file) => fileUtils_1.writeFile(this.fileMap[file.name], file.content)));
104 }
105 writeFileInSandbox(file) {
106 const fileNameInSandbox = this.fileMap[file.name];
107 return fileUtils_1.writeFile(fileNameInSandbox, file.content);
108 }
109 fillSandbox() {
110 this.fileMap = Object.create(null);
111 const copyPromises = this.files.map((file) => this.fillFile(file));
112 return Promise.all(copyPromises);
113 }
114 async symlinkNodeModulesIfNeeded() {
115 if (this.options.symlinkNodeModules) {
116 // TODO: Change with this.options.basePath when we have it
117 const basePath = process.cwd();
118 const nodeModules = await fileUtils_1.findNodeModules(basePath);
119 if (nodeModules) {
120 await fileUtils_1.symlinkJunction(nodeModules, path.join(this.workingDirectory, 'node_modules')).catch((error) => {
121 if (error.code === 'EEXIST') {
122 this.log.warn(util_1.normalizeWhitespaces(`Could not symlink "${nodeModules}" in sandbox directory,
123 it is already created in the sandbox. Please remove the node_modules from your sandbox files.
124 Alternatively, set \`symlinkNodeModules\` to \`false\` to disable this warning.`));
125 }
126 else {
127 this.log.warn(`Unexpected error while trying to symlink "${nodeModules}" in sandbox directory.`, error);
128 }
129 });
130 }
131 else {
132 this.log.warn(`Could not find a node_modules folder to symlink into the sandbox directory. Search "${basePath}" and its parent directories`);
133 }
134 }
135 }
136 fillFile(file) {
137 const relativePath = path.relative(process.cwd(), file.name);
138 const folderName = path.join(this.workingDirectory, path.dirname(relativePath));
139 mkdirp.sync(folderName);
140 const targetFile = path.join(folderName, path.basename(relativePath));
141 this.fileMap[file.name] = targetFile;
142 return fileUtils_1.writeFile(targetFile, file.content);
143 }
144 async initializeTestRunner() {
145 const fileNames = Object.keys(this.fileMap).map((sourceFileName) => this.fileMap[sourceFileName]);
146 this.log.debug('Creating test runner %s', this.index);
147 this.testRunner = ResilientTestRunnerFactory_1.default.create(this.options, fileNames, this.workingDirectory, this.loggingContext);
148 await this.testRunner.init();
149 }
150 calculateTimeout(mutant) {
151 const baseTimeout = mutant.timeSpentScopedTests;
152 return this.options.timeoutFactor * baseTimeout + this.options.timeoutMS + this.timeOverheadMS;
153 }
154 getFilterTestsHooks(mutant) {
155 if (this.testFramework) {
156 if (mutant.runAllTests) {
157 return objectUtils_1.wrapInClosure(this.testFramework.filter([]));
158 }
159 else {
160 return objectUtils_1.wrapInClosure(this.testFramework.filter(mutant.selectedTests));
161 }
162 }
163 else {
164 return undefined;
165 }
166 }
167}
168exports.default = Sandbox;
169//# sourceMappingURL=Sandbox.js.map
\No newline at end of file