1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const path = require("path");
|
4 | const report_1 = require("@stryker-mutator/api/report");
|
5 | const test_runner_1 = require("@stryker-mutator/api/test_runner");
|
6 | const util_1 = require("@stryker-mutator/util");
|
7 | const log4js_1 = require("log4js");
|
8 | const mkdirp = require("mkdirp");
|
9 | const ResilientTestRunnerFactory_1 = require("./test-runner/ResilientTestRunnerFactory");
|
10 | const TestableMutant_1 = require("./TestableMutant");
|
11 | const fileUtils_1 = require("./utils/fileUtils");
|
12 | const objectUtils_1 = require("./utils/objectUtils");
|
13 | class 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 |
|
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 |
|
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 | }
|
168 | exports.default = Sandbox;
|
169 |
|
\ | No newline at end of file |