@zohodesk/codestandard-validator 1.2.4-exp-5 → 1.2.4-exp-7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +170 -1
- package/bin/cliCI.js +1 -0
- package/bin/zdcodequality.js +50 -0
- package/build/hooks/hook.js +3 -1
- package/build/mutation/_config.json +55 -0
- package/build/mutation/branchDiff.js +224 -149
- package/build/mutation/fileResolver.js +164 -133
- package/build/mutation/mutationCli.js +162 -100
- package/build/mutation/mutationRunner.js +216 -189
- package/build/mutation/reportGenerator.js +239 -51
- package/build/mutation/strykerConfigBuilder.js +327 -0
- package/build/mutation/strykerWrapper.js +107 -51
- package/build/utils/General/Config.js +4 -0
- package/build/utils/PluginsInstallation/checkIfPluginsAreInstalled.js +1 -0
- package/index.js +2 -1
- package/package.json +12 -6
- package/samples/sample-branch-mode.js +0 -34
- package/samples/sample-cli-entry.js +0 -34
- package/samples/sample-components.js +0 -63
- package/samples/sample-directory-mode.js +0 -30
- package/samples/sample-runner-direct.js +0 -32
- package/samples/sample-with-api.js +0 -44
|
@@ -1,208 +1,235 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
3
|
+
var StrykerWrapper = require('./strykerWrapper');
|
|
4
|
+
var StrykerConfigBuilder = require('./strykerConfigBuilder');
|
|
5
|
+
var BranchDiff = require('./branchDiff');
|
|
6
|
+
var FileResolver = require('./fileResolver');
|
|
7
|
+
var ReportGenerator = require('./reportGenerator');
|
|
8
|
+
var path = require('path');
|
|
9
|
+
/**
|
|
10
|
+
* MutationRunner — orchestrates mutation testing.
|
|
11
|
+
*
|
|
12
|
+
* Exposes exactly two public methods:
|
|
13
|
+
*
|
|
14
|
+
* runLocal(releaseBranch)
|
|
15
|
+
* Pre-commit / local dev. Collects staged files AND changed files
|
|
16
|
+
* vs the release branch, deduplicates, resolves source↔test pairs,
|
|
17
|
+
* runs Stryker on those specific files, and writes JSON reports
|
|
18
|
+
* (mutation summary + SonarQube-compatible external issues report).
|
|
19
|
+
*
|
|
20
|
+
* runCI(releaseBranch)
|
|
21
|
+
* CI pipeline. Collects changed files vs the release branch,
|
|
22
|
+
* resolves source↔test pairs, runs Stryker, and writes JSON reports.
|
|
23
|
+
* The caller decides pass/fail based on the report.
|
|
24
|
+
*
|
|
25
|
+
* Compatible with Node 16+ and @stryker-mutator/core v5.
|
|
26
|
+
*
|
|
27
|
+
* @param {object} options
|
|
28
|
+
* @param {StrykerConfigBuilder} [options.configBuilder] - pre-configured builder
|
|
29
|
+
*/
|
|
30
|
+
function MutationRunner(options) {
|
|
31
|
+
options = options || {};
|
|
32
|
+
this._cwd = options.cwd || process.cwd();
|
|
33
|
+
var configBuilder = options.configBuilder;
|
|
34
|
+
if (!configBuilder) {
|
|
35
|
+
configBuilder = new StrykerConfigBuilder();
|
|
36
|
+
if (options.strykerConfigpath) {
|
|
37
|
+
configBuilder.fromConfigFile(options.strykerConfigpath);
|
|
35
38
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
projectId: options.projectId || null
|
|
45
|
-
});
|
|
46
|
-
if (sourceFiles.length === 0 && testFiles.length === 0) {
|
|
47
|
-
const emptyResult = this._buildEmptyResult();
|
|
48
|
-
const {
|
|
49
|
-
report,
|
|
50
|
-
filePath
|
|
51
|
-
} = this._reportGenerator.generateAndWrite(emptyResult, {
|
|
52
|
-
command: `mutate --branch=${branch}`,
|
|
53
|
-
branch,
|
|
54
|
-
filePairs: []
|
|
39
|
+
if (options.concurrency != null) configBuilder.setConcurrency(options.concurrency);
|
|
40
|
+
if (options.timeoutMS != null) configBuilder.setTimeoutMS(options.timeoutMS);
|
|
41
|
+
if (options.logLevel) configBuilder.setLogLevel(options.logLevel);
|
|
42
|
+
if (options.reportPath) {
|
|
43
|
+
configBuilder.setHtmlReporter({
|
|
44
|
+
fileName: options.reportPath
|
|
45
|
+
}).setJsonReporter({
|
|
46
|
+
fileName: options.reportPath
|
|
55
47
|
});
|
|
56
|
-
return {
|
|
57
|
-
report,
|
|
58
|
-
filePath,
|
|
59
|
-
message: 'No changed source or test files found in branch diff.'
|
|
60
|
-
};
|
|
61
48
|
}
|
|
49
|
+
}
|
|
50
|
+
this._stryker = new StrykerWrapper(configBuilder);
|
|
51
|
+
this._branchDiff = new BranchDiff({
|
|
52
|
+
cwd: this._cwd,
|
|
53
|
+
token: options.token,
|
|
54
|
+
gitEndPoint: options.gitEndPoint,
|
|
55
|
+
branchDiffPath: options.branchDiffPath,
|
|
56
|
+
compareBranch: options.compareBranch,
|
|
57
|
+
projectId: options.projectId
|
|
58
|
+
});
|
|
59
|
+
this._fileResolver = new FileResolver({
|
|
60
|
+
cwd: this._cwd
|
|
61
|
+
});
|
|
62
|
+
this._reportGenerator = new ReportGenerator({
|
|
63
|
+
cwd: this._cwd,
|
|
64
|
+
outputDir: options.outputDir || 'reports/mutation',
|
|
65
|
+
outputFileName: options.outputFileName || 'mutation-report.json'
|
|
66
|
+
});
|
|
67
|
+
this._jestConfig = options.jestConfig || {};
|
|
68
|
+
}
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const sourcesToMutate = [...new Set(validPairs.map(p => p.source))];
|
|
67
|
-
const testsToRun = [...new Set(validPairs.map(p => p.test))];
|
|
68
|
-
if (sourcesToMutate.length === 0) {
|
|
69
|
-
const emptyResult = this._buildEmptyResult();
|
|
70
|
-
const {
|
|
71
|
-
report,
|
|
72
|
-
filePath
|
|
73
|
-
} = this._reportGenerator.generateAndWrite(emptyResult, {
|
|
74
|
-
command: `mutate --branch=${branch}`,
|
|
75
|
-
branch,
|
|
76
|
-
filePairs: pairs
|
|
77
|
-
});
|
|
78
|
-
return {
|
|
79
|
-
report,
|
|
80
|
-
filePath,
|
|
81
|
-
message: 'No source files with corresponding test files found.'
|
|
82
|
-
};
|
|
83
|
-
}
|
|
70
|
+
/* ------------------------------------------------------------------ */
|
|
71
|
+
/* 1. LOCAL (pre-commit / staged) */
|
|
72
|
+
/* ------------------------------------------------------------------ */
|
|
84
73
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Run mutation testing on locally-changed files.
|
|
76
|
+
*
|
|
77
|
+
* Combines:
|
|
78
|
+
* • staged files (git diff --staged)
|
|
79
|
+
* • changed files (git diff vs releaseBranch, filter ACMR)
|
|
80
|
+
*
|
|
81
|
+
* @param {string} releaseBranch - branch to diff against (e.g. 'release')
|
|
82
|
+
* @returns {Promise<{ report: object, sonarReport: object, filePath: string, sonarFilePath: string, message?: string }>}
|
|
83
|
+
*/
|
|
84
|
+
MutationRunner.prototype.runLocal = function (releaseBranch) {
|
|
85
|
+
if (!releaseBranch) {
|
|
86
|
+
throw new Error('releaseBranch is required. Usage: mutate --mode=local --release=<branch>');
|
|
87
|
+
}
|
|
88
|
+
var self = this;
|
|
89
|
+
return self._branchDiff.getLocalChanges(releaseBranch).then(function (categorized) {
|
|
90
|
+
return self._runMutation(categorized.sourceFiles, categorized.testFiles, {
|
|
91
|
+
mode: 'local',
|
|
92
|
+
releaseBranch: releaseBranch,
|
|
93
|
+
command: 'mutate --mode=local --release=' + releaseBranch
|
|
92
94
|
});
|
|
95
|
+
});
|
|
96
|
+
};
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
filePath
|
|
98
|
-
} = this._reportGenerator.generateAndWrite(mutationResult, {
|
|
99
|
-
command: `mutate --branch=${branch}`,
|
|
100
|
-
branch,
|
|
101
|
-
filePairs: pairs
|
|
102
|
-
});
|
|
103
|
-
return {
|
|
104
|
-
report,
|
|
105
|
-
filePath
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
async runByDirectory(srcDir, testDir, options = {}) {
|
|
109
|
-
if (!srcDir || !testDir) {
|
|
110
|
-
throw new Error('Both source and test directories are required. Usage: npx ZDLintFramework mutate --src=<sourcedir> --test=<testdir>');
|
|
111
|
-
}
|
|
98
|
+
/* ------------------------------------------------------------------ */
|
|
99
|
+
/* 2. CI */
|
|
100
|
+
/* ------------------------------------------------------------------ */
|
|
112
101
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
102
|
+
/**
|
|
103
|
+
* Run mutation testing on files changed vs the release branch (CI).
|
|
104
|
+
*
|
|
105
|
+
* @param {string} releaseBranch - branch to diff against
|
|
106
|
+
* @returns {Promise<{ report: object, sonarReport: object, filePath: string, sonarFilePath: string, message?: string }>}
|
|
107
|
+
*/
|
|
108
|
+
MutationRunner.prototype.runCI = function (releaseBranch) {
|
|
109
|
+
if (!releaseBranch) {
|
|
110
|
+
throw new Error('releaseBranch is required. Usage: mutate --mode=ci --release=<branch>');
|
|
111
|
+
}
|
|
112
|
+
var self = this;
|
|
113
|
+
return self._branchDiff.getCIChanges(releaseBranch).then(function (categorized) {
|
|
114
|
+
return self._runMutation(categorized.sourceFiles, categorized.testFiles, {
|
|
115
|
+
mode: 'ci',
|
|
116
|
+
releaseBranch: releaseBranch,
|
|
117
|
+
command: 'mutate --mode=ci --release=' + releaseBranch
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
};
|
|
133
121
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const sourcesToMutate = [...new Set(validPairs.map(p => p.source))];
|
|
138
|
-
const testsToRun = [...new Set(validPairs.map(p => p.test))];
|
|
139
|
-
if (sourcesToMutate.length === 0) {
|
|
140
|
-
// Fallback: mutate all source files with all test files
|
|
141
|
-
const mutationResult = await this._stryker.run(sourceFiles, {
|
|
142
|
-
testFiles,
|
|
143
|
-
jest: {
|
|
144
|
-
...this._jestConfig,
|
|
145
|
-
enableFindRelatedTests: true
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
const {
|
|
149
|
-
report,
|
|
150
|
-
filePath
|
|
151
|
-
} = this._reportGenerator.generateAndWrite(mutationResult, {
|
|
152
|
-
command: `mutate --src=${srcDir} --test=${testDir}`,
|
|
153
|
-
srcDir,
|
|
154
|
-
testDir,
|
|
155
|
-
filePairs: sourceFiles.map(s => ({
|
|
156
|
-
source: s,
|
|
157
|
-
test: null
|
|
158
|
-
}))
|
|
159
|
-
});
|
|
160
|
-
return {
|
|
161
|
-
report,
|
|
162
|
-
filePath
|
|
163
|
-
};
|
|
164
|
-
}
|
|
122
|
+
/* ------------------------------------------------------------------ */
|
|
123
|
+
/* Shared pipeline */
|
|
124
|
+
/* ------------------------------------------------------------------ */
|
|
165
125
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
126
|
+
/**
|
|
127
|
+
* Internal — common mutation pipeline for both modes.
|
|
128
|
+
*
|
|
129
|
+
* @param {string[]} sourceFiles
|
|
130
|
+
* @param {string[]} testFiles
|
|
131
|
+
* @param {{ mode: string, releaseBranch: string, command: string }} ctx
|
|
132
|
+
* @returns {Promise<{ report: object, sonarReport: object, filePath: string, sonarFilePath: string, message?: string }>}
|
|
133
|
+
*/
|
|
134
|
+
MutationRunner.prototype._runMutation = function (sourceFiles, testFiles, ctx) {
|
|
135
|
+
var self = this;
|
|
136
|
+
if (sourceFiles.length === 0 && testFiles.length === 0) {
|
|
137
|
+
var emptyResult = self._buildEmptyResult();
|
|
138
|
+
var out = self._reportGenerator.generateAndWrite(emptyResult, {
|
|
139
|
+
command: ctx.command,
|
|
140
|
+
mode: ctx.mode,
|
|
141
|
+
releaseBranch: ctx.releaseBranch,
|
|
142
|
+
filePairs: []
|
|
173
143
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
144
|
+
return Promise.resolve({
|
|
145
|
+
report: out.report,
|
|
146
|
+
sonarReport: out.sonarReport,
|
|
147
|
+
filePath: out.filePath,
|
|
148
|
+
sonarFilePath: out.sonarFilePath,
|
|
149
|
+
message: 'No changed source or test files found.'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
var pairs = self._fileResolver.resolveSourceTestPairs(sourceFiles, testFiles);
|
|
153
|
+
var validPairs = pairs.filter(function (p) {
|
|
154
|
+
return p.source && p.test;
|
|
155
|
+
});
|
|
156
|
+
var sourcesToMutate = [];
|
|
157
|
+
var testsToRun = [];
|
|
158
|
+
var seenSrc = {};
|
|
159
|
+
var seenTest = {};
|
|
160
|
+
var i;
|
|
161
|
+
for (i = 0; i < validPairs.length; i++) {
|
|
162
|
+
if (!seenSrc[validPairs[i].source]) {
|
|
163
|
+
seenSrc[validPairs[i].source] = true;
|
|
164
|
+
sourcesToMutate.push(validPairs[i].source);
|
|
165
|
+
}
|
|
166
|
+
if (!seenTest[validPairs[i].test]) {
|
|
167
|
+
seenTest[validPairs[i].test] = true;
|
|
168
|
+
testsToRun.push(path.join(this._branchDiff.getRootDirectory(), validPairs[i].test));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (sourcesToMutate.length === 0) {
|
|
172
|
+
var emptyResult2 = self._buildEmptyResult();
|
|
173
|
+
var out2 = self._reportGenerator.generateAndWrite(emptyResult2, {
|
|
174
|
+
command: ctx.command,
|
|
175
|
+
mode: ctx.mode,
|
|
176
|
+
releaseBranch: ctx.releaseBranch,
|
|
183
177
|
filePairs: pairs
|
|
184
178
|
});
|
|
185
|
-
return {
|
|
186
|
-
report,
|
|
187
|
-
|
|
188
|
-
|
|
179
|
+
return Promise.resolve({
|
|
180
|
+
report: out2.report,
|
|
181
|
+
sonarReport: out2.sonarReport,
|
|
182
|
+
filePath: out2.filePath,
|
|
183
|
+
sonarFilePath: out2.sonarFilePath,
|
|
184
|
+
message: 'No source files with corresponding test files found.'
|
|
185
|
+
});
|
|
189
186
|
}
|
|
190
|
-
|
|
187
|
+
var strykerOpts = {
|
|
188
|
+
rootDirectory: this._branchDiff.getRootDirectory(),
|
|
189
|
+
testFiles: testsToRun,
|
|
190
|
+
jest: Object.assign({}, self._jestConfig, {
|
|
191
|
+
config: {
|
|
192
|
+
testMatch: testsToRun,
|
|
193
|
+
reporters: ['json']
|
|
194
|
+
},
|
|
195
|
+
enableFindRelatedTests: true
|
|
196
|
+
})
|
|
197
|
+
};
|
|
198
|
+
return self._stryker.run(sourcesToMutate, strykerOpts).then(function (mutationResult) {
|
|
199
|
+
var out3 = self._reportGenerator.generateAndWrite(mutationResult, {
|
|
200
|
+
command: ctx.command,
|
|
201
|
+
mode: ctx.mode,
|
|
202
|
+
releaseBranch: ctx.releaseBranch,
|
|
203
|
+
filePairs: pairs
|
|
204
|
+
});
|
|
191
205
|
return {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
killed: 0,
|
|
197
|
-
survived: 0,
|
|
198
|
-
timeout: 0,
|
|
199
|
-
noCoverage: 0,
|
|
200
|
-
ignored: 0,
|
|
201
|
-
runtimeErrors: 0,
|
|
202
|
-
compileErrors: 0,
|
|
203
|
-
mutationScore: 0
|
|
204
|
-
}
|
|
206
|
+
report: out3.report,
|
|
207
|
+
sonarReport: out3.sonarReport,
|
|
208
|
+
filePath: out3.filePath,
|
|
209
|
+
sonarFilePath: out3.sonarFilePath
|
|
205
210
|
};
|
|
206
|
-
}
|
|
207
|
-
}
|
|
211
|
+
});
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/* ------------------------------------------------------------------ */
|
|
215
|
+
/* Helpers */
|
|
216
|
+
/* ------------------------------------------------------------------ */
|
|
217
|
+
|
|
218
|
+
MutationRunner.prototype._buildEmptyResult = function () {
|
|
219
|
+
return {
|
|
220
|
+
files: {},
|
|
221
|
+
mutants: [],
|
|
222
|
+
summary: {
|
|
223
|
+
totalMutants: 0,
|
|
224
|
+
killed: 0,
|
|
225
|
+
survived: 0,
|
|
226
|
+
timeout: 0,
|
|
227
|
+
noCoverage: 0,
|
|
228
|
+
ignored: 0,
|
|
229
|
+
runtimeErrors: 0,
|
|
230
|
+
compileErrors: 0,
|
|
231
|
+
mutationScore: 0
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
};
|
|
208
235
|
module.exports = MutationRunner;
|