jest-test-lineage-reporter 2.1.3 → 2.2.0

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 CHANGED
@@ -90,16 +90,24 @@ jest-lineage test --no-performance --no-quality
90
90
  Run mutation testing standalone (on existing lineage data).
91
91
 
92
92
  ```bash
93
- # Basic mutation testing
93
+ # Basic mutation testing (serial)
94
94
  jest-lineage mutate
95
95
 
96
96
  # With custom threshold
97
97
  jest-lineage mutate --threshold 90
98
98
 
99
+ # Parallel execution with 4 workers (faster!)
100
+ jest-lineage mutate --workers 4
101
+
102
+ # Auto-detect CPU cores and use optimal worker count
103
+ jest-lineage mutate --workers 0
104
+
99
105
  # Debug mode (create mutation files without running tests)
100
106
  jest-lineage mutate --debug --debug-dir ./mutations
101
107
  ```
102
108
 
109
+ **Performance Tip**: Use `--workers` to run mutations in parallel. Each worker processes a different file concurrently, significantly reducing execution time for projects with multiple files.
110
+
103
111
  **Note**: Mutation results are saved to `.jest-lineage-mutation-results.json`. Run `jest-lineage report` after mutation testing to generate an HTML report with mutation data included.
104
112
 
105
113
  #### `jest-lineage report`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jest-test-lineage-reporter",
3
- "version": "2.1.3",
3
+ "version": "2.2.0",
4
4
  "main": "src/TestCoverageReporter.js",
5
5
  "bin": {
6
6
  "jest-lineage": "./bin/jest-lineage.js"
@@ -219,42 +219,99 @@ class MutationTester {
219
219
  fileResults: {},
220
220
  };
221
221
 
222
- let currentFileIndex = 0;
223
- let currentMutationIndex = 0;
222
+ // Determine worker count
223
+ const workers = this.config.workers || 1;
224
+ const shouldParallelize = workers > 1 || workers === 0;
225
+
226
+ if (shouldParallelize) {
227
+ // Parallel execution - process multiple files concurrently
228
+ const os = require('os');
229
+ const actualWorkers = workers === 0 ? Math.max(1, os.cpus().length - 1) : workers;
230
+ console.log(`\n⚔ Running mutations in parallel with ${actualWorkers} workers\n`);
231
+
232
+ const fileEntries = Object.entries(this.lineageData);
233
+ const filePromises = fileEntries.map(async ([filePath, lines], index) => {
234
+ console.log(
235
+ `\nšŸ”¬ [Worker ${(index % actualWorkers) + 1}] Testing mutations in ${filePath} (${index + 1}/${totalFiles})...`
236
+ );
224
237
 
225
- for (const [filePath, lines] of Object.entries(this.lineageData)) {
226
- currentFileIndex++;
227
- console.log(
228
- `\nšŸ”¬ Testing mutations in ${filePath} (${currentFileIndex}/${totalFiles})...`
229
- );
238
+ const fileResults = await this.testFileLines(
239
+ filePath,
240
+ lines,
241
+ 0, // Start from 0 for each file in parallel mode
242
+ totalMutationsCount
243
+ );
230
244
 
231
- const fileResults = await this.testFileLines(
232
- filePath,
233
- lines,
234
- currentMutationIndex,
235
- totalMutationsCount
236
- );
237
- results.fileResults[filePath] = fileResults;
238
-
239
- results.totalMutations += fileResults.totalMutations;
240
- results.killedMutations += fileResults.killedMutations;
241
- results.survivedMutations += fileResults.survivedMutations;
242
- results.timeoutMutations += fileResults.timeoutMutations;
243
- results.errorMutations += fileResults.errorMutations;
244
-
245
- currentMutationIndex += fileResults.totalMutations;
246
-
247
- // Log file completion summary
248
- const fileName = filePath.split("/").pop();
249
- const fileScore =
250
- fileResults.totalMutations > 0
251
- ? Math.round(
252
- (fileResults.killedMutations / fileResults.totalMutations) * 100
253
- )
254
- : 0;
255
- console.log(
256
- `āœ… ${fileName}: ${fileResults.totalMutations} mutations, ${fileResults.killedMutations} killed, ${fileResults.survivedMutations} survived (${fileScore}% score)`
257
- );
245
+ // Log file completion summary
246
+ const fileName = filePath.split("/").pop();
247
+ const fileScore =
248
+ fileResults.totalMutations > 0
249
+ ? Math.round(
250
+ (fileResults.killedMutations / fileResults.totalMutations) * 100
251
+ )
252
+ : 0;
253
+ console.log(
254
+ `āœ… [Worker ${(index % actualWorkers) + 1}] ${fileName}: ${fileResults.totalMutations} mutations, ${fileResults.killedMutations} killed, ${fileResults.survivedMutations} survived (${fileScore}% score)`
255
+ );
256
+
257
+ return { filePath, fileResults };
258
+ });
259
+
260
+ // Process files with concurrency limit
261
+ const chunkSize = actualWorkers;
262
+ for (let i = 0; i < filePromises.length; i += chunkSize) {
263
+ const chunk = filePromises.slice(i, i + chunkSize);
264
+ const chunkResults = await Promise.all(chunk);
265
+
266
+ // Aggregate results
267
+ for (const { filePath, fileResults } of chunkResults) {
268
+ results.fileResults[filePath] = fileResults;
269
+ results.totalMutations += fileResults.totalMutations;
270
+ results.killedMutations += fileResults.killedMutations;
271
+ results.survivedMutations += fileResults.survivedMutations;
272
+ results.timeoutMutations += fileResults.timeoutMutations;
273
+ results.errorMutations += fileResults.errorMutations;
274
+ }
275
+ }
276
+ } else {
277
+ // Serial execution - process one file at a time
278
+ let currentFileIndex = 0;
279
+ let currentMutationIndex = 0;
280
+
281
+ for (const [filePath, lines] of Object.entries(this.lineageData)) {
282
+ currentFileIndex++;
283
+ console.log(
284
+ `\nšŸ”¬ Testing mutations in ${filePath} (${currentFileIndex}/${totalFiles})...`
285
+ );
286
+
287
+ const fileResults = await this.testFileLines(
288
+ filePath,
289
+ lines,
290
+ currentMutationIndex,
291
+ totalMutationsCount
292
+ );
293
+ results.fileResults[filePath] = fileResults;
294
+
295
+ results.totalMutations += fileResults.totalMutations;
296
+ results.killedMutations += fileResults.killedMutations;
297
+ results.survivedMutations += fileResults.survivedMutations;
298
+ results.timeoutMutations += fileResults.timeoutMutations;
299
+ results.errorMutations += fileResults.errorMutations;
300
+
301
+ currentMutationIndex += fileResults.totalMutations;
302
+
303
+ // Log file completion summary
304
+ const fileName = filePath.split("/").pop();
305
+ const fileScore =
306
+ fileResults.totalMutations > 0
307
+ ? Math.round(
308
+ (fileResults.killedMutations / fileResults.totalMutations) * 100
309
+ )
310
+ : 0;
311
+ console.log(
312
+ `āœ… ${fileName}: ${fileResults.totalMutations} mutations, ${fileResults.killedMutations} killed, ${fileResults.survivedMutations} survived (${fileScore}% score)`
313
+ );
314
+ }
258
315
  }
259
316
 
260
317
  // Calculate mutation score
package/src/cli/index.js CHANGED
@@ -47,6 +47,7 @@ async function run(argv) {
47
47
  .option('--data <path>', 'Path to lineage data file', '.jest-lineage-data.json')
48
48
  .option('--threshold <number>', 'Mutation score threshold (%)', '80')
49
49
  .option('--timeout <ms>', 'Timeout per mutation (ms)', '5000')
50
+ .option('--workers <number>', 'Number of parallel workers (1=serial, 0=auto)', '1')
50
51
  .option('--debug', 'Create debug mutation files instead of running tests')
51
52
  .option('--debug-dir <path>', 'Directory for debug files', './mutations-debug')
52
53
  .option('--operators <list>', 'Comma-separated mutation operators to enable')
@@ -50,6 +50,9 @@ function mapCliOptionsToConfig(cliOptions) {
50
50
  if (cliOptions.timeout !== undefined) {
51
51
  config.mutationTimeout = parseInt(cliOptions.timeout);
52
52
  }
53
+ if (cliOptions.workers !== undefined) {
54
+ config.workers = parseInt(cliOptions.workers);
55
+ }
53
56
  if (cliOptions.debug === true) {
54
57
  config.debugMutations = true;
55
58
  }