@systemfsoftware/stryker-js-mutation-run 1.2.5

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.
Files changed (53) hide show
  1. package/LICENSE +212 -0
  2. package/README.md +38 -0
  3. package/dist/checker-C4tGiXtZ.mjs +588 -0
  4. package/dist/checker-resource-DTYEKXdg.d.mts +108 -0
  5. package/dist/checker-worker.d.mts +16 -0
  6. package/dist/checker-worker.mjs +2 -0
  7. package/dist/child-process-proxy-worker-lx1M5Mz3.mjs +567 -0
  8. package/dist/child-process-proxy-worker-main.d.mts +1 -0
  9. package/dist/child-process-proxy-worker-main.mjs +6 -0
  10. package/dist/child-process-test-runner-worker-BTWRYb8V.mjs +50 -0
  11. package/dist/child-process-test-runner-worker.d.mts +16 -0
  12. package/dist/child-process-test-runner-worker.mjs +2 -0
  13. package/dist/config/base.d.mts +21 -0
  14. package/dist/config/base.mjs +47 -0
  15. package/dist/config/config-resolution.d.mts +80 -0
  16. package/dist/config/config-resolution.mjs +2 -0
  17. package/dist/config/fork-schema.d.mts +5 -0
  18. package/dist/config/fork-schema.mjs +2 -0
  19. package/dist/errors-BkjxkY_S.d.mts +6 -0
  20. package/dist/errors.d.mts +2 -0
  21. package/dist/errors.mjs +10 -0
  22. package/dist/exit-classification.d.mts +28 -0
  23. package/dist/exit-classification.mjs +39 -0
  24. package/dist/fork-schema-BHuTbRBR.mjs +62 -0
  25. package/dist/incremental-differ-DDVwtXBn.d.mts +72 -0
  26. package/dist/incremental-differ-DY8AA09Q.mjs +579 -0
  27. package/dist/index-CyOSz6LC.d.mts +104 -0
  28. package/dist/index.d.mts +87 -0
  29. package/dist/index.mjs +1885 -0
  30. package/dist/mutants/incremental-differ.d.mts +2 -0
  31. package/dist/mutants/incremental-differ.mjs +2 -0
  32. package/dist/output-mode-CfGHDpi5.d.mts +17 -0
  33. package/dist/output-mode.d.mts +2 -0
  34. package/dist/output-mode.mjs +1 -0
  35. package/dist/plugins-Nx86IJQW.mjs +1618 -0
  36. package/dist/plugins.d.mts +2 -0
  37. package/dist/plugins.mjs +2 -0
  38. package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
  39. package/dist/run-event-CzRz9Gtk.d.mts +121 -0
  40. package/dist/run-event.d.mts +2 -0
  41. package/dist/run-event.mjs +1 -0
  42. package/dist/stryker-package-CTcsIZ4S.mjs +23 -0
  43. package/dist/stryker-package.d.mts +7 -0
  44. package/dist/stryker-package.mjs +2 -0
  45. package/dist/timer-BMrE1SQ3.d.mts +20 -0
  46. package/dist/timer.d.mts +2 -0
  47. package/dist/timer.mjs +48 -0
  48. package/dist/verdict-envelope-BWc61mHm.mjs +229 -0
  49. package/dist/verdict-envelope-CKLZPjxC.d.mts +94 -0
  50. package/dist/verdict-envelope.d.mts +2 -0
  51. package/dist/verdict-envelope.mjs +2 -0
  52. package/package.json +102 -0
  53. package/schema/stryker-schema.json +903 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,1885 @@
1
+ import { ConfigError, retrieveCause } from "./errors.mjs";
2
+ import { $ as testRunnerPool, A as mutantTestPlanner, B as reporter, F as pluginModulePaths, G as runEventSink, H as reporterPluginModules, I as pluginsByKind, J as sandbox, K as runId, L as process$1, M as mutationTestReportHelper, N as optionsValidator, O as loggingServerAddress, P as pluginCreator, Q as testRunnerFactory, R as progressEnabled, S as incrementalDiffer, U as requireFromCwd, V as reporterOverride, W as resolvedMode, X as testCoverage, Y as temporaryDirectory, Z as testRunnerConcurrencyTokens, _ as clearTextEnabled, a as isWarningEnabled, b as dryRunResult, c as ProjectReader, d as ConfigReader, et as timeOverheadMS, f as FileSystem, g as checkerPool, h as checkerFactory, i as optionsPath, it as workerIdGenerator, j as mutants, k as loggingSink, l as MetaSchemaBuilder, m as checkerConcurrencyTokens, n as OptionsValidator, nt as unexpectedExitRegistry, o as CommandTestRunner, p as PluginCreator, q as runStartedAt, rt as validationSchema, t as PluginLoader, tt as timer, u as FileMatcher, v as concurrencyTokenProvider, x as execa, y as disableTypeChecksHelper, z as project } from "./plugins-Nx86IJQW.mjs";
3
+ import { Timer } from "./timer.mjs";
4
+ import { t as forkCoreSchema } from "./fork-schema-BHuTbRBR.mjs";
5
+ import { a as provideLogging, o as provideLoggingBackend } from "./child-process-proxy-worker-lx1M5Mz3.mjs";
6
+ import { a as ChildProcessCrashedError, c as createTestRunnerPool, i as OutOfMemoryError, l as ConcurrencyTokenProvider, o as ResourceDecorator, r as ChildProcessProxy, s as createCheckerPool, t as createCheckerFactory } from "./checker-C4tGiXtZ.mjs";
7
+ import { n as toRelativeNormalizedFileName, r as TestCoverage, t as IncrementalDiffer } from "./incremental-differ-DY8AA09Q.mjs";
8
+ import { setPendingExitClass } from "./exit-classification.mjs";
9
+ import { n as strykerVersion } from "./stryker-package-CTcsIZ4S.mjs";
10
+ import { o as judgeTestContribution, r as buildVerdictEnvelope } from "./verdict-envelope-BWc61mHm.mjs";
11
+ import { t as ChildProcessTestRunnerWorker } from "./child-process-test-runner-worker-BTWRYb8V.mjs";
12
+ import { PlanKind } from "@systemfsoftware/stryker-js-plugin-api/core";
13
+ import { PluginKind, commonTokens, tokens } from "@systemfsoftware/stryker-js-plugin-api/plugin";
14
+ import { createInjector, tokens as tokens$1 } from "typed-inject";
15
+ import { ExpirableTask, deepFreeze, errorToString, isErrnoException, normalizeFileName, normalizeWhitespaces, notEmpty, requireResolve, split } from "@stryker-mutator/util";
16
+ import fs from "fs";
17
+ import path from "path";
18
+ import { URL } from "url";
19
+ import { Result, Schema } from "effect";
20
+ import "@systemfsoftware/stryker-js-plugin-api/logging";
21
+ import { EOL } from "os";
22
+ import * as S from "effect/Schema";
23
+ import { EMPTY, bufferTime, concat, from, lastValueFrom, merge, mergeMap, of, partition } from "rxjs";
24
+ import { createInstrumenter, disableTypeChecks, frameworkPluginsFileUrl } from "@stryker-mutator/instrumenter";
25
+ import { DryRunStatus, MutantRunStatus, TestStatus } from "@systemfsoftware/stryker-js-plugin-api/test-runner";
26
+ import { execaCommand } from "execa";
27
+ import "@systemfsoftware/stryker-js-plugin-api/report";
28
+ import { calculateMutationTestMetrics } from "mutation-testing-metrics";
29
+ import { CheckStatus } from "@systemfsoftware/stryker-js-plugin-api/check";
30
+ import { npmRunPathEnv } from "npm-run-path";
31
+ import { map, shareReplay, tap as tap$1, toArray } from "rxjs/operators";
32
+ //#region src/reporting/broadcast-reporter.ts
33
+ var BroadcastReporter = class {
34
+ options;
35
+ pluginCreator;
36
+ log;
37
+ reporterOverride;
38
+ static inject = tokens$1(commonTokens.options, pluginCreator, commonTokens.logger, reporterOverride, progressEnabled, clearTextEnabled);
39
+ reporters;
40
+ /**
41
+ * The progress bar's gate, resolved by the host once at the edge (U13):
42
+ * human mode on a non-TTY stdout suppresses the bar so its control
43
+ * sequences never reach a pipe. Received as data — core no longer probes
44
+ * the terminal.
45
+ */
46
+ progressEnabled;
47
+ /**
48
+ * The clear-text reporter's gate, resolved by the host once at the edge
49
+ * (U13): machine mode keeps stdout exclusively for the NDJSON stream, so
50
+ * the prose reporter is dropped before dispatch. Received as data — core
51
+ * no longer probes the terminal.
52
+ */
53
+ clearTextEnabled;
54
+ constructor(options, pluginCreator, log, reporterOverride, progressEnabled, clearTextEnabled) {
55
+ this.options = options;
56
+ this.pluginCreator = pluginCreator;
57
+ this.log = log;
58
+ this.reporterOverride = reporterOverride;
59
+ this.progressEnabled = progressEnabled;
60
+ this.clearTextEnabled = clearTextEnabled;
61
+ this.reporters = {};
62
+ if (this.reporterOverride) this.reporters["in-memory"] = this.reporterOverride;
63
+ else this.options.reporters.forEach((reporterName) => this.createReporter(reporterName));
64
+ this.logAboutReporters();
65
+ }
66
+ createReporter(reporterName) {
67
+ this.reporters[reporterName] = this.pluginCreator.create(PluginKind.Reporter, reporterName);
68
+ }
69
+ logAboutReporters() {
70
+ const reporterNames = Object.keys(this.reporters);
71
+ if (reporterNames.length) {
72
+ if (this.log.isDebugEnabled()) this.log.debug(`Broadcasting to reporters ${JSON.stringify(reporterNames)}`);
73
+ } else this.log.warn("No reporter configured. Please configure one or more reporters in the (for example: reporters: ['progress'])");
74
+ }
75
+ broadcast(methodName, call) {
76
+ return Promise.all(Object.entries(this.reporters).map(async ([reporterName, reporter]) => {
77
+ if (reporterName === "progress" && !this.progressEnabled || reporterName === "clear-text" && !this.clearTextEnabled) return;
78
+ try {
79
+ await call(reporter);
80
+ } catch (error) {
81
+ this.handleError(error, methodName, reporterName);
82
+ }
83
+ }));
84
+ }
85
+ onDryRunCompleted(event) {
86
+ this.broadcast("onDryRunCompleted", (reporter) => reporter.onDryRunCompleted?.(event));
87
+ }
88
+ onMutationTestingPlanReady(event) {
89
+ this.broadcast("onMutationTestingPlanReady", (reporter) => reporter.onMutationTestingPlanReady?.(event));
90
+ }
91
+ onMutantTested(result) {
92
+ this.broadcast("onMutantTested", (reporter) => reporter.onMutantTested?.(result));
93
+ }
94
+ onMutationTestReportReady(report, metrics) {
95
+ this.broadcast("onMutationTestReportReady", (reporter) => reporter.onMutationTestReportReady?.(report, metrics));
96
+ }
97
+ async wrapUp() {
98
+ await this.broadcast("wrapUp", (reporter) => reporter.wrapUp?.());
99
+ }
100
+ handleError(error, methodName, reporterName) {
101
+ this.log.error(`An error occurred during '${methodName}' on reporter '${reporterName}'.`, error);
102
+ }
103
+ };
104
+ //#endregion
105
+ //#region src/sandbox/temporary-directory.ts
106
+ var TemporaryDirectory = class {
107
+ log;
108
+ options;
109
+ #temporaryDirectory;
110
+ removeDuringDisposal;
111
+ static inject = tokens(commonTokens.logger, commonTokens.options);
112
+ constructor(log, options) {
113
+ this.log = log;
114
+ this.options = options;
115
+ this.removeDuringDisposal = Boolean(options.cleanTempDir);
116
+ }
117
+ async initialize() {
118
+ const parent = path.resolve(this.options.tempDirName);
119
+ await fs.promises.mkdir(parent, { recursive: true });
120
+ this.#temporaryDirectory = await fs.promises.mkdtemp(path.join(parent, this.options.inPlace ? "backup-" : "sandbox-"));
121
+ this.log.debug("Using temp directory \"%s\"", this.#temporaryDirectory);
122
+ }
123
+ get path() {
124
+ if (!this.#temporaryDirectory) this.#throwNotInitialized();
125
+ return this.#temporaryDirectory;
126
+ }
127
+ #throwNotInitialized() {
128
+ throw new Error("initialize() was not called!");
129
+ }
130
+ /**
131
+ * Deletes the Stryker-temp directory
132
+ */
133
+ async dispose() {
134
+ if (this.removeDuringDisposal && this.#temporaryDirectory) {
135
+ this.log.debug("Deleting stryker temp directory %s", this.#temporaryDirectory);
136
+ try {
137
+ await fs.promises.rm(this.#temporaryDirectory, {
138
+ recursive: true,
139
+ force: true
140
+ });
141
+ } catch {
142
+ this.log.info(`Failed to delete stryker temp directory ${this.#temporaryDirectory}`);
143
+ }
144
+ try {
145
+ if (!(await fs.promises.readdir(this.options.tempDirName)).length) try {
146
+ await fs.promises.rmdir(this.options.tempDirName);
147
+ } catch (e) {
148
+ this.log.debug(`Failed to clean temp ${path.basename(this.options.tempDirName)}`, e);
149
+ }
150
+ } catch {}
151
+ }
152
+ }
153
+ };
154
+ //#endregion
155
+ //#region src/unexpected-exit-handler.ts
156
+ /**
157
+ * Runs registered cleanup handlers when the process exits. Signal handling
158
+ * was deliberately removed: SIGINT/SIGTERM are owned by the runMain bootstrap
159
+ * (fiber interruption + `128 + n` at teardown), and a synchronous
160
+ * `process.exit` in a signal handler raced the runtime and killed finalizers.
161
+ * The `exit` event still fires for both `process.exit` and a drained loop.
162
+ */
163
+ var UnexpectedExitHandler = class {
164
+ process;
165
+ unexpectedExitHandlers = [];
166
+ static inject = [process$1];
167
+ constructor(process) {
168
+ this.process = process;
169
+ process.on("exit", this.handleExit);
170
+ }
171
+ handleExit = () => {
172
+ this.unexpectedExitHandlers.forEach((handler) => handler());
173
+ };
174
+ registerHandler(handler) {
175
+ this.unexpectedExitHandlers.push(handler);
176
+ }
177
+ dispose() {
178
+ this.process.off("exit", this.handleExit);
179
+ }
180
+ };
181
+ //#endregion
182
+ //#region src/run-stages/1-prepare-executor.ts
183
+ var PrepareExecutor = class {
184
+ injector;
185
+ loggingBackend;
186
+ reporterPluginModules;
187
+ static inject = tokens(commonTokens.injector, loggingSink, reporterPluginModules);
188
+ constructor(injector, loggingBackend, reporterPluginModules) {
189
+ this.injector = injector;
190
+ this.loggingBackend = loggingBackend;
191
+ this.reporterPluginModules = reporterPluginModules;
192
+ }
193
+ async execute({ cliOptions, targetMutatePatterns }) {
194
+ const timer$1 = new Timer();
195
+ this.loggingBackend.configure(cliOptions);
196
+ const configReaderInjector = this.injector.provideValue(validationSchema, forkCoreSchema).provideClass(optionsValidator, OptionsValidator);
197
+ const options = await configReaderInjector.injectClass(ConfigReader).readConfig(cliOptions);
198
+ const pluginLoader = configReaderInjector.injectClass(PluginLoader);
199
+ const pluginDescriptors = [
200
+ ...options.plugins,
201
+ frameworkPluginsFileUrl,
202
+ ...this.reporterPluginModules,
203
+ ...options.appendPlugins
204
+ ];
205
+ const loadedPlugins = await pluginLoader.load(pluginDescriptors);
206
+ const metaSchema = configReaderInjector.injectClass(MetaSchemaBuilder).buildMetaSchema(loadedPlugins.schemaContributions);
207
+ const optionsValidatorInjector = configReaderInjector.provideValue(validationSchema, S.decodeUnknownSync(S.Record(S.String, S.Unknown))(metaSchema));
208
+ optionsValidatorInjector.injectClass(OptionsValidator).validate(options, true);
209
+ deepFreeze(options);
210
+ this.loggingBackend.configure(options);
211
+ const projectFileReaderInjector = optionsValidatorInjector.provideValue(commonTokens.options, options).provideClass(temporaryDirectory, TemporaryDirectory).provideClass("fs", FileSystem).provideValue(pluginsByKind, loadedPlugins.pluginsByKind);
212
+ const project$2 = await projectFileReaderInjector.injectClass(ProjectReader).read(targetMutatePatterns);
213
+ if (project$2.isEmpty) throw new ConfigError("No input files found.");
214
+ else {
215
+ await projectFileReaderInjector.resolve(temporaryDirectory).initialize();
216
+ return projectFileReaderInjector.provideValue(project, project$2).provideValue(commonTokens.fileDescriptions, project$2.fileDescriptions).provideClass(pluginCreator, PluginCreator).provideClass(reporter, BroadcastReporter).provideValue(timer, timer$1).provideValue(project, project$2).provideValue(execa, execaCommand).provideValue(process$1, process).provideClass(unexpectedExitRegistry, UnexpectedExitHandler).provideValue(pluginModulePaths, loadedPlugins.pluginModulePaths);
217
+ }
218
+ }
219
+ };
220
+ //#endregion
221
+ //#region src/worker-pool/id-generator.ts
222
+ var IdGenerator = class {
223
+ id = 0;
224
+ next() {
225
+ return this.id++;
226
+ }
227
+ };
228
+ //#endregion
229
+ //#region src/run-stages/map.ts
230
+ /**
231
+ * Calls a defined callback function on each element of a map, and returns an array that contains the results.
232
+ *
233
+ * @param subject The map to act on
234
+ * @param callbackFn The callback fn
235
+ * @returns
236
+ */
237
+ function map$1(subject, callbackFn) {
238
+ const results = [];
239
+ subject.forEach((value, key) => results.push(callbackFn(value, key)));
240
+ return results;
241
+ }
242
+ //#endregion
243
+ //#region src/sandbox/disable-type-checks-preprocessor.ts
244
+ /**
245
+ * Disabled type checking by inserting `@ts-nocheck` atop TS/JS files and removing other @ts-xxx directives from comments:
246
+ * @see https://github.com/stryker-mutator/stryker-js/issues/2438
247
+ */
248
+ var DisableTypeChecksPreprocessor = class {
249
+ log;
250
+ options;
251
+ impl;
252
+ static inject = tokens(commonTokens.logger, commonTokens.options, disableTypeChecksHelper);
253
+ constructor(log, options, impl) {
254
+ this.log = log;
255
+ this.options = options;
256
+ this.impl = impl;
257
+ }
258
+ async preprocess(project) {
259
+ const matcher = new FileMatcher(this.options.disableTypeChecks);
260
+ let warningLogged = false;
261
+ await Promise.all(map$1(project.files, async (file, name) => {
262
+ if (matcher.matches(path.resolve(name))) try {
263
+ const { content } = await this.impl(await file.toInstrumenterFile(), { plugins: this.options.mutator.plugins ? [...this.options.mutator.plugins] : null });
264
+ file.setContent(content);
265
+ } catch (err) {
266
+ if (isWarningEnabled("preprocessorErrors", this.options.warnings)) {
267
+ warningLogged = true;
268
+ this.log.warn(`Unable to disable type checking for file "${name}". Shouldn't type checking be disabled for this file? Consider configuring a more restrictive "${optionsPath("disableTypeChecks")}" settings (or turn it completely off with \`false\`)`, err);
269
+ }
270
+ }
271
+ }));
272
+ if (warningLogged) this.log.warn(`(disable "${optionsPath("warnings", "preprocessorErrors")}" to ignore this warning`);
273
+ }
274
+ };
275
+ //#endregion
276
+ //#region src/sandbox/multi-preprocessor.ts
277
+ var MultiPreprocessor = class {
278
+ preprocessors;
279
+ constructor(preprocessors) {
280
+ this.preprocessors = preprocessors;
281
+ }
282
+ async preprocess(project) {
283
+ for (const preprocessor of this.preprocessors) await preprocessor.preprocess(project);
284
+ }
285
+ };
286
+ //#endregion
287
+ //#region ../../../node_modules/.pnpm/@jsr+std__jsonc@1.0.2/node_modules/@jsr/std__jsonc/parse.js
288
+ /**
289
+ * Converts a JSON with Comments (JSONC) string into an object.
290
+ *
291
+ * @example Usage
292
+ * ```ts
293
+ * import { parse } from "@std/jsonc";
294
+ * import { assertEquals } from "@std/assert";
295
+ *
296
+ * assertEquals(parse('{"foo": "bar"}'), { foo: "bar" });
297
+ * assertEquals(parse('{"foo": "bar", }'), { foo: "bar" });
298
+ * assertEquals(parse('{"foo": "bar", } /* comment *\/'), { foo: "bar" });
299
+ * ```
300
+ *
301
+ * @throws {SyntaxError} If the JSONC string is invalid.
302
+ * @param text A valid JSONC string.
303
+ * @returns The parsed JsonValue from the JSONC string.
304
+ */ function parse(text) {
305
+ if (new.target) throw new TypeError("Cannot create an instance: parse is not a constructor");
306
+ return new JsoncParser(text).parse();
307
+ }
308
+ var JsoncParser = class {
309
+ #whitespace = /* @__PURE__ */ new Set(" \r\n");
310
+ #numberEndToken = /* @__PURE__ */ new Set([..."[]{}:,/", ...this.#whitespace]);
311
+ #text;
312
+ #length;
313
+ #tokenized;
314
+ constructor(text) {
315
+ this.#text = `${text}`;
316
+ this.#length = this.#text.length;
317
+ this.#tokenized = this.#tokenize();
318
+ }
319
+ parse() {
320
+ const token = this.#getNext();
321
+ const res = this.#parseJsonValue(token);
322
+ const { done, value } = this.#tokenized.next();
323
+ if (!done) throw new SyntaxError(buildErrorMessage(value));
324
+ return res;
325
+ }
326
+ /** Read the next token. If the token is read to the end, it throws a SyntaxError. */ #getNext() {
327
+ const { done, value } = this.#tokenized.next();
328
+ if (done) throw new SyntaxError("Cannot parse JSONC: unexpected end of JSONC input");
329
+ return value;
330
+ }
331
+ /** Split the JSONC string into token units. Whitespace and comments are skipped. */ *#tokenize() {
332
+ for (let i = 0; i < this.#length; i++) {
333
+ if (this.#whitespace.has(this.#text[i])) continue;
334
+ if (this.#text[i] === "/" && this.#text[i + 1] === "*") {
335
+ i += 2;
336
+ let hasEndOfComment = false;
337
+ for (; i < this.#length; i++) if (this.#text[i] === "*" && this.#text[i + 1] === "/") {
338
+ hasEndOfComment = true;
339
+ break;
340
+ }
341
+ if (!hasEndOfComment) throw new SyntaxError("Cannot parse JSONC: unexpected end of JSONC input");
342
+ i++;
343
+ continue;
344
+ }
345
+ if (this.#text[i] === "/" && this.#text[i + 1] === "/") {
346
+ i += 2;
347
+ for (; i < this.#length; i++) if (this.#text[i] === "\n" || this.#text[i] === "\r") break;
348
+ continue;
349
+ }
350
+ switch (this.#text[i]) {
351
+ case "{":
352
+ yield {
353
+ type: "BeginObject",
354
+ position: i
355
+ };
356
+ break;
357
+ case "}":
358
+ yield {
359
+ type: "EndObject",
360
+ position: i
361
+ };
362
+ break;
363
+ case "[":
364
+ yield {
365
+ type: "BeginArray",
366
+ position: i
367
+ };
368
+ break;
369
+ case "]":
370
+ yield {
371
+ type: "EndArray",
372
+ position: i
373
+ };
374
+ break;
375
+ case ":":
376
+ yield {
377
+ type: "NameSeparator",
378
+ position: i
379
+ };
380
+ break;
381
+ case ",":
382
+ yield {
383
+ type: "ValueSeparator",
384
+ position: i
385
+ };
386
+ break;
387
+ case "\"": {
388
+ const startIndex = i;
389
+ let shouldEscapeNext = false;
390
+ i++;
391
+ for (; i < this.#length; i++) {
392
+ if (this.#text[i] === "\"" && !shouldEscapeNext) break;
393
+ shouldEscapeNext = this.#text[i] === "\\" && !shouldEscapeNext;
394
+ }
395
+ yield {
396
+ type: "String",
397
+ sourceText: this.#text.substring(startIndex, i + 1),
398
+ position: startIndex
399
+ };
400
+ break;
401
+ }
402
+ default: {
403
+ const startIndex = i;
404
+ for (; i < this.#length; i++) if (this.#numberEndToken.has(this.#text[i])) break;
405
+ i--;
406
+ yield {
407
+ type: "NullOrTrueOrFalseOrNumber",
408
+ sourceText: this.#text.substring(startIndex, i + 1),
409
+ position: startIndex
410
+ };
411
+ }
412
+ }
413
+ }
414
+ }
415
+ #parseJsonValue(value) {
416
+ switch (value.type) {
417
+ case "BeginObject": return this.#parseObject();
418
+ case "BeginArray": return this.#parseArray();
419
+ case "NullOrTrueOrFalseOrNumber": return this.#parseNullOrTrueOrFalseOrNumber(value);
420
+ case "String": return this.#parseString(value);
421
+ default: throw new SyntaxError(buildErrorMessage(value));
422
+ }
423
+ }
424
+ #parseObject() {
425
+ const target = {};
426
+ while (true) {
427
+ const token1 = this.#getNext();
428
+ if (token1.type === "EndObject") return target;
429
+ if (token1.type !== "String") throw new SyntaxError(buildErrorMessage(token1));
430
+ const key = this.#parseString(token1);
431
+ const token2 = this.#getNext();
432
+ if (token2.type !== "NameSeparator") throw new SyntaxError(buildErrorMessage(token2));
433
+ const token3 = this.#getNext();
434
+ Object.defineProperty(target, key, {
435
+ value: this.#parseJsonValue(token3),
436
+ writable: true,
437
+ enumerable: true,
438
+ configurable: true
439
+ });
440
+ const token4 = this.#getNext();
441
+ if (token4.type === "EndObject") return target;
442
+ if (token4.type !== "ValueSeparator") throw new SyntaxError(buildErrorMessage(token4));
443
+ }
444
+ }
445
+ #parseArray() {
446
+ const target = [];
447
+ while (true) {
448
+ const token1 = this.#getNext();
449
+ if (token1.type === "EndArray") return target;
450
+ target.push(this.#parseJsonValue(token1));
451
+ const token2 = this.#getNext();
452
+ if (token2.type === "EndArray") return target;
453
+ if (token2.type !== "ValueSeparator") throw new SyntaxError(buildErrorMessage(token2));
454
+ }
455
+ }
456
+ #parseString(value) {
457
+ let parsed;
458
+ try {
459
+ parsed = JSON.parse(value.sourceText);
460
+ } catch {
461
+ throw new SyntaxError(buildErrorMessage(value));
462
+ }
463
+ if (typeof parsed !== "string") throw new TypeError(`Parsed value is not a string: ${parsed}`);
464
+ return parsed;
465
+ }
466
+ #parseNullOrTrueOrFalseOrNumber(value) {
467
+ if (value.sourceText === "null") return null;
468
+ if (value.sourceText === "true") return true;
469
+ if (value.sourceText === "false") return false;
470
+ let parsed;
471
+ try {
472
+ parsed = JSON.parse(value.sourceText);
473
+ } catch {
474
+ throw new SyntaxError(buildErrorMessage(value));
475
+ }
476
+ if (typeof parsed !== "number") throw new TypeError(`Parsed value is not a number: ${parsed}`);
477
+ return parsed;
478
+ }
479
+ };
480
+ function buildErrorMessage({ type, sourceText, position }) {
481
+ let token = "";
482
+ switch (type) {
483
+ case "BeginObject":
484
+ token = "{";
485
+ break;
486
+ case "EndObject":
487
+ token = "}";
488
+ break;
489
+ case "BeginArray":
490
+ token = "[";
491
+ break;
492
+ case "EndArray":
493
+ token = "]";
494
+ break;
495
+ case "NameSeparator":
496
+ token = ":";
497
+ break;
498
+ case "ValueSeparator":
499
+ token = ",";
500
+ break;
501
+ case "NullOrTrueOrFalseOrNumber":
502
+ case "String": token = 30 < sourceText.length ? `${sourceText.slice(0, 30)}...` : sourceText;
503
+ }
504
+ return `Cannot parse JSONC: unexpected token "${token}" in JSONC at position ${position}`;
505
+ }
506
+ //#endregion
507
+ //#region src/sandbox/parse-config.schema.ts
508
+ /**
509
+ * Error returned when a tsconfig file fails to parse, or parses to a value that
510
+ * does not match the shape this package consumes.
511
+ */
512
+ var TsConfigParseError = class extends Schema.TaggedError()("TsConfigParseError", {
513
+ file: Schema.String,
514
+ reason: Schema.String
515
+ }) {};
516
+ const JsonRecord = Schema.Record(Schema.String, Schema.Unknown);
517
+ /**
518
+ * The tsconfig shape this package reads and rewrites in the sandbox.
519
+ *
520
+ * The index signature keeps every key the schema does not declare — including
521
+ * `compilerOptions` and `$schema` — so unknown keys survive the write-back untouched.
522
+ * `S.mutableKey` keeps the fields the preprocessor rewrites writable (v4's `mutable`
523
+ * only covers arrays), and `S.mutable` keeps the parsed arrays themselves mutable so
524
+ * `TSConfigPreprocessor` can assign rewritten values back onto the config.
525
+ */
526
+ const TsConfigSchema = Schema.StructWithRest(Schema.Struct({
527
+ extends: Schema.mutableKey(Schema.optional(Schema.Union([Schema.String, Schema.Array(Schema.String)]))),
528
+ references: Schema.mutableKey(Schema.optional(Schema.mutable(Schema.Array(Schema.StructWithRest(Schema.Struct({ path: Schema.mutableKey(Schema.String) }), [JsonRecord]))))),
529
+ files: Schema.mutableKey(Schema.optional(Schema.mutable(Schema.Array(Schema.String)))),
530
+ include: Schema.mutableKey(Schema.optional(Schema.mutable(Schema.Array(Schema.String)))),
531
+ exclude: Schema.mutableKey(Schema.optional(Schema.mutable(Schema.Array(Schema.String))))
532
+ }), [JsonRecord]);
533
+ /** A tsconfig `extends` entry list: the array form of `extends`. */
534
+ const ExtendsArraySchema = Schema.Array(Schema.String);
535
+ //#endregion
536
+ //#region src/sandbox/parse-config-helper.ts
537
+ /**
538
+ * Inline replacement for `ts.parseConfigFileTextToJson()` removed in TypeScript 7.
539
+ *
540
+ * No imports from `typescript` — delegates to `@std/jsonc`, which handles comments,
541
+ * trailing commas, and string-aware escaping the way `tsc` does. The parsed value is
542
+ * then shape-checked with an Effect Schema type guard, so consumers receive a config
543
+ * that is already narrowed to the fields this package rewrites.
544
+ */
545
+ /**
546
+ * Parses a JSONC string into a typed tsconfig.
547
+ *
548
+ * @param fileName — used only for error message context (mirrors upstream behaviour).
549
+ * @param jsonText — the raw tsconfig text, possibly containing comments.
550
+ * @returns `Result.success(config)` when the text parses and matches the shape,
551
+ * `Result.failure(TsConfigParseError)` on a parse failure or a shape mismatch.
552
+ */
553
+ function parseTsConfig(fileName, jsonText) {
554
+ let parsed;
555
+ try {
556
+ parsed = parse(jsonText.replace(/^\uFEFF/, ""));
557
+ } catch (error) {
558
+ return Result.fail(new TsConfigParseError({
559
+ file: fileName,
560
+ reason: errorToString(error)
561
+ }));
562
+ }
563
+ if (Schema.is(TsConfigSchema)(parsed)) return Result.succeed(parsed);
564
+ return Result.fail(new TsConfigParseError({
565
+ file: fileName,
566
+ reason: `parsed to ${JSON.stringify(parsed)}, which does not match the tsconfig shape this package consumes`
567
+ }));
568
+ }
569
+ //#endregion
570
+ //#region src/sandbox/resolve-reference-helper.ts
571
+ function resolveProjectReferencePath(ref) {
572
+ return ref.path.endsWith(".json") ? ref.path : `${ref.path}/tsconfig.json`;
573
+ }
574
+ //#endregion
575
+ //#region src/sandbox/ts-config-preprocessor.ts
576
+ /**
577
+ * A helper class that rewrites `references` and `extends` file paths if they end up falling outside of the sandbox.
578
+ * @example
579
+ * {
580
+ * "extends": "../../tsconfig.settings.json",
581
+ * "references": {
582
+ * "path": "../model"
583
+ * }
584
+ * }
585
+ * becomes:
586
+ * {
587
+ * "extends": "../../../../tsconfig.settings.json",
588
+ * "references": {
589
+ * "path": "../../../model"
590
+ * }
591
+ * }
592
+ */
593
+ var TSConfigPreprocessor = class {
594
+ log;
595
+ options;
596
+ touched = /* @__PURE__ */ new Set();
597
+ static inject = tokens(commonTokens.logger, commonTokens.options);
598
+ constructor(log, options) {
599
+ this.log = log;
600
+ this.options = options;
601
+ }
602
+ async preprocess(project) {
603
+ if (this.options.inPlace) return;
604
+ else {
605
+ this.touched.clear();
606
+ await this.rewriteTSConfigFile(project, path.resolve(this.options.tsconfigFile));
607
+ }
608
+ }
609
+ async rewriteTSConfigFile(project, tsconfigFileName) {
610
+ if (!this.touched.has(tsconfigFileName)) {
611
+ this.touched.add(tsconfigFileName);
612
+ const tsconfigFile = project.files.get(tsconfigFileName);
613
+ if (tsconfigFile) {
614
+ this.log.debug("Rewriting file %s", tsconfigFile);
615
+ const parsed = parseTsConfig(tsconfigFileName, await tsconfigFile.readContent());
616
+ if (Result.isSuccess(parsed)) {
617
+ const config = parsed.success;
618
+ await this.rewriteExtends(project, config, tsconfigFileName);
619
+ await this.rewriteProjectReferences(project, config, tsconfigFileName);
620
+ this.rewriteFileArrayProperty(config, tsconfigFileName, "include");
621
+ this.rewriteFileArrayProperty(config, tsconfigFileName, "exclude");
622
+ this.rewriteFileArrayProperty(config, tsconfigFileName, "files");
623
+ tsconfigFile.setContent(JSON.stringify(config, null, 2));
624
+ } else {
625
+ const error = parsed.failure;
626
+ this.log.warn(`Could not rewrite tsconfig file "%s": %s. Its extends, project references, and file array properties were not rewritten for the sandbox, so this file still points at paths outside it.`, tsconfigFileName, error.reason);
627
+ }
628
+ }
629
+ }
630
+ }
631
+ async rewriteExtends(project, config, tsconfigFileName) {
632
+ const extend = config.extends;
633
+ if (typeof extend === "string") config.extends = await this.rewriteExtendsEntry(project, extend, tsconfigFileName);
634
+ else if (Schema.is(ExtendsArraySchema)(extend)) {
635
+ const rewritten = [];
636
+ for (const entry of extend) rewritten.push(await this.rewriteExtendsEntry(project, entry, tsconfigFileName));
637
+ config.extends = rewritten;
638
+ }
639
+ }
640
+ async rewriteExtendsEntry(project, extend, tsconfigFileName) {
641
+ const rewritten = this.tryRewriteReference(extend, tsconfigFileName);
642
+ if (rewritten) return rewritten;
643
+ await this.rewriteTSConfigFile(project, path.resolve(path.dirname(tsconfigFileName), extend));
644
+ return extend;
645
+ }
646
+ rewriteFileArrayProperty(config, tsconfigFileName, prop) {
647
+ const fileArray = config[prop];
648
+ if (Array.isArray(fileArray)) config[prop] = fileArray.map((pattern) => {
649
+ const rewritten = this.tryRewriteReference(pattern, tsconfigFileName);
650
+ if (rewritten) return rewritten;
651
+ else return pattern;
652
+ });
653
+ }
654
+ async rewriteProjectReferences(project, config, originTSConfigFileName) {
655
+ if (Array.isArray(config.references)) for (const reference of config.references) {
656
+ const referencePath = resolveProjectReferencePath(reference);
657
+ const rewritten = this.tryRewriteReference(referencePath, originTSConfigFileName);
658
+ if (rewritten) reference.path = rewritten;
659
+ else await this.rewriteTSConfigFile(project, path.resolve(path.dirname(originTSConfigFileName), referencePath));
660
+ }
661
+ }
662
+ tryRewriteReference(reference, originTSConfigFileName) {
663
+ const dirName = path.dirname(originTSConfigFileName);
664
+ const fileName = path.resolve(dirName, reference);
665
+ if (path.relative(process.cwd(), fileName).startsWith("..")) return this.join("..", "..", reference);
666
+ return false;
667
+ }
668
+ join(...pathSegments) {
669
+ return pathSegments.map((segment) => segment.replace(/\\/g, "/")).join("/");
670
+ }
671
+ };
672
+ //#endregion
673
+ //#region src/sandbox/create-preprocessor.ts
674
+ createPreprocessor.inject = tokens(commonTokens.injector);
675
+ function createPreprocessor(injector) {
676
+ return new MultiPreprocessor([injector.provideValue(disableTypeChecksHelper, disableTypeChecks).injectClass(DisableTypeChecksPreprocessor), injector.injectClass(TSConfigPreprocessor)]);
677
+ }
678
+ //#endregion
679
+ //#region src/sandbox/sandbox.ts
680
+ async function findNodeModulesList(basePath, tempDirName) {
681
+ const nodeModulesList = [];
682
+ const dirBfsQueue = ["."];
683
+ let dir;
684
+ while (dir = dirBfsQueue.pop()) {
685
+ if (path.basename(dir) === tempDirName) continue;
686
+ if (path.basename(dir) === "node_modules") {
687
+ nodeModulesList.push(dir);
688
+ continue;
689
+ }
690
+ const parentDir = dir;
691
+ const dirs = (await fs.promises.readdir(path.join(basePath, dir), { withFileTypes: true })).filter((file) => file.isDirectory()).map((childDir) => path.join(parentDir, childDir.name));
692
+ dirBfsQueue.push(...dirs);
693
+ }
694
+ return nodeModulesList;
695
+ }
696
+ async function symlinkJunction(to, from) {
697
+ await fs.promises.mkdir(path.dirname(from), { recursive: true });
698
+ return fs.promises.symlink(to, from, "junction");
699
+ }
700
+ /**
701
+ * Recursively walks the from directory and copy the content to the target directory synchronously
702
+ * @param from The source directory to move from
703
+ * @param to The target directory to move to
704
+ */
705
+ function moveDirectoryRecursiveSync(from, to) {
706
+ if (!fs.existsSync(from)) return;
707
+ if (!fs.existsSync(to)) fs.mkdirSync(to);
708
+ const files = fs.readdirSync(from);
709
+ for (const file of files) {
710
+ const fromFileName = path.join(from, file);
711
+ const toFileName = path.join(to, file);
712
+ if (fs.lstatSync(fromFileName).isFile()) fs.renameSync(fromFileName, toFileName);
713
+ else moveDirectoryRecursiveSync(fromFileName, toFileName);
714
+ }
715
+ fs.rmdirSync(from);
716
+ }
717
+ var Sandbox = class {
718
+ options;
719
+ log;
720
+ project;
721
+ execCommand;
722
+ fileMap = /* @__PURE__ */ new Map();
723
+ /**
724
+ * The working directory for this sandbox
725
+ * Either an actual sandbox directory, or the cwd when running in --inPlace mode
726
+ */
727
+ workingDirectory;
728
+ /** 11
729
+ * The backup directory when running in --inPlace mode
730
+ */
731
+ backupDirectory = "";
732
+ static inject = tokens(commonTokens.options, commonTokens.logger, temporaryDirectory, project, execa, unexpectedExitRegistry);
733
+ /**
734
+ * @param temporaryDirectory The sandbox dir or the backup dir when running in `--inPlace` mode
735
+ */
736
+ constructor(options, log, temporaryDirectory, project, execCommand, unexpectedExitHandler) {
737
+ this.options = options;
738
+ this.log = log;
739
+ this.project = project;
740
+ this.execCommand = execCommand;
741
+ if (options.inPlace) {
742
+ this.workingDirectory = process.cwd();
743
+ this.backupDirectory = temporaryDirectory.path;
744
+ this.log.info("In place mode is enabled, Stryker will be overriding YOUR files. Find your backup at: %s", path.relative(process.cwd(), this.backupDirectory));
745
+ unexpectedExitHandler.registerHandler(this.dispose.bind(this, true));
746
+ } else {
747
+ this.workingDirectory = temporaryDirectory.path;
748
+ this.log.debug("Creating a sandbox for files in %s", this.workingDirectory);
749
+ }
750
+ }
751
+ async init() {
752
+ await this.fillSandbox();
753
+ await this.runBuildCommand();
754
+ await this.symlinkNodeModulesIfNeeded();
755
+ }
756
+ sandboxFileFor(fileName) {
757
+ const sandboxFileName = this.fileMap.get(fileName);
758
+ if (sandboxFileName === void 0) throw new Error(`Cannot find sandbox file for ${fileName}`);
759
+ return sandboxFileName;
760
+ }
761
+ originalFileFor(sandboxFileName) {
762
+ return path.resolve(sandboxFileName).replace(path.resolve(this.workingDirectory), process.cwd());
763
+ }
764
+ async fillSandbox() {
765
+ await Promise.all(map$1(this.project.files, (file, name) => this.sandboxFile(name, file)));
766
+ }
767
+ async runBuildCommand() {
768
+ if (this.options.buildCommand) {
769
+ const env = npmRunPathEnv();
770
+ this.log.info("Running build command \"%s\" in \"%s\".", this.options.buildCommand, this.workingDirectory);
771
+ this.log.debug("(using PATH: %s)", env["PATH"]);
772
+ await this.execCommand(this.options.buildCommand, {
773
+ cwd: this.workingDirectory,
774
+ env
775
+ });
776
+ }
777
+ }
778
+ async symlinkNodeModulesIfNeeded() {
779
+ this.log.debug("Start symlink node_modules");
780
+ if (this.options.symlinkNodeModules && !this.options.inPlace) {
781
+ const basePath = process.cwd();
782
+ const nodeModulesList = await findNodeModulesList(basePath, this.options.tempDirName);
783
+ if (nodeModulesList.length > 0) for (const nodeModules of nodeModulesList) {
784
+ this.log.debug(`Create symlink from ${path.resolve(nodeModules)} to ${path.join(this.workingDirectory, nodeModules)}`);
785
+ await symlinkJunction(path.resolve(nodeModules), path.join(this.workingDirectory, nodeModules)).catch((error) => {
786
+ if (isErrnoException(error) && error.code === "EEXIST") this.log.warn(normalizeWhitespaces(`Could not symlink "${nodeModules}" in sandbox directory,
787
+ it is already created in the sandbox. Please remove the node_modules from your sandbox files.
788
+ Alternatively, set \`symlinkNodeModules\` to \`false\` to disable this warning.`));
789
+ else this.log.warn(`Unexpected error while trying to symlink "${nodeModules}" in sandbox directory.`, error);
790
+ });
791
+ }
792
+ else this.log.debug(`Could not find a node_modules folder to symlink into the sandbox directory. Search "${basePath}" and its parent directories`);
793
+ }
794
+ }
795
+ /**
796
+ * Sandboxes a file (writes it to the sandbox). Either in-place, or an actual sandbox directory.
797
+ * @param name The name of the file
798
+ * @param file The file reference
799
+ */
800
+ async sandboxFile(name, file) {
801
+ if (this.options.inPlace) {
802
+ if (file.hasChanges) {
803
+ const backupFileName = await file.backupTo(this.backupDirectory);
804
+ this.log.debug("Stored backup file at %s", backupFileName);
805
+ await file.writeInPlace();
806
+ }
807
+ this.fileMap.set(name, name);
808
+ } else {
809
+ const targetFileName = await file.writeToSandbox(this.workingDirectory);
810
+ this.fileMap.set(name, targetFileName);
811
+ }
812
+ }
813
+ dispose(unexpected = false) {
814
+ if (this.backupDirectory) {
815
+ if (unexpected) console.error(`Detecting unexpected exit, recovering original files from ${path.relative(process.cwd(), this.backupDirectory)}`);
816
+ else this.log.info(`Resetting your original files from ${path.relative(process.cwd(), this.backupDirectory)}.`);
817
+ moveDirectoryRecursiveSync(this.backupDirectory, this.workingDirectory);
818
+ }
819
+ }
820
+ };
821
+ //#endregion
822
+ //#region src/mutants/mutant-test-planner.ts
823
+ /**
824
+ * The factor by which hit count from dry run is multiplied to calculate the hit limit for a mutant.
825
+ * This is intentionally a high value to prevent false positives.
826
+ *
827
+ * For example, a property testing library might execute a failing scenario multiple times to determine the smallest possible counterexample.
828
+ * @see https://jsverify.github.io/#minimal-counterexample
829
+ */
830
+ const HIT_LIMIT_FACTOR = 100;
831
+ /**
832
+ * Responsible for determining the tests to execute for each mutant, as well as other run option specific details
833
+ */
834
+ var MutantTestPlanner = class {
835
+ testCoverage;
836
+ incrementalDiffer;
837
+ reporter;
838
+ sandbox;
839
+ project;
840
+ timeOverheadMS;
841
+ options;
842
+ logger;
843
+ static inject = tokens(testCoverage, incrementalDiffer, reporter, sandbox, project, timeOverheadMS, commonTokens.options, commonTokens.logger);
844
+ timeSpentAllTests;
845
+ globalTestFilter;
846
+ constructor(testCoverage, incrementalDiffer, reporter, sandbox, project, timeOverheadMS, options, logger) {
847
+ this.testCoverage = testCoverage;
848
+ this.incrementalDiffer = incrementalDiffer;
849
+ this.reporter = reporter;
850
+ this.sandbox = sandbox;
851
+ this.project = project;
852
+ this.timeOverheadMS = timeOverheadMS;
853
+ this.options = options;
854
+ this.logger = logger;
855
+ this.timeSpentAllTests = calculateTotalTime(this.testCoverage.testsById.values());
856
+ this.globalTestFilter = this.project.testFiles.length > 0 ? this.project.testFiles.map((file) => this.sandbox.sandboxFileFor(file)) : void 0;
857
+ }
858
+ async makePlan(mutants) {
859
+ const mutantPlans = (await this.incrementalDiff(mutants)).map((mutant) => this.planMutant(mutant));
860
+ this.reporter.onMutationTestingPlanReady({ mutantPlans });
861
+ this.warnAboutSlow(mutantPlans);
862
+ return mutantPlans;
863
+ }
864
+ planMutant(mutant) {
865
+ const isStatic = this.testCoverage.hasStaticCoverage(mutant.id);
866
+ if (mutant.status) return this.createMutantEarlyResultPlan(mutant, {
867
+ isStatic,
868
+ ...mutant.coveredBy === void 0 ? {} : { coveredBy: mutant.coveredBy },
869
+ ...mutant.killedBy === void 0 ? {} : { killedBy: mutant.killedBy },
870
+ status: mutant.status,
871
+ ...mutant.statusReason === void 0 ? {} : { statusReason: mutant.statusReason }
872
+ });
873
+ else if (this.testCoverage.hasCoverage) {
874
+ const tests = this.testCoverage.testsByMutantId.get(mutant.id) ?? [];
875
+ const coveredBy = toTestIds(tests);
876
+ if (!isStatic || this.options.ignoreStatic && coveredBy.length) {
877
+ const netTime = calculateTotalTime(tests);
878
+ return this.createMutantRunPlan(mutant, {
879
+ netTime,
880
+ coveredBy,
881
+ isStatic,
882
+ testFilter: coveredBy
883
+ });
884
+ } else if (this.options.ignoreStatic) return this.createMutantEarlyResultPlan(mutant, {
885
+ status: "Ignored",
886
+ statusReason: "Static mutant (and \"ignoreStatic\" was enabled)",
887
+ isStatic,
888
+ coveredBy
889
+ });
890
+ else return this.createMutantRunPlan(mutant, {
891
+ netTime: this.timeSpentAllTests,
892
+ isStatic,
893
+ coveredBy,
894
+ testFilter: this.globalTestFilter
895
+ });
896
+ } else return this.createMutantRunPlan(mutant, {
897
+ netTime: this.timeSpentAllTests,
898
+ testFilter: this.globalTestFilter
899
+ });
900
+ }
901
+ createMutantEarlyResultPlan(mutant, { isStatic, status, statusReason, coveredBy, killedBy }) {
902
+ return {
903
+ plan: PlanKind.EarlyResult,
904
+ mutant: {
905
+ ...mutant,
906
+ status,
907
+ ...isStatic === void 0 ? {} : { static: isStatic },
908
+ ...statusReason === void 0 ? {} : { statusReason },
909
+ ...coveredBy === void 0 ? {} : { coveredBy },
910
+ ...killedBy === void 0 ? {} : { killedBy }
911
+ }
912
+ };
913
+ }
914
+ createMutantRunPlan(mutant, { netTime, testFilter, isStatic, coveredBy }) {
915
+ const { disableBail, timeoutMS, timeoutFactor } = this.options;
916
+ const timeout = timeoutFactor * netTime + timeoutMS + this.timeOverheadMS;
917
+ const hitCount = this.testCoverage.hitsByMutantId.get(mutant.id);
918
+ const hitLimit = hitCount === void 0 ? void 0 : hitCount * HIT_LIMIT_FACTOR;
919
+ const canHotSwap = !!testFilter && isStatic === false;
920
+ return {
921
+ plan: PlanKind.Run,
922
+ netTime,
923
+ mutant: {
924
+ ...mutant,
925
+ ...isStatic === void 0 ? {} : { static: isStatic },
926
+ ...coveredBy === void 0 ? {} : { coveredBy }
927
+ },
928
+ runOptions: {
929
+ activeMutant: {
930
+ id: mutant.id,
931
+ fileName: mutant.fileName,
932
+ location: mutant.location,
933
+ mutatorName: mutant.mutatorName,
934
+ replacement: mutant.replacement
935
+ },
936
+ mutantActivation: testFilter ? "runtime" : "static",
937
+ timeout,
938
+ ...testFilter === void 0 ? {} : { testFilter },
939
+ sandboxFileName: this.sandbox.sandboxFileFor(mutant.fileName),
940
+ ...hitLimit === void 0 ? {} : { hitLimit },
941
+ disableBail,
942
+ reloadEnvironment: !canHotSwap
943
+ }
944
+ };
945
+ }
946
+ warnAboutSlow(mutantPlans) {
947
+ if (!this.options.ignoreStatic && isWarningEnabled("slow", this.options.warnings)) {
948
+ const ABSOLUTE_CUT_OFF_PERUNAGE = .4;
949
+ const RELATIVE_CUT_OFF_FACTOR = 2;
950
+ const zeroIfNaN = (n) => isNaN(n) ? 0 : n;
951
+ const totalNetTime = (runPlans) => runPlans.reduce((acc, { netTime }) => acc + netTime, 0);
952
+ const runPlans = mutantPlans.filter(isRunPlan);
953
+ const [staticRunPlans, runTimeRunPlans] = split(runPlans, ({ mutant }) => Boolean(mutant.static));
954
+ const estimatedTimeForStaticMutants = totalNetTime(staticRunPlans);
955
+ const estimatedTimeForRunTimeMutants = totalNetTime(runTimeRunPlans);
956
+ const estimatedTotalTime = estimatedTimeForRunTimeMutants + estimatedTimeForStaticMutants;
957
+ const avgTimeForAStaticMutant = zeroIfNaN(estimatedTimeForStaticMutants / staticRunPlans.length);
958
+ const avgTimeForARunTimeMutant = zeroIfNaN(estimatedTimeForRunTimeMutants / runTimeRunPlans.length);
959
+ const relativeTimeForStaticMutants = estimatedTimeForStaticMutants / estimatedTotalTime;
960
+ const absoluteCondition = relativeTimeForStaticMutants >= ABSOLUTE_CUT_OFF_PERUNAGE;
961
+ if (avgTimeForAStaticMutant >= RELATIVE_CUT_OFF_FACTOR * avgTimeForARunTimeMutant && absoluteCondition) {
962
+ const percentage = (perunage) => Math.round(perunage * 100);
963
+ this.logger.warn(`Detected ${staticRunPlans.length} static mutants (${percentage(staticRunPlans.length / runPlans.length)}% of total) that are estimated to take ${percentage(relativeTimeForStaticMutants)}% of the time running the tests!\n You might want to enable "ignoreStatic" to ignore these static mutants for your next run. \n For more information about static mutants visit: https://stryker-mutator.io/docs/mutation-testing-elements/static-mutants\n (disable "${optionsPath("warnings", "slow")}" to ignore this warning)`);
964
+ }
965
+ }
966
+ }
967
+ async incrementalDiff(currentMutants) {
968
+ const { incrementalReport } = this.project;
969
+ if (incrementalReport) {
970
+ const currentFiles = await this.readAllOriginalFiles(currentMutants, this.testCoverage.testsById.values(), Object.keys(incrementalReport.files), Object.keys(incrementalReport.testFiles ?? {}));
971
+ return this.incrementalDiffer.diff(currentMutants, this.testCoverage, incrementalReport, currentFiles);
972
+ }
973
+ return currentMutants;
974
+ }
975
+ async readAllOriginalFiles(...thingsWithFileNamesOrFileNames) {
976
+ const uniqueFileNames = [...new Set(thingsWithFileNamesOrFileNames.flatMap((container) => [...container].map((thing) => typeof thing === "string" ? thing : thing.fileName)).filter(notEmpty).map((fileName) => path.resolve(fileName)))];
977
+ const result = await Promise.all(uniqueFileNames.map(async (fileName) => {
978
+ const originalContent = await this.project.files.get(fileName)?.readOriginal();
979
+ if (originalContent) return [toRelativeNormalizedFileName(fileName), originalContent];
980
+ else return;
981
+ }));
982
+ return new Map(result.filter(notEmpty));
983
+ }
984
+ };
985
+ function calculateTotalTime(testResults) {
986
+ let total = 0;
987
+ for (const test of testResults) total += test.timeSpentMs;
988
+ return total;
989
+ }
990
+ function toTestIds(testResults) {
991
+ const result = [];
992
+ for (const test of testResults) result.push(test.id);
993
+ return result;
994
+ }
995
+ function isEarlyResult(mutantPlan) {
996
+ return mutantPlan.plan === PlanKind.EarlyResult;
997
+ }
998
+ function isRunPlan(mutantPlan) {
999
+ return mutantPlan.plan === PlanKind.Run;
1000
+ }
1001
+ //#endregion
1002
+ //#region src/reporting/mutation-test-report-helper.ts
1003
+ const STRYKER_FRAMEWORK = Object.freeze({
1004
+ name: "StrykerJS",
1005
+ version: strykerVersion,
1006
+ branding: {
1007
+ homepageUrl: "https://stryker-mutator.io",
1008
+ imageUrl: "data:image/svg+xml;utf8,%3Csvg viewBox='0 0 1458 1458' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' clip-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='2'%3E%3Cpath fill='none' d='M0 0h1458v1458H0z'/%3E%3CclipPath id='a'%3E%3Cpath d='M0 0h1458v1458H0z'/%3E%3C/clipPath%3E%3Cg clip-path='url(%23a)'%3E%3Cpath d='M1458 729c0 402.655-326.345 729-729 729S0 1131.655 0 729C0 326.445 326.345 0 729 0s729 326.345 729 729' fill='%23e74c3c' fill-rule='nonzero'/%3E%3Cpath d='M778.349 1456.15L576.6 1254.401l233-105 85-78.668v-64.332l-257-257-44-187-50-208 251.806-82.793L1076.6 389.401l380.14 379.15c-19.681 367.728-311.914 663.049-678.391 687.599z' fill-opacity='.3'/%3E%3Cpath d='M753.4 329.503c41.79 0 74.579 7.83 97.925 25.444 23.571 18.015 41.69 43.956 55.167 77.097l11.662 28.679 165.733-58.183-14.137-32.13c-26.688-60.655-64.896-108.61-114.191-144.011-49.329-35.423-117.458-54.302-204.859-54.302-50.78 0-95.646 7.376-134.767 21.542-40.093 14.671-74.09 34.79-102.239 60.259-28.84 26.207-50.646 57.06-65.496 92.701-14.718 35.052-22.101 72.538-22.101 112.401 0 72.536 20.667 133.294 61.165 182.704 38.624 47.255 98.346 88.037 179.861 121.291 42.257 17.475 78.715 33.125 109.227 46.994 27.193 12.361 49.294 26.124 66.157 41.751 15.309 14.186 26.497 30.584 33.63 49.258 7.721 20.214 11.16 45.69 11.16 76.402 0 28.021-4.251 51.787-13.591 71.219-8.832 18.374-20.171 33.178-34.523 44.219-14.787 11.374-31.193 19.591-49.393 24.466-19.68 5.359-39.14 7.993-58.69 7.993-29.359 0-54.387-3.407-75.182-10.747-20.112-7.013-37.144-16.144-51.259-27.486-13.618-11.009-24.971-23.766-33.744-38.279-9.64-15.8-17.272-31.924-23.032-48.408l-10.965-31.376-161.669 60.585 10.734 30.124c10.191 28.601 24.197 56.228 42.059 82.748 18.208 27.144 41.322 51.369 69.525 72.745 27.695 21.075 60.904 38.218 99.481 51.041 37.777 12.664 82.004 19.159 132.552 19.159 49.998 0 95.818-8.321 137.611-24.622 42.228-16.471 78.436-38.992 108.835-67.291 30.719-28.597 54.631-62.103 71.834-100.642 17.263-38.56 25.923-79.392 25.923-122.248 0-54.339-8.368-100.37-24.208-138.32-16.29-38.759-38.252-71.661-65.948-98.797-26.965-26.418-58.269-48.835-93.858-67.175-33.655-17.241-69.196-33.11-106.593-47.533-35.934-13.429-65.822-26.601-89.948-39.525-22.153-11.868-40.009-24.21-53.547-37.309-11.429-11.13-19.83-23.678-24.718-37.664-5.413-15.49-7.98-33.423-7.98-53.577 0-40.883 11.293-71.522 37.086-90.539 28.443-20.825 64.985-30.658 109.311-30.658z' fill='%23f1c40f' fill-rule='nonzero'/%3E%3Cpath d='M720 0h18v113h-18zM1458 738v-18h-113v18h113zM720 1345h18v113h-18zM113 738v-18H0v18h113z'/%3E%3C/g%3E%3C/svg%3E"
1009
+ }
1010
+ });
1011
+ /**
1012
+ * Converts an internal StrykerJS 0-based location to a schema.Location (1-based).
1013
+ * @param location the StrykerJS 0-based location
1014
+ * @returns the schema.Location (1-based)
1015
+ */
1016
+ function toSchemaLocation(location) {
1017
+ return {
1018
+ end: toSchemaPosition(location.end),
1019
+ start: toSchemaPosition(location.start)
1020
+ };
1021
+ }
1022
+ /**
1023
+ * Converts an internal StrykerJS 0-based position to a schema.Position (1-based).
1024
+ * @param pos the StrykerJS 0-based position
1025
+ * @returns the schema.Position (1-based)
1026
+ */
1027
+ function toSchemaPosition(pos) {
1028
+ return {
1029
+ column: pos.column + 1,
1030
+ line: pos.line + 1
1031
+ };
1032
+ }
1033
+ /**
1034
+ * A helper class to convert and report mutants that survived or get killed
1035
+ */
1036
+ var MutationTestReportHelper = class {
1037
+ reporter;
1038
+ options;
1039
+ project;
1040
+ log;
1041
+ testCoverage;
1042
+ fs;
1043
+ requireFromCwd;
1044
+ runEventSink;
1045
+ runId;
1046
+ resolvedMode;
1047
+ static inject = tokens(reporter, commonTokens.options, project, commonTokens.logger, testCoverage, "fs", requireFromCwd, runEventSink, runId, resolvedMode);
1048
+ constructor(reporter, options, project, log, testCoverage, fs, requireFromCwd, runEventSink, runId, resolvedMode) {
1049
+ this.reporter = reporter;
1050
+ this.options = options;
1051
+ this.project = project;
1052
+ this.log = log;
1053
+ this.testCoverage = testCoverage;
1054
+ this.fs = fs;
1055
+ this.requireFromCwd = requireFromCwd;
1056
+ this.runEventSink = runEventSink;
1057
+ this.runId = runId;
1058
+ this.resolvedMode = resolvedMode;
1059
+ }
1060
+ reportCheckFailed(mutant, checkResult) {
1061
+ const location = toSchemaLocation(mutant.location);
1062
+ return this.reportOne({
1063
+ ...mutant,
1064
+ status: this.checkStatusToResultStatus(checkResult.status),
1065
+ statusReason: checkResult.reason,
1066
+ location
1067
+ });
1068
+ }
1069
+ reportMutantStatus(mutant, status) {
1070
+ const location = toSchemaLocation(mutant.location);
1071
+ return this.reportOne({
1072
+ ...mutant,
1073
+ status,
1074
+ location
1075
+ });
1076
+ }
1077
+ reportMutantRunResult(mutant, result) {
1078
+ const location = toSchemaLocation(mutant.location);
1079
+ switch (result.status) {
1080
+ case MutantRunStatus.Error: return this.reportOne({
1081
+ ...mutant,
1082
+ status: "RuntimeError",
1083
+ statusReason: result.errorMessage,
1084
+ location
1085
+ });
1086
+ case MutantRunStatus.Killed: return this.reportOne({
1087
+ ...mutant,
1088
+ status: "Killed",
1089
+ testsCompleted: result.nrOfTests,
1090
+ killedBy: result.killedBy,
1091
+ statusReason: result.failureMessage,
1092
+ location
1093
+ });
1094
+ case MutantRunStatus.Timeout: return this.reportOne({
1095
+ ...mutant,
1096
+ status: "Timeout",
1097
+ ...result.reason === void 0 ? {} : { statusReason: result.reason },
1098
+ location
1099
+ });
1100
+ case MutantRunStatus.Survived: return this.reportOne({
1101
+ ...mutant,
1102
+ status: "Survived",
1103
+ testsCompleted: result.nrOfTests,
1104
+ location
1105
+ });
1106
+ }
1107
+ }
1108
+ reportOne(result) {
1109
+ this.reporter.onMutantTested(result);
1110
+ return result;
1111
+ }
1112
+ checkStatusToResultStatus(status) {
1113
+ switch (status) {
1114
+ case CheckStatus.CompileError: return "CompileError";
1115
+ }
1116
+ }
1117
+ async reportAll(results) {
1118
+ const report = await this.mutationTestReport(results);
1119
+ const metrics = calculateMutationTestMetrics(report);
1120
+ this.reporter.onMutationTestReportReady(report, metrics);
1121
+ if (this.options.incremental) {
1122
+ await this.fs.mkdir(path.dirname(this.options.incrementalFile), { recursive: true });
1123
+ await this.fs.writeFile(this.options.incrementalFile, JSON.stringify(report, null, 2), "utf-8");
1124
+ }
1125
+ this.determineExitCode(metrics);
1126
+ this.determineTestContribution(report);
1127
+ this.emitVerdict(report);
1128
+ }
1129
+ /**
1130
+ * Emits the verdict envelope as the terminal `verdict` run event at the
1131
+ * end of a run (U4). Core always pushes; whether the event renders — and
1132
+ * as what — is the host sink's decision, never core's. The run id comes
1133
+ * from the host alongside the sink, so the verdict matches the header and
1134
+ * every stream line of this run — never a fresh id (KTD11/U6).
1135
+ */
1136
+ emitVerdict(report) {
1137
+ this.runEventSink({
1138
+ kind: "verdict",
1139
+ ...buildVerdictEnvelope(report, this.resolvedMode.mode, this.resolvedMode.signal, this.runId)
1140
+ });
1141
+ }
1142
+ determineTestContribution(report) {
1143
+ const verdict = judgeTestContribution(report, this.options["requireTestContribution"], this.options.disableBail);
1144
+ if (verdict === void 0) {
1145
+ this.log.debug("No test contribution required. Won't fail the build for a test file that kills nothing another test file does not also kill. Set `requireTestContribution` to change this behavior.");
1146
+ return;
1147
+ }
1148
+ if (!verdict.failed) {
1149
+ this.log.info(verdict.message);
1150
+ return;
1151
+ }
1152
+ this.log.error(`${verdict.message}\nSetting exit code to 1 (failure).`);
1153
+ this.log.info("(sharpen or delete them, or set `requireTestContribution = null` to prevent this error in the future)");
1154
+ setPendingExitClass(1);
1155
+ }
1156
+ determineExitCode(metrics) {
1157
+ const { mutationScore } = metrics.systemUnderTestMetrics.metrics;
1158
+ const breaking = this.options.thresholds.break;
1159
+ const formattedScore = mutationScore.toFixed(2);
1160
+ if (typeof breaking === "number") {
1161
+ if (mutationScore < breaking) {
1162
+ this.log.error(`Final mutation score ${formattedScore} under breaking threshold ${String(breaking)}, setting exit code to 1 (failure).`);
1163
+ this.log.info("(improve mutation score or set `thresholds.break = null` to prevent this error in the future)");
1164
+ setPendingExitClass(1);
1165
+ } else this.log.info(`Final mutation score of ${formattedScore} is greater than or equal to break threshold ${String(breaking)}`);
1166
+ } else this.log.debug("No breaking threshold configured. Won't fail the build no matter how low your mutation score is. Set `thresholds.break` to change this behavior.");
1167
+ }
1168
+ async mutationTestReport(results) {
1169
+ const testIdMap = new Map([...this.testCoverage.testsById.values()].map((test, index) => [test.id, index.toString()]));
1170
+ const remapTestId = (id) => testIdMap.get(id) ?? id;
1171
+ const remapTestIds = (ids) => ids?.map(remapTestId);
1172
+ return {
1173
+ files: await this.toFileResults(results, remapTestIds),
1174
+ schemaVersion: "1.0",
1175
+ thresholds: this.options.thresholds,
1176
+ testFiles: await this.toTestFiles(remapTestId),
1177
+ projectRoot: process.cwd(),
1178
+ config: this.options,
1179
+ framework: {
1180
+ ...STRYKER_FRAMEWORK,
1181
+ dependencies: this.discoverDependencies()
1182
+ }
1183
+ };
1184
+ }
1185
+ async toFileResults(results, remapTestIds) {
1186
+ const fileResultsByName = new Map(await Promise.all([...new Set(results.map(({ fileName }) => fileName))].map(async (fileName) => [fileName, await this.toFileResult(fileName)])));
1187
+ return results.reduce((acc, mutantResult) => {
1188
+ const reportFileName = normalizeReportFileName(mutantResult.fileName);
1189
+ let fileResult = acc[reportFileName];
1190
+ if (fileResult === void 0) {
1191
+ const prepared = fileResultsByName.get(mutantResult.fileName);
1192
+ if (prepared === void 0) throw new Error(`No file result prepared for ${mutantResult.fileName}`);
1193
+ acc[reportFileName] = prepared;
1194
+ fileResult = prepared;
1195
+ }
1196
+ fileResult.mutants.push(this.toMutantResult(mutantResult, remapTestIds));
1197
+ return acc;
1198
+ }, {});
1199
+ }
1200
+ async toTestFiles(remapTestId) {
1201
+ const testFilesByName = new Map(await Promise.all([...new Set([...this.testCoverage.testsById.values()].map(({ fileName }) => fileName))].map(async (fileName) => [normalizeReportFileName(fileName), await this.toTestFile(fileName)])));
1202
+ return [...this.testCoverage.testsById.values()].reduce((acc, testResult) => {
1203
+ const test = this.toTestDefinition(testResult, remapTestId);
1204
+ const reportFileName = normalizeReportFileName(testResult.fileName);
1205
+ let testFile = acc[reportFileName];
1206
+ if (testFile === void 0) {
1207
+ const prepared = testFilesByName.get(reportFileName);
1208
+ if (prepared === void 0) throw new Error(`No test file prepared for ${reportFileName}`);
1209
+ acc[reportFileName] = prepared;
1210
+ testFile = prepared;
1211
+ }
1212
+ testFile.tests.push(test);
1213
+ return acc;
1214
+ }, {});
1215
+ }
1216
+ async toFileResult(fileName) {
1217
+ const fileResult = {
1218
+ language: this.determineLanguage(fileName),
1219
+ mutants: [],
1220
+ source: ""
1221
+ };
1222
+ const sourceFile = this.project.files.get(fileName);
1223
+ if (sourceFile) fileResult.source = await sourceFile.readOriginal();
1224
+ else this.log.warn(normalizeWhitespaces(`File "${fileName}" not found
1225
+ in input files, but did receive mutant result for it. This shouldn't happen`));
1226
+ return fileResult;
1227
+ }
1228
+ async toTestFile(fileName) {
1229
+ const testFile = { tests: [] };
1230
+ if (fileName) {
1231
+ const file = this.project.files.get(fileName);
1232
+ if (file) testFile.source = await file.readOriginal();
1233
+ else this.log.warn(normalizeWhitespaces(`Test file "${fileName}" not found
1234
+ in input files, but did receive test result for it. This shouldn't happen.`));
1235
+ }
1236
+ return testFile;
1237
+ }
1238
+ toTestDefinition(test, remapTestId) {
1239
+ return {
1240
+ id: remapTestId(test.id),
1241
+ name: test.name,
1242
+ ...test.startPosition === void 0 ? {} : { location: { start: toSchemaPosition(test.startPosition) } }
1243
+ };
1244
+ }
1245
+ determineLanguage(name) {
1246
+ switch (path.extname(name).toLowerCase()) {
1247
+ case ".ts":
1248
+ case ".tsx": return "typescript";
1249
+ case ".html":
1250
+ case ".vue": return "html";
1251
+ default: return "javascript";
1252
+ }
1253
+ }
1254
+ toMutantResult(mutantResult, remapTestIds) {
1255
+ const { fileName, location, killedBy, coveredBy, ...apiMutant } = mutantResult;
1256
+ const remappedKilledBy = remapTestIds(killedBy);
1257
+ const remappedCoveredBy = remapTestIds(coveredBy);
1258
+ return {
1259
+ ...apiMutant,
1260
+ ...remappedKilledBy === void 0 ? {} : { killedBy: remappedKilledBy },
1261
+ ...remappedCoveredBy === void 0 ? {} : { coveredBy: remappedCoveredBy },
1262
+ location
1263
+ };
1264
+ }
1265
+ discoverDependencies() {
1266
+ const discover = (specifier) => {
1267
+ try {
1268
+ const version = S.decodeUnknownSync(S.Record(S.String, S.Unknown))(this.requireFromCwd(`${specifier}/package.json`))["version"];
1269
+ return [specifier, typeof version === "string" ? version : JSON.stringify(version) ?? ""];
1270
+ } catch {
1271
+ return;
1272
+ }
1273
+ };
1274
+ return [
1275
+ "@stryker-mutator/mocha-runner",
1276
+ "@stryker-mutator/karma-runner",
1277
+ "@stryker-mutator/jasmine-runner",
1278
+ "@stryker-mutator/jest-runner",
1279
+ "@stryker-mutator/typescript-checker",
1280
+ "karma",
1281
+ "karma-chai",
1282
+ "karma-chrome-launcher",
1283
+ "karma-jasmine",
1284
+ "karma-mocha",
1285
+ "mocha",
1286
+ "jasmine",
1287
+ "jasmine-core",
1288
+ "jest",
1289
+ "react-scripts",
1290
+ "typescript",
1291
+ "@angular/cli",
1292
+ "webpack",
1293
+ "webpack-cli",
1294
+ "ts-jest"
1295
+ ].map(discover).reduce((acc, dependency) => {
1296
+ if (dependency) acc[dependency[0]] = dependency[1];
1297
+ return acc;
1298
+ }, {});
1299
+ }
1300
+ };
1301
+ function normalizeReportFileName(fileName) {
1302
+ if (fileName) return normalizeFileName(path.relative(process.cwd(), fileName));
1303
+ return "";
1304
+ }
1305
+ //#endregion
1306
+ //#region src/test-runner/child-process-test-runner-proxy.ts
1307
+ const MAX_WAIT_FOR_DISPOSE = 2e3;
1308
+ /**
1309
+ * Runs the given test runner in a child process and forwards reports about test results
1310
+ */
1311
+ var ChildProcessTestRunnerProxy = class ChildProcessTestRunnerProxy {
1312
+ worker;
1313
+ log;
1314
+ constructor(options, fileDescriptions, sandboxWorkingDirectory, loggingServerAddress, pluginModulePaths, getLogger, idGenerator) {
1315
+ this.log = getLogger(ChildProcessTestRunnerProxy.name);
1316
+ this.worker = ChildProcessProxy.create(new URL("./child-process-test-runner-worker.mjs", import.meta.url).toString(), loggingServerAddress, options, fileDescriptions, pluginModulePaths, sandboxWorkingDirectory, ChildProcessTestRunnerWorker, options.testRunnerNodeArgs, getLogger, idGenerator);
1317
+ }
1318
+ capabilities() {
1319
+ return this.worker.proxy.capabilities();
1320
+ }
1321
+ init() {
1322
+ return this.worker.proxy.init();
1323
+ }
1324
+ dryRun(options) {
1325
+ return this.worker.proxy.dryRun(options);
1326
+ }
1327
+ mutantRun(options) {
1328
+ return this.worker.proxy.mutantRun(options);
1329
+ }
1330
+ async dispose() {
1331
+ await ExpirableTask.timeout(this.worker.proxy.dispose().catch((error) => {
1332
+ if (!(error instanceof ChildProcessCrashedError)) this.log.warn("An unexpected error occurred during test runner disposal. This might be worth looking into. Stryker will ignore this error.", error);
1333
+ }), MAX_WAIT_FOR_DISPOSE);
1334
+ await this.worker.dispose();
1335
+ }
1336
+ };
1337
+ //#endregion
1338
+ //#region src/test-runner/test-runner-decorator.ts
1339
+ var TestRunnerDecorator = class extends ResourceDecorator {
1340
+ async capabilities() {
1341
+ return this.innerResource.capabilities();
1342
+ }
1343
+ dryRun(options) {
1344
+ return this.innerResource.dryRun(options);
1345
+ }
1346
+ mutantRun(options) {
1347
+ return this.innerResource.mutantRun(options);
1348
+ }
1349
+ };
1350
+ //#endregion
1351
+ //#region src/test-runner/max-test-runner-reuse-decorator.ts
1352
+ /**
1353
+ * Wraps a test runner and implements the retry functionality.
1354
+ */
1355
+ var MaxTestRunnerReuseDecorator = class extends TestRunnerDecorator {
1356
+ runs = 0;
1357
+ restartAfter;
1358
+ constructor(testRunnerProducer, options) {
1359
+ super(testRunnerProducer);
1360
+ this.restartAfter = options.maxTestRunnerReuse || 0;
1361
+ }
1362
+ async mutantRun(options) {
1363
+ this.runs++;
1364
+ if (this.restartAfter > 0 && this.runs > this.restartAfter) {
1365
+ await this.recover();
1366
+ this.runs = 1;
1367
+ }
1368
+ return super.mutantRun(options);
1369
+ }
1370
+ dispose() {
1371
+ this.runs = 0;
1372
+ return super.dispose();
1373
+ }
1374
+ };
1375
+ //#endregion
1376
+ //#region src/test-runner/reload-environment-decorator.ts
1377
+ var ReloadEnvironmentDecorator = class extends TestRunnerDecorator {
1378
+ _capabilities;
1379
+ testEnvironment = 0;
1380
+ async capabilities() {
1381
+ if (!this._capabilities) this._capabilities = await super.capabilities();
1382
+ return this._capabilities;
1383
+ }
1384
+ async dryRun(options) {
1385
+ this.testEnvironment = 1;
1386
+ return super.dryRun(options);
1387
+ }
1388
+ async mutantRun(options) {
1389
+ let newState;
1390
+ if (options.reloadEnvironment) {
1391
+ newState = 2;
1392
+ options.reloadEnvironment = this.testEnvironment !== 0;
1393
+ if (options.reloadEnvironment && !await this.testRunnerIsCapableOfReload()) {
1394
+ await this.recover();
1395
+ options.reloadEnvironment = false;
1396
+ }
1397
+ } else {
1398
+ newState = 1;
1399
+ if (this.testEnvironment === 2) {
1400
+ if (await this.testRunnerIsCapableOfReload()) options.reloadEnvironment = true;
1401
+ else await this.recover();
1402
+ }
1403
+ }
1404
+ const result = await super.mutantRun(options);
1405
+ this.testEnvironment = newState;
1406
+ return result;
1407
+ }
1408
+ async testRunnerIsCapableOfReload() {
1409
+ return (await this.capabilities()).reloadEnvironment;
1410
+ }
1411
+ };
1412
+ //#endregion
1413
+ //#region src/test-runner/retry-rejected-decorator.ts
1414
+ const ERROR_MESSAGE = "Test runner crashed. Tried twice to restart it without any luck. Last time the error message was: ";
1415
+ /**
1416
+ * Implements the retry functionality whenever an internal test runner rejects a promise.
1417
+ */
1418
+ var RetryRejectedDecorator = class extends TestRunnerDecorator {
1419
+ log;
1420
+ constructor(log, producer) {
1421
+ super(producer);
1422
+ this.log = log;
1423
+ }
1424
+ async dryRun(options) {
1425
+ const result = await this.run(() => super.dryRun(options));
1426
+ if (typeof result === "string") return {
1427
+ status: DryRunStatus.Error,
1428
+ errorMessage: result
1429
+ };
1430
+ else return result;
1431
+ }
1432
+ async mutantRun(options) {
1433
+ const result = await this.run(() => super.mutantRun(options));
1434
+ if (typeof result === "string") return {
1435
+ status: MutantRunStatus.Error,
1436
+ errorMessage: result
1437
+ };
1438
+ else return result;
1439
+ }
1440
+ async run(actRun, attemptsLeft = 2, lastError) {
1441
+ if (attemptsLeft > 0) try {
1442
+ return await actRun();
1443
+ } catch (error) {
1444
+ if (error instanceof OutOfMemoryError) this.log.info("Test runner process [%s] ran out of memory. You probably have a memory leak in your tests. Don't worry, Stryker will restart the process, but you might want to investigate this later, because this decreases performance.", error.pid);
1445
+ await this.recover();
1446
+ return this.run(actRun, attemptsLeft - 1, error);
1447
+ }
1448
+ else {
1449
+ await this.recover();
1450
+ return `${ERROR_MESSAGE}${errorToString(lastError)}`;
1451
+ }
1452
+ }
1453
+ };
1454
+ //#endregion
1455
+ //#region src/test-runner/timeout-decorator.ts
1456
+ /**
1457
+ * Wraps a test runner and implements the timeout functionality.
1458
+ */
1459
+ var TimeoutDecorator = class extends TestRunnerDecorator {
1460
+ log;
1461
+ constructor(log, producer) {
1462
+ super(producer);
1463
+ this.log = log;
1464
+ }
1465
+ async dryRun(options) {
1466
+ const result = await this.run(options, () => super.dryRun(options));
1467
+ if (result === ExpirableTask.TimeoutExpired) return { status: DryRunStatus.Timeout };
1468
+ else return result;
1469
+ }
1470
+ async mutantRun(options) {
1471
+ const result = await this.run(options, () => super.mutantRun(options));
1472
+ if (result === ExpirableTask.TimeoutExpired) return { status: MutantRunStatus.Timeout };
1473
+ else return result;
1474
+ }
1475
+ async run(options, actRun) {
1476
+ this.log.debug("Starting timeout timer (%s ms) for a test run", options.timeout);
1477
+ const result = await ExpirableTask.timeout(actRun(), options.timeout);
1478
+ if (result === ExpirableTask.TimeoutExpired) {
1479
+ await this.handleTimeout();
1480
+ return result;
1481
+ } else return result;
1482
+ }
1483
+ async handleTimeout() {
1484
+ this.log.debug("Timeout expired, restarting the process and reporting timeout");
1485
+ await this.recover();
1486
+ }
1487
+ };
1488
+ //#endregion
1489
+ //#region src/test-runner/index.ts
1490
+ createTestRunnerFactory.inject = tokens(commonTokens.options, commonTokens.fileDescriptions, sandbox, loggingServerAddress, commonTokens.getLogger, pluginModulePaths, workerIdGenerator);
1491
+ function createTestRunnerFactory(options, fileDescriptions, sandbox, loggingServerAddress, getLogger, pluginModulePaths, idGenerator) {
1492
+ if (CommandTestRunner.is(options.testRunner)) return () => new RetryRejectedDecorator(getLogger(RetryRejectedDecorator.name), () => new TimeoutDecorator(getLogger(TimeoutDecorator.name), () => new CommandTestRunner(sandbox.workingDirectory, options)));
1493
+ else return () => new RetryRejectedDecorator(getLogger(RetryRejectedDecorator.name), () => new ReloadEnvironmentDecorator(() => new MaxTestRunnerReuseDecorator(() => new TimeoutDecorator(getLogger(TimeoutDecorator.name), () => new ChildProcessTestRunnerProxy(options, fileDescriptions, sandbox.workingDirectory, loggingServerAddress, pluginModulePaths, getLogger, idGenerator)), options)));
1494
+ }
1495
+ //#endregion
1496
+ //#region src/run-stages/4-mutation-test-executor.ts
1497
+ const CHECK_BUFFER_MS = 1e4;
1498
+ /**
1499
+ * Sorting the tests just before running them can yield a significant performance boost,
1500
+ * because it can reduce the number of times a test runner process needs to be recreated.
1501
+ * However, we need to buffer the results in order to be able to sort them.
1502
+ *
1503
+ * This value is very low, since it would halt the test execution otherwise.
1504
+ * @see https://github.com/stryker-mutator/stryker-js/issues/3462
1505
+ */
1506
+ const BUFFER_FOR_SORTING_MS = 0;
1507
+ var MutationTestExecutor = class {
1508
+ reporter;
1509
+ testRunnerPool;
1510
+ checkerPool;
1511
+ mutants;
1512
+ planner;
1513
+ mutationTestReportHelper;
1514
+ log;
1515
+ options;
1516
+ timer;
1517
+ concurrencyTokenProvider;
1518
+ dryRunResult;
1519
+ static inject = tokens(reporter, testRunnerPool, checkerPool, mutants, mutantTestPlanner, mutationTestReportHelper, commonTokens.logger, commonTokens.options, timer, concurrencyTokenProvider, dryRunResult);
1520
+ constructor(reporter, testRunnerPool, checkerPool, mutants, planner, mutationTestReportHelper, log, options, timer, concurrencyTokenProvider, dryRunResult) {
1521
+ this.reporter = reporter;
1522
+ this.testRunnerPool = testRunnerPool;
1523
+ this.checkerPool = checkerPool;
1524
+ this.mutants = mutants;
1525
+ this.planner = planner;
1526
+ this.mutationTestReportHelper = mutationTestReportHelper;
1527
+ this.log = log;
1528
+ this.options = options;
1529
+ this.timer = timer;
1530
+ this.concurrencyTokenProvider = concurrencyTokenProvider;
1531
+ this.dryRunResult = dryRunResult;
1532
+ }
1533
+ async execute() {
1534
+ if (this.options.dryRunOnly) {
1535
+ this.log.info("The dry-run has been completed successfully. No mutations have been executed.");
1536
+ return [];
1537
+ }
1538
+ if (this.dryRunResult.tests.length === 0 && this.options.allowEmpty) {
1539
+ this.logDone();
1540
+ return [];
1541
+ }
1542
+ const mutantTestPlans = await this.planner.makePlan(this.mutants);
1543
+ const { earlyResult$, runMutant$ } = this.executeEarlyResult(from(mutantTestPlans));
1544
+ const { passedMutant$, checkResult$ } = this.executeCheck(runMutant$);
1545
+ const { coveredMutant$, noCoverageResult$ } = this.executeNoCoverage(passedMutant$);
1546
+ const testRunnerResult$ = this.executeRunInTestRunner(coveredMutant$);
1547
+ const results = await lastValueFrom(merge(testRunnerResult$, checkResult$, noCoverageResult$, earlyResult$).pipe(toArray()));
1548
+ await this.mutationTestReportHelper.reportAll(results);
1549
+ await this.reporter.wrapUp();
1550
+ this.logDone();
1551
+ return results;
1552
+ }
1553
+ executeEarlyResult(input$) {
1554
+ const [earlyResultMutants$, runMutant$] = partition(input$.pipe(shareReplay()), isEarlyResult);
1555
+ return {
1556
+ earlyResult$: earlyResultMutants$.pipe(map(({ mutant }) => this.mutationTestReportHelper.reportMutantStatus(mutant, mutant.status))),
1557
+ runMutant$
1558
+ };
1559
+ }
1560
+ executeNoCoverage(input$) {
1561
+ const [noCoverageMatchedMutant$, coveredMutant$] = partition(input$.pipe(shareReplay()), ({ runOptions }) => runOptions.testFilter?.length === 0);
1562
+ return {
1563
+ noCoverageResult$: noCoverageMatchedMutant$.pipe(map(({ mutant }) => this.mutationTestReportHelper.reportMutantStatus(mutant, "NoCoverage"))),
1564
+ coveredMutant$
1565
+ };
1566
+ }
1567
+ executeRunInTestRunner(input$) {
1568
+ const sortedPlan$ = input$.pipe(bufferTime(BUFFER_FOR_SORTING_MS), mergeMap((plans) => plans.sort(reloadEnvironmentLast)));
1569
+ return this.testRunnerPool.schedule(sortedPlan$, async (testRunner, { mutant, runOptions }) => {
1570
+ const result = await testRunner.mutantRun(runOptions);
1571
+ return this.mutationTestReportHelper.reportMutantRunResult(mutant, result);
1572
+ });
1573
+ }
1574
+ logDone() {
1575
+ this.log.info("Done in %s.", this.timer.humanReadableElapsed());
1576
+ }
1577
+ /**
1578
+ * Checks mutants against all configured checkers (if any) and returns steams for failed checks and passed checks respectively
1579
+ * @param input$ The mutant run plans to check
1580
+ */
1581
+ executeCheck(input$) {
1582
+ let checkResult$ = EMPTY;
1583
+ let passedMutant$ = input$;
1584
+ for (const checkerName of this.options.checkers) {
1585
+ const [checkFailedResult$, checkPassedResult$] = partition(this.executeSingleChecker(checkerName, passedMutant$).pipe(shareReplay()), isEarlyResult);
1586
+ passedMutant$ = checkPassedResult$;
1587
+ checkResult$ = concat(checkResult$, checkFailedResult$.pipe(map(({ mutant }) => mutant)));
1588
+ }
1589
+ return {
1590
+ checkResult$,
1591
+ passedMutant$: passedMutant$.pipe(tap$1({ complete: () => {
1592
+ this.checkerPool.dispose().then(() => {
1593
+ this.concurrencyTokenProvider.freeCheckers();
1594
+ }).catch((error) => {
1595
+ this.log.error("An error occurred while disposing checkers: %s", error);
1596
+ });
1597
+ } }))
1598
+ };
1599
+ }
1600
+ /**
1601
+ * Executes the check task for one checker
1602
+ * @param checkerName The name of the checker to execute
1603
+ * @param input$ The mutants tasks to check
1604
+ * @returns An observable stream with early results (check failed) and passed results
1605
+ */
1606
+ executeSingleChecker(checkerName, input$) {
1607
+ const group$ = this.checkerPool.schedule(input$.pipe(bufferTime(CHECK_BUFFER_MS)), (checker, mutants) => checker.group(checkerName, mutants)).pipe(mergeMap((mutantGroups) => mutantGroups));
1608
+ return this.checkerPool.schedule(group$, (checker, group) => checker.check(checkerName, group)).pipe(mergeMap((mutantGroupResults) => mutantGroupResults), map(([mutantRunPlan, checkResult]) => checkResult.status === CheckStatus.Passed ? mutantRunPlan : {
1609
+ plan: PlanKind.EarlyResult,
1610
+ mutant: this.mutationTestReportHelper.reportCheckFailed(mutantRunPlan.mutant, checkResult)
1611
+ }));
1612
+ }
1613
+ };
1614
+ /**
1615
+ * Sorting function that sorts mutant run plans that reload environments last.
1616
+ * This can yield a significant performance boost, because it reduces the times a test runner process needs to restart.
1617
+ * @see https://github.com/stryker-mutator/stryker-js/issues/3462
1618
+ */
1619
+ function reloadEnvironmentLast(a, b) {
1620
+ if (a.plan === PlanKind.Run && b.plan === PlanKind.Run) {
1621
+ if (a.runOptions.reloadEnvironment && !b.runOptions.reloadEnvironment) return 1;
1622
+ if (!a.runOptions.reloadEnvironment && b.runOptions.reloadEnvironment) return -1;
1623
+ return 0;
1624
+ }
1625
+ return 0;
1626
+ }
1627
+ //#endregion
1628
+ //#region src/run-stages/3-dry-run-executor.ts
1629
+ const INITIAL_TEST_RUN_MARKER = "Initial test run";
1630
+ function isFailedTest(testResult) {
1631
+ return testResult.status === TestStatus.Failed;
1632
+ }
1633
+ var DryRunExecutor = class {
1634
+ injector;
1635
+ log;
1636
+ options;
1637
+ timer;
1638
+ concurrencyTokenProvider;
1639
+ sandbox;
1640
+ reporter;
1641
+ static inject = tokens(commonTokens.injector, commonTokens.logger, commonTokens.options, timer, concurrencyTokenProvider, sandbox, reporter);
1642
+ constructor(injector, log, options, timer, concurrencyTokenProvider, sandbox, reporter) {
1643
+ this.injector = injector;
1644
+ this.log = log;
1645
+ this.options = options;
1646
+ this.timer = timer;
1647
+ this.concurrencyTokenProvider = concurrencyTokenProvider;
1648
+ this.sandbox = sandbox;
1649
+ this.reporter = reporter;
1650
+ }
1651
+ async execute() {
1652
+ const testRunnerInjector = this.injector.provideClass(workerIdGenerator, IdGenerator).provideFactory(testRunnerFactory, createTestRunnerFactory).provideValue(testRunnerConcurrencyTokens, this.concurrencyTokenProvider.testRunnerToken$).provideFactory(testRunnerPool, createTestRunnerPool);
1653
+ const testRunnerPool$1 = testRunnerInjector.resolve(testRunnerPool);
1654
+ const { result, timing } = await lastValueFrom(testRunnerPool$1.schedule(of(0), (testRunner) => this.executeDryRun(testRunner)));
1655
+ this.logInitialTestRunSucceeded(result.tests, timing);
1656
+ if (!result.tests.length && !this.options.allowEmpty) throw new ConfigError("No tests were executed. Stryker will exit prematurely. Please check your configuration.");
1657
+ return testRunnerInjector.provideValue(timeOverheadMS, timing.overhead).provideValue(dryRunResult, result).provideValue(requireFromCwd, requireResolve).provideFactory(testCoverage, TestCoverage.from).provideClass(incrementalDiffer, IncrementalDiffer).provideClass(mutantTestPlanner, MutantTestPlanner).provideClass(mutationTestReportHelper, MutationTestReportHelper).provideClass(workerIdGenerator, IdGenerator);
1658
+ }
1659
+ validateResultCompleted(runResult) {
1660
+ switch (runResult.status) {
1661
+ case DryRunStatus.Complete: {
1662
+ const failedTests = runResult.tests.filter(isFailedTest);
1663
+ if (failedTests.length) {
1664
+ this.logFailedTestsInInitialRun(failedTests);
1665
+ throw new ConfigError("There were failed tests in the initial test run.");
1666
+ }
1667
+ return;
1668
+ }
1669
+ case DryRunStatus.Error:
1670
+ this.logErrorsInInitialRun(runResult);
1671
+ break;
1672
+ case DryRunStatus.Timeout: this.logTimeoutInitialRun();
1673
+ }
1674
+ throw new Error("Something went wrong in the initial test run");
1675
+ }
1676
+ async executeDryRun(testRunner) {
1677
+ if (this.options.dryRunOnly) this.log.info("Note: running the dry-run only. No mutations will be tested.");
1678
+ const dryRunTimeout = this.options.dryRunTimeoutMinutes * 1e3 * 60;
1679
+ const project$1 = this.injector.resolve(project);
1680
+ const dryRunFiles = map$1(project$1.filesToMutate, (_, name) => this.sandbox.sandboxFileFor(name));
1681
+ const testFiles = project$1.testFiles.length > 0 ? project$1.testFiles.map((file) => this.sandbox.sandboxFileFor(file)) : void 0;
1682
+ this.options.coverageAnalysis, this.options.disableBail, { ...testFiles ? { testFiles } : {} };
1683
+ this.timer.mark(INITIAL_TEST_RUN_MARKER);
1684
+ this.log.info(`Starting initial test run (${this.options.testRunner} test runner with "${this.options.coverageAnalysis}" coverage analysis). This may take a while.`);
1685
+ this.log.debug(`Using timeout of ${dryRunTimeout} ms.`);
1686
+ const result = await testRunner.dryRun({
1687
+ timeout: dryRunTimeout,
1688
+ coverageAnalysis: this.options.coverageAnalysis,
1689
+ disableBail: this.options.disableBail,
1690
+ files: dryRunFiles,
1691
+ ...testFiles ? { testFiles } : {}
1692
+ });
1693
+ const grossTimeMS = this.timer.elapsedMs(INITIAL_TEST_RUN_MARKER);
1694
+ const capabilities = await testRunner.capabilities();
1695
+ this.validateResultCompleted(result);
1696
+ this.remapSandboxFilesToOriginalFiles(result);
1697
+ const dryRunCompleted = {
1698
+ result,
1699
+ timing: this.calculateTiming(grossTimeMS, result.tests),
1700
+ capabilities
1701
+ };
1702
+ this.reporter.onDryRunCompleted(dryRunCompleted);
1703
+ return dryRunCompleted;
1704
+ }
1705
+ /**
1706
+ * Remaps test files to their respective original names outside the sandbox.
1707
+ * @param dryRunResult the completed result
1708
+ */
1709
+ remapSandboxFilesToOriginalFiles(dryRunResult) {
1710
+ const disableTypeCheckingFileMatcher = new FileMatcher(this.options.disableTypeChecks);
1711
+ dryRunResult.tests.forEach((test) => {
1712
+ if (test.fileName) {
1713
+ test.fileName = this.sandbox.originalFileFor(test.fileName);
1714
+ if (test.startPosition && disableTypeCheckingFileMatcher.matches(test.fileName)) test.startPosition.line--;
1715
+ }
1716
+ });
1717
+ }
1718
+ logInitialTestRunSucceeded(tests, timing) {
1719
+ if (!tests.length) {
1720
+ this.log.info("No tests were found");
1721
+ return;
1722
+ }
1723
+ this.log.info("Initial test run succeeded. Ran %s tests in %s (net %s ms, overhead %s ms).", tests.length, this.timer.humanReadableElapsed(INITIAL_TEST_RUN_MARKER), timing.net, timing.overhead);
1724
+ }
1725
+ /**
1726
+ * Calculates the timing variables for the test run.
1727
+ * grossTime = NetTime + overheadTime
1728
+ *
1729
+ * The overhead time is used to calculate exact timeout values during mutation testing.
1730
+ * See timeoutMS setting in README for more information on this calculation
1731
+ */
1732
+ calculateTiming(grossTimeMS, tests) {
1733
+ const netTimeMS = tests.reduce((total, test) => total + test.timeSpentMs, 0);
1734
+ const overheadTimeMS = grossTimeMS - netTimeMS;
1735
+ return {
1736
+ net: netTimeMS,
1737
+ overhead: overheadTimeMS < 0 ? 0 : overheadTimeMS
1738
+ };
1739
+ }
1740
+ logFailedTestsInInitialRun(failedTests) {
1741
+ let message = "One or more tests failed in the initial test run:";
1742
+ failedTests.forEach((test) => {
1743
+ message += `${EOL}\t${test.name}`;
1744
+ message += `${EOL}\t\t${test.failureMessage}`;
1745
+ });
1746
+ this.log.error(message);
1747
+ }
1748
+ logErrorsInInitialRun(runResult) {
1749
+ const message = `One or more tests resulted in an error:${EOL}\t${runResult.errorMessage}`;
1750
+ this.log.error(message);
1751
+ }
1752
+ logTimeoutInitialRun() {
1753
+ this.log.error("Initial test run timed out!");
1754
+ }
1755
+ };
1756
+ //#endregion
1757
+ //#region src/run-stages/2-mutant-instrumenter-executor.ts
1758
+ var MutantInstrumenterExecutor = class {
1759
+ injector;
1760
+ project;
1761
+ options;
1762
+ pluginCreator;
1763
+ static inject = tokens(commonTokens.injector, project, commonTokens.options, pluginCreator);
1764
+ constructor(injector, project, options, pluginCreator) {
1765
+ this.injector = injector;
1766
+ this.project = project;
1767
+ this.options = options;
1768
+ this.pluginCreator = pluginCreator;
1769
+ }
1770
+ async execute() {
1771
+ const instrumenter = this.injector.injectFunction(createInstrumenter);
1772
+ const instrumenterOptions = {
1773
+ ignorers: this.options.ignorers.map((name) => this.pluginCreator.create(PluginKind.Ignore, name)),
1774
+ ...this.options.mutator,
1775
+ plugins: this.options.mutator.plugins === null ? null : [...this.options.mutator.plugins],
1776
+ excludedMutations: [...this.options.mutator.excludedMutations]
1777
+ };
1778
+ const instrumentResult = await instrumenter.instrument(await this.readFilesToMutate(), instrumenterOptions);
1779
+ const preprocess = this.injector.injectFunction(createPreprocessor);
1780
+ this.writeInstrumentedFiles(instrumentResult);
1781
+ await preprocess.preprocess(this.project);
1782
+ const concurrencyTokenProviderProvider = this.injector.provideClass(concurrencyTokenProvider, ConcurrencyTokenProvider);
1783
+ const concurrencyTokenProvider$1 = concurrencyTokenProviderProvider.resolve(concurrencyTokenProvider);
1784
+ const checkerPoolProvider = concurrencyTokenProviderProvider.provideValue(checkerConcurrencyTokens, concurrencyTokenProvider$1.checkerToken$).provideClass(workerIdGenerator, IdGenerator).provideFactory(checkerFactory, createCheckerFactory).provideFactory(checkerPool, createCheckerPool);
1785
+ await checkerPoolProvider.resolve(checkerPool).init();
1786
+ const dryRunProvider = checkerPoolProvider.provideClass(sandbox, Sandbox).provideValue(mutants, instrumentResult.mutants);
1787
+ await dryRunProvider.resolve(sandbox).init();
1788
+ return dryRunProvider;
1789
+ }
1790
+ readFilesToMutate() {
1791
+ return Promise.all([...this.project.filesToMutate.values()].map((file) => file.toInstrumenterFile()));
1792
+ }
1793
+ writeInstrumentedFiles(instrumentResult) {
1794
+ for (const { name, content } of Object.values(instrumentResult.files)) {
1795
+ const file = this.project.files.get(name);
1796
+ if (file === void 0) throw new Error(`Instrumented file "${name}" is missing from the project's file map.`);
1797
+ file.setContent(content);
1798
+ }
1799
+ }
1800
+ };
1801
+ //#endregion
1802
+ //#region src/stryker.ts
1803
+ /**
1804
+ * The main Stryker class.
1805
+ * It provides a single `runMutationTest()` function which runs mutation testing:
1806
+ */
1807
+ var Stryker = class Stryker {
1808
+ cliOptions;
1809
+ hostOptions;
1810
+ injectorFactory;
1811
+ /**
1812
+ * @class
1813
+ * @param cliOptions The cli options.
1814
+ * @param hostOptions What the host resolved for this run, see {@link StrykerHostOptions}.
1815
+ * @param injectorFactory The injector factory, for testing purposes only
1816
+ */
1817
+ constructor(cliOptions, hostOptions, injectorFactory = createInjector) {
1818
+ this.cliOptions = cliOptions;
1819
+ this.hostOptions = hostOptions;
1820
+ this.injectorFactory = injectorFactory;
1821
+ }
1822
+ async runMutationTest() {
1823
+ const rootInjector = this.injectorFactory();
1824
+ try {
1825
+ const prepareInjector = provideLogging(await provideLoggingBackend(rootInjector, this.hostOptions.loggerConsoleOut, this.hostOptions.showColors)).provideValue(reporterOverride, void 0).provideValue(runEventSink, this.hostOptions.runEventSink).provideValue(runId, this.hostOptions.runId).provideValue(resolvedMode, this.hostOptions.resolvedMode).provideValue(progressEnabled, this.hostOptions.progressEnabled).provideValue(clearTextEnabled, this.hostOptions.clearTextEnabled).provideValue(runStartedAt, this.hostOptions.runStartedAt).provideValue(reporterPluginModules, this.hostOptions.reporterPluginModules);
1826
+ return await Stryker.run(prepareInjector, {
1827
+ cliOptions: this.cliOptions,
1828
+ targetMutatePatterns: void 0
1829
+ });
1830
+ } finally {
1831
+ await rootInjector.dispose();
1832
+ }
1833
+ }
1834
+ /**
1835
+ * Does the actual mutation testing.
1836
+ * Note: this is a public static method, so it can be reused from `StrykerServer`
1837
+ * @internal
1838
+ */
1839
+ static async run(mutationRunInjector, args) {
1840
+ const sink = mutationRunInjector.resolve(runEventSink);
1841
+ const runStartedAt$1 = mutationRunInjector.resolve(runStartedAt);
1842
+ const emitPhase = (phase) => {
1843
+ sink({
1844
+ kind: "phase",
1845
+ phase,
1846
+ elapsedMs: Date.now() - runStartedAt$1
1847
+ });
1848
+ };
1849
+ try {
1850
+ emitPhase("prepare");
1851
+ const mutantInstrumenterInjector = await mutationRunInjector.injectClass(PrepareExecutor).execute(args);
1852
+ try {
1853
+ emitPhase("instrument");
1854
+ const dryRunExecutorInjector = await mutantInstrumenterInjector.injectClass(MutantInstrumenterExecutor).execute();
1855
+ emitPhase("dry-run");
1856
+ const mutationRunExecutorInjector = await dryRunExecutorInjector.injectClass(DryRunExecutor).execute();
1857
+ emitPhase("mutation-test");
1858
+ return await mutationRunExecutorInjector.injectClass(MutationTestExecutor).execute();
1859
+ } catch (error) {
1860
+ const cleanTempDir = mutantInstrumenterInjector.resolve(commonTokens.options).cleanTempDir;
1861
+ if (cleanTempDir === false || cleanTempDir === true) {
1862
+ mutationRunInjector.resolve(commonTokens.getLogger)(Stryker.name).debug("Not removing the temp dir because an error occurred");
1863
+ mutantInstrumenterInjector.resolve(temporaryDirectory).removeDuringDisposal = false;
1864
+ }
1865
+ throw error;
1866
+ }
1867
+ } catch (error) {
1868
+ const log = mutationRunInjector.resolve(commonTokens.getLogger)(Stryker.name);
1869
+ const cause = retrieveCause(error);
1870
+ if (cause instanceof ConfigError) log.error(cause.message);
1871
+ else {
1872
+ log.error("Unexpected error occurred while running Stryker", error);
1873
+ log.info("This might be a known problem with a solution documented in our troubleshooting guide.");
1874
+ log.info("You can find it at https://stryker-mutator.io/docs/stryker-js/troubleshooting/");
1875
+ if (!log.isTraceEnabled()) log.info("Still having trouble figuring out what went wrong? Try `npx stryker run --fileLogLevel trace --logLevel debug` to get some more info.");
1876
+ }
1877
+ throw cause;
1878
+ }
1879
+ }
1880
+ };
1881
+ //#endregion
1882
+ //#region src/index.ts
1883
+ var src_default = Stryker;
1884
+ //#endregion
1885
+ export { Stryker, src_default as default };