@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
@@ -0,0 +1,588 @@
1
+ import { F as pluginModulePaths, O as loggingServerAddress, P as pluginCreator, Q as testRunnerFactory, Z as testRunnerConcurrencyTokens, h as checkerFactory, it as workerIdGenerator, m as checkerConcurrencyTokens, s as kill } from "./plugins-Nx86IJQW.mjs";
2
+ import { i as WorkerMessageSchema, n as ProtocolEncodeError, r as ParentMessageSchema } from "./child-process-proxy-worker-lx1M5Mz3.mjs";
3
+ import "@systemfsoftware/stryker-js-plugin-api/core";
4
+ import { PluginKind, commonTokens, tokens } from "@systemfsoftware/stryker-js-plugin-api/plugin";
5
+ import { tokens as tokens$1 } from "typed-inject";
6
+ import { ExpirableTask, StrykerError, Task, isErrnoException, notEmpty } from "@stryker-mutator/util";
7
+ import { URL, fileURLToPath } from "url";
8
+ import "@systemfsoftware/stryker-js-plugin-api/logging";
9
+ import os, { EOL } from "os";
10
+ import * as S from "effect/Schema";
11
+ import { BehaviorSubject, ReplaySubject, Subject, filter, ignoreElements, lastValueFrom, mergeMap, range, takeUntil, tap, zip } from "rxjs";
12
+ import childProcess from "child_process";
13
+ import "@systemfsoftware/stryker-js-plugin-api/test-runner";
14
+ import "@systemfsoftware/stryker-js-plugin-api/check";
15
+ import * as Exit from "effect/Exit";
16
+ //#region src/worker-pool/concurrency-token-provider.ts
17
+ var ConcurrencyTokenProvider = class {
18
+ log;
19
+ concurrencyCheckers;
20
+ concurrencyTestRunners;
21
+ testRunnerTokenSubject = new ReplaySubject();
22
+ get testRunnerToken$() {
23
+ return this.testRunnerTokenSubject;
24
+ }
25
+ checkerToken$;
26
+ static inject = tokens$1(commonTokens.options, commonTokens.logger);
27
+ constructor(options, log) {
28
+ this.log = log;
29
+ const availableParallelism = os.availableParallelism();
30
+ const concurrency = this.computeConcurrency(options.concurrency, availableParallelism);
31
+ if (options.checkers.length > 0) {
32
+ this.concurrencyCheckers = Math.max(Math.ceil(concurrency / 2), 1);
33
+ this.checkerToken$ = range(this.concurrencyCheckers);
34
+ this.concurrencyTestRunners = Math.max(Math.floor(concurrency / 2), 1);
35
+ log.info("Creating %s checker process(es) and %s test runner process(es).", this.concurrencyCheckers, this.concurrencyTestRunners);
36
+ } else {
37
+ this.concurrencyCheckers = 0;
38
+ this.checkerToken$ = range(1);
39
+ this.concurrencyTestRunners = concurrency;
40
+ log.info("Creating %s test runner process(es).", this.concurrencyTestRunners);
41
+ }
42
+ Array.from({ length: this.concurrencyTestRunners }).forEach(() => this.testRunnerTokenSubject.next(this.tick()));
43
+ }
44
+ computeConcurrency(concurrencyOption, availableParallelism) {
45
+ if (typeof concurrencyOption === "string") {
46
+ const percentageMatch = concurrencyOption.match(/^(100|[1-9]?[0-9])%$/);
47
+ if (percentageMatch && percentageMatch[1] !== void 0) {
48
+ const percentage = parseInt(percentageMatch[1], 10);
49
+ const computed = Math.max(1, Math.round(availableParallelism * percentage / 100));
50
+ this.log.debug("Computed concurrency %s from \"%s\" based on %s available parallelism.", computed, concurrencyOption, availableParallelism);
51
+ return computed;
52
+ }
53
+ }
54
+ if (typeof concurrencyOption === "number") return concurrencyOption;
55
+ return availableParallelism > 4 ? availableParallelism - 1 : availableParallelism;
56
+ }
57
+ freeCheckers() {
58
+ if (this.concurrencyCheckers > 0) {
59
+ this.log.debug("Checking done, creating %s additional test runner process(es)", this.concurrencyCheckers);
60
+ for (let i = 0; i < this.concurrencyCheckers; i++) this.testRunnerTokenSubject.next(this.tick());
61
+ this.testRunnerTokenSubject.complete();
62
+ }
63
+ }
64
+ count = 0;
65
+ tick() {
66
+ return this.count++;
67
+ }
68
+ dispose() {
69
+ this.testRunnerTokenSubject.complete();
70
+ }
71
+ };
72
+ //#endregion
73
+ //#region src/worker-pool/pool.ts
74
+ const MAX_CONCURRENT_INIT = 2;
75
+ createTestRunnerPool.inject = tokens$1(testRunnerFactory, testRunnerConcurrencyTokens);
76
+ function createTestRunnerPool(factory, concurrencyToken$) {
77
+ return new Pool(factory, concurrencyToken$);
78
+ }
79
+ createCheckerPool.inject = tokens$1(checkerFactory, checkerConcurrencyTokens);
80
+ function createCheckerPool(factory, concurrencyToken$) {
81
+ return new Pool(factory, concurrencyToken$);
82
+ }
83
+ /**
84
+ * Represents a work item: an input with a task and with a `result$` observable where the result (exactly one) will be streamed to.
85
+ */
86
+ var WorkItem = class {
87
+ input;
88
+ task;
89
+ resultSubject = new Subject();
90
+ result$ = this.resultSubject.asObservable();
91
+ /**
92
+ * @param input The input to the ask
93
+ * @param task The task, where a resource and input is presented
94
+ */
95
+ constructor(input, task) {
96
+ this.input = input;
97
+ this.task = task;
98
+ }
99
+ async execute(resource) {
100
+ try {
101
+ const output = await this.task(resource, this.input);
102
+ this.resultSubject.next(output);
103
+ this.resultSubject.complete();
104
+ } catch (err) {
105
+ this.resultSubject.error(err);
106
+ }
107
+ }
108
+ reject(error) {
109
+ this.resultSubject.error(error);
110
+ }
111
+ complete() {
112
+ this.resultSubject.complete();
113
+ }
114
+ };
115
+ /**
116
+ * Represents a pool of resources. Use `schedule` to schedule work to be executed on the resources.
117
+ * The pool will automatically recycle the resources, but will make sure only one task is executed
118
+ * on one resource at any one time. Creates as many resources as the concurrency tokens allow.
119
+ * Also takes care of the initialing of the resources (with `init()`)
120
+ */
121
+ var Pool = class {
122
+ initSubject = new ReplaySubject();
123
+ disposedSubject = new BehaviorSubject(false);
124
+ dispose$ = this.disposedSubject.pipe(filter((isDisposed) => isDisposed));
125
+ createdResources = [];
126
+ todoSubject = new ReplaySubject();
127
+ constructor(factory, concurrencyToken$) {
128
+ const resourcesSubject = new Subject();
129
+ zip(resourcesSubject, this.todoSubject).pipe(mergeMap(async ([resource, workItem]) => {
130
+ await workItem.execute(resource);
131
+ resourcesSubject.next(resource);
132
+ }), ignoreElements(), takeUntil(this.dispose$)).subscribe({ error: (error) => {
133
+ this.todoSubject.subscribe((workItem) => workItem.reject(error));
134
+ } });
135
+ concurrencyToken$.pipe(takeUntil(this.dispose$), mergeMap(async () => {
136
+ if (this.disposedSubject.value) return;
137
+ const resource = factory();
138
+ this.createdResources.push(resource);
139
+ await resource.init?.();
140
+ return resource;
141
+ }, MAX_CONCURRENT_INIT), filter(notEmpty), tap({
142
+ complete: () => {
143
+ this.initSubject.next();
144
+ this.initSubject.complete();
145
+ },
146
+ error: (err) => {
147
+ this.initSubject.error(err);
148
+ }
149
+ })).subscribe({
150
+ next: (resource) => resourcesSubject.next(resource),
151
+ error: (err) => resourcesSubject.error(err)
152
+ });
153
+ }
154
+ /**
155
+ * Returns a promise that resolves if all concurrency tokens have resulted in initialized resources.
156
+ * This is optional, resources will get initialized either way.
157
+ */
158
+ async init() {
159
+ await lastValueFrom(this.initSubject);
160
+ }
161
+ /**
162
+ * Schedules a task to be executed on resources in the pool. Each input is paired with a resource, which allows async work to be done.
163
+ * @param input$ The inputs to pair up with a resource.
164
+ * @param task The task to execute on each resource
165
+ */
166
+ schedule(input$, task) {
167
+ return input$.pipe(mergeMap((input) => {
168
+ const workItem = new WorkItem(input, task);
169
+ this.todoSubject.next(workItem);
170
+ return workItem.result$;
171
+ }));
172
+ }
173
+ /**
174
+ * Dispose the pool
175
+ */
176
+ async dispose() {
177
+ if (!this.disposedSubject.value) {
178
+ this.disposedSubject.next(true);
179
+ this.todoSubject.subscribe((workItem) => workItem.complete());
180
+ this.todoSubject.complete();
181
+ await Promise.all(this.createdResources.map((resource) => resource.dispose?.()));
182
+ }
183
+ }
184
+ };
185
+ //#endregion
186
+ //#region src/worker-pool/resource-decorator.ts
187
+ var ResourceDecorator = class {
188
+ producer;
189
+ innerResource;
190
+ constructor(producer) {
191
+ this.producer = producer;
192
+ this.innerResource = producer();
193
+ }
194
+ async init() {
195
+ await this.innerResource.init?.();
196
+ }
197
+ async dispose() {
198
+ await this.innerResource.dispose?.();
199
+ }
200
+ /**
201
+ * Disposes the current test runner and creates a new one
202
+ * To be used in decorators that need recreation.
203
+ */
204
+ async recover() {
205
+ await this.dispose();
206
+ this.innerResource = this.producer();
207
+ return this.init();
208
+ }
209
+ };
210
+ //#endregion
211
+ //#region src/checker/checker-facade.ts
212
+ function toMap(mutantRunPlans) {
213
+ return new Map(mutantRunPlans.map((mutant) => [mutant.mutant.id, mutant]));
214
+ }
215
+ var CheckerFacade = class extends ResourceDecorator {
216
+ async check(checkerName, mutantRunPlans) {
217
+ const innerCheckerResult = Object.entries(await this.innerResource.check(checkerName, mutantRunPlans.map((mr) => mr.mutant)));
218
+ const mutantRunPlanMap = toMap(mutantRunPlans);
219
+ const results = innerCheckerResult.map(([id, res]) => {
220
+ const mutantRunPlan = mutantRunPlanMap.get(id);
221
+ if (!mutantRunPlan) throw new Error(`Checker "${checkerName}" returned a check result for mutant id "${id}", but a check wasn't requested for it. Stryker asked to check mutant ids: ${mutantRunPlans.map(({ mutant }) => mutant.id).join(",")}`);
222
+ return [mutantRunPlan, res];
223
+ });
224
+ if (mutantRunPlans.length > results.length) {
225
+ const resultIds = new Set(results.map(([{ mutant }]) => mutant.id));
226
+ const missingIds = mutantRunPlans.map(({ mutant }) => mutant.id).filter((id) => !resultIds.has(id));
227
+ throw new Error(`Checker "${checkerName}" was missing check results for mutant ids "${missingIds.join(",")}", while Stryker asked to check them`);
228
+ }
229
+ return results;
230
+ }
231
+ async group(checkerName, mutantRunPlans) {
232
+ const mutantIdGroups = await this.innerResource.group(checkerName, mutantRunPlans.map((mr) => mr.mutant));
233
+ const mutantRunPlanMap = toMap(mutantRunPlans);
234
+ const groupedMutantIds = /* @__PURE__ */ new Set();
235
+ const groups = mutantIdGroups.map((group) => group.map((id) => {
236
+ const mutantRunPlan = mutantRunPlanMap.get(id);
237
+ groupedMutantIds.add(id);
238
+ if (!mutantRunPlan) throw new Error(`Checker "${checkerName}" returned a group result for mutant id "${id}", but a group wasn't requested for it. Stryker asked to group mutant ids: ${mutantRunPlans.map(({ mutant }) => mutant.id).join(",")}!`);
239
+ return mutantRunPlan;
240
+ }));
241
+ if (mutantRunPlans.length > groupedMutantIds.size) {
242
+ const missingIds = mutantRunPlans.map(({ mutant }) => mutant.id).filter((id) => !groupedMutantIds.has(id));
243
+ throw new Error(`Checker "${checkerName}" was missing group results for mutant ids "${missingIds.join(",")}", while Stryker asked to group them!`);
244
+ }
245
+ return groups;
246
+ }
247
+ };
248
+ //#endregion
249
+ //#region src/worker-pool/child-process-crashed-error.ts
250
+ var ChildProcessCrashedError = class ChildProcessCrashedError extends StrykerError {
251
+ pid;
252
+ exitCode;
253
+ signal;
254
+ constructor(pid, message, exitCode, signal, innerError) {
255
+ super(message, innerError);
256
+ this.pid = pid;
257
+ this.exitCode = exitCode;
258
+ this.signal = signal;
259
+ Error.captureStackTrace(this, ChildProcessCrashedError);
260
+ Object.setPrototypeOf(this, ChildProcessCrashedError.prototype);
261
+ }
262
+ };
263
+ //#endregion
264
+ //#region src/worker-pool/out-of-memory-error.ts
265
+ var OutOfMemoryError = class OutOfMemoryError extends ChildProcessCrashedError {
266
+ constructor(pid, exitCode) {
267
+ super(pid, `Process ${pid} ran out of memory`, exitCode);
268
+ this.message = "Process ";
269
+ Error.captureStackTrace(this, OutOfMemoryError);
270
+ Object.setPrototypeOf(this, OutOfMemoryError.prototype);
271
+ }
272
+ };
273
+ //#endregion
274
+ //#region src/worker-pool/string-builder.ts
275
+ const DEFAULT_MAX_SIZE = 8192;
276
+ var StringBuilder = class {
277
+ currentLength = 0;
278
+ strings = [];
279
+ maxSize = DEFAULT_MAX_SIZE;
280
+ append(str) {
281
+ this.strings.push(str);
282
+ this.currentLength += str.length;
283
+ while (this.currentLength > this.maxSize && this.strings.length > 1) {
284
+ const shifted = this.strings.shift();
285
+ if (shifted !== void 0) this.currentLength -= shifted.length;
286
+ }
287
+ }
288
+ toString() {
289
+ return this.strings.join("");
290
+ }
291
+ static concat(...builders) {
292
+ return builders.map((b) => b.toString()).filter(Boolean).join(EOL);
293
+ }
294
+ };
295
+ //#endregion
296
+ //#region src/worker-pool/string-utils.ts
297
+ function padLeft(input) {
298
+ return input.split("\n").map((str) => " " + str).join("\n");
299
+ }
300
+ //#endregion
301
+ //#region src/worker-pool/child-process-proxy.ts
302
+ /**
303
+ * This half's two directions, built once from the declared schemas. They take
304
+ * `unknown` and return an `Exit`, which is what makes them the *only* codec this
305
+ * half needs: the envelope performs the narrowing, so a separate `S.Json` check
306
+ * on `args` would guard the same bytes twice, and a decode failure on a process
307
+ * boundary is an expected outcome this code answers for rather than an exception
308
+ * that leaves the type unmarked.
309
+ */
310
+ const encodeWorkerMessage = S.encodeUnknownExit(S.fromJsonString(WorkerMessageSchema));
311
+ const decodeParentMessage = S.decodeUnknownExit(S.fromJsonString(ParentMessageSchema));
312
+ const BROKEN_PIPE_ERROR_CODE = "EPIPE";
313
+ const IPC_CHANNEL_CLOSED_ERROR_CODE = "ERR_IPC_CHANNEL_CLOSED";
314
+ const TIMEOUT_FOR_DISPOSE = 2e3;
315
+ var ChildProcessProxy = class ChildProcessProxy {
316
+ proxy;
317
+ worker;
318
+ initTask;
319
+ disposeTask;
320
+ fatalError;
321
+ workerTasks = /* @__PURE__ */ new Map();
322
+ workerTaskCounter = 0;
323
+ log;
324
+ stdoutBuilder = new StringBuilder();
325
+ stderrBuilder = new StringBuilder();
326
+ isDisposed = false;
327
+ initMessage;
328
+ constructor(modulePath, namedExport, loggingServerAddress, options, fileDescriptions, pluginModulePaths, workingDirectory, logger, execArgv, idGenerator) {
329
+ const workerId = idGenerator.next().toString();
330
+ this.worker = childProcess.fork(fileURLToPath(new URL("./child-process-proxy-worker-main.mjs", import.meta.url)), {
331
+ silent: true,
332
+ execArgv,
333
+ env: {
334
+ STRYKER_MUTATOR_WORKER: workerId,
335
+ ...process.env
336
+ }
337
+ });
338
+ this.initTask = new Task();
339
+ this.log = logger;
340
+ this.log.debug("Started %s in worker process %s with pid %s %s", namedExport, workerId, this.worker.pid, execArgv.length ? ` (using args ${execArgv.join(" ")})` : "");
341
+ this.worker.on("close", this.handleUnexpectedExit);
342
+ this.worker.on("error", this.handleError);
343
+ this.initMessage = {
344
+ kind: 0,
345
+ loggingServerAddress,
346
+ options,
347
+ fileDescriptions,
348
+ pluginModulePaths,
349
+ namedExport,
350
+ modulePath,
351
+ workingDirectory
352
+ };
353
+ this.listenForMessages();
354
+ this.listenToStdoutAndStderr();
355
+ this.proxy = this.initProxy();
356
+ }
357
+ /**
358
+ * @description Creates a proxy where each function of the object created using the constructorFunction arg is ran inside of a child process
359
+ */
360
+ static create(modulePath, loggingServerAddress, options, fileDescriptions, pluginModulePaths, workingDirectory, injectableClass, execArgv, getLogger, idGenerator) {
361
+ return new ChildProcessProxy(modulePath, injectableClass.name, loggingServerAddress, options, fileDescriptions, pluginModulePaths, workingDirectory, getLogger(ChildProcessProxy.name), [...execArgv], idGenerator);
362
+ }
363
+ send(message) {
364
+ const encoded = encodeWorkerMessage(message);
365
+ if (Exit.isFailure(encoded)) throw new ProtocolEncodeError({ reason: String(encoded.cause) });
366
+ this.worker.send(encoded.value);
367
+ }
368
+ initProxy() {
369
+ return S.decodeUnknownSync(S.declare((input) => input !== null && typeof input === "object" && !Array.isArray(input), { description: "The runtime promisified facade of a child-process worker" }))(new Proxy({}, { get: (_, propertyKey) => {
370
+ if (typeof propertyKey === "string") return this.forward(propertyKey);
371
+ else return;
372
+ } }));
373
+ }
374
+ forward(methodName) {
375
+ return async (...args) => {
376
+ if (this.fatalError) return Promise.reject(this.fatalError);
377
+ else {
378
+ const workerTask = new Task();
379
+ const correlationId = this.workerTaskCounter++;
380
+ this.workerTasks.set(correlationId, workerTask);
381
+ this.initTask.promise.then(() => {
382
+ this.send({
383
+ args,
384
+ correlationId,
385
+ kind: 1,
386
+ methodName
387
+ });
388
+ }).catch((error) => {
389
+ workerTask.reject(error);
390
+ });
391
+ return workerTask.promise;
392
+ }
393
+ };
394
+ }
395
+ listenForMessages() {
396
+ this.worker.on("message", (serializedMessage) => {
397
+ const decoded = decodeParentMessage(serializedMessage);
398
+ if (Exit.isFailure(decoded)) {
399
+ this.fatalError = new ChildProcessCrashedError(this.worker.pid, String(decoded.cause));
400
+ return;
401
+ }
402
+ const message = decoded.value;
403
+ switch (message.kind) {
404
+ case 0:
405
+ this.send(this.initMessage);
406
+ break;
407
+ case 1:
408
+ this.initTask.resolve(void 0);
409
+ break;
410
+ case 3: {
411
+ const task = this.workerTasks.get(message.correlationId);
412
+ if (task !== void 0) {
413
+ task.resolve(message.result);
414
+ this.workerTasks.delete(message.correlationId);
415
+ }
416
+ break;
417
+ }
418
+ case 4: {
419
+ const task = this.workerTasks.get(message.correlationId);
420
+ if (task !== void 0) {
421
+ task.reject(new StrykerError(message.error));
422
+ this.workerTasks.delete(message.correlationId);
423
+ }
424
+ break;
425
+ }
426
+ case 5:
427
+ if (this.disposeTask) this.disposeTask.resolve(void 0);
428
+ break;
429
+ case 2:
430
+ this.fatalError = new StrykerError(message.error);
431
+ this.reportError(this.fatalError);
432
+ this.dispose();
433
+ break;
434
+ default: this.logUnidentifiedMessage(message);
435
+ }
436
+ });
437
+ }
438
+ listenToStdoutAndStderr() {
439
+ const handleData = (builder) => (data) => {
440
+ const output = data.toString();
441
+ builder.append(output);
442
+ this.log.trace(output);
443
+ };
444
+ if (this.worker.stdout) this.worker.stdout.on("data", handleData(this.stdoutBuilder));
445
+ if (this.worker.stderr) this.worker.stderr.on("data", handleData(this.stderrBuilder));
446
+ }
447
+ get stdout() {
448
+ return this.stdoutBuilder.toString();
449
+ }
450
+ get stderr() {
451
+ return this.stderrBuilder.toString();
452
+ }
453
+ reportError(error) {
454
+ const onGoingWorkerTasks = [...this.workerTasks.values()].filter((task) => !task.isCompleted);
455
+ if (!this.initTask.isCompleted) onGoingWorkerTasks.push(this.initTask);
456
+ if (onGoingWorkerTasks.length) onGoingWorkerTasks.forEach((task) => task.reject(error));
457
+ }
458
+ handleUnexpectedExit = (code, signal) => {
459
+ this.isDisposed = true;
460
+ const output = StringBuilder.concat(this.stderrBuilder, this.stdoutBuilder);
461
+ if (processOutOfMemory()) {
462
+ const oom = new OutOfMemoryError(this.worker.pid, code);
463
+ this.fatalError = oom;
464
+ this.log.warn(`Child process [pid ${oom.pid}] ran out of memory. Stdout and stderr are logged on debug level.`);
465
+ this.log.debug(stdoutAndStderr());
466
+ } else {
467
+ this.fatalError = new ChildProcessCrashedError(this.worker.pid, `Child process [pid ${this.worker.pid}] exited unexpectedly with exit code ${code} (${signal || "without signal"}). ${stdoutAndStderr()}`, code, signal);
468
+ this.log.warn(this.fatalError.message, this.fatalError);
469
+ }
470
+ this.reportError(this.fatalError);
471
+ function processOutOfMemory() {
472
+ return output.includes("JavaScript heap out of memory") || output.includes("FatalProcessOutOfMemory");
473
+ }
474
+ function stdoutAndStderr() {
475
+ if (output.length) return `Last part of stdout and stderr was:${os.EOL}${padLeft(output)}`;
476
+ else return "Stdout and stderr were empty.";
477
+ }
478
+ };
479
+ handleError = (error) => {
480
+ if (this.innerProcessIsCrashed(error)) {
481
+ this.log.warn(`Child process [pid ${this.worker.pid}] has crashed. See other warning messages for more info.`, error);
482
+ this.reportError(new ChildProcessCrashedError(this.worker.pid, `Child process [pid ${this.worker.pid}] has crashed`, void 0, void 0, error));
483
+ } else this.reportError(error);
484
+ };
485
+ innerProcessIsCrashed(error) {
486
+ return isErrnoException(error) && (error.code === BROKEN_PIPE_ERROR_CODE || error.code === IPC_CHANNEL_CLOSED_ERROR_CODE);
487
+ }
488
+ async dispose() {
489
+ if (!this.isDisposed) {
490
+ this.worker.removeListener("close", this.handleUnexpectedExit);
491
+ this.isDisposed = true;
492
+ this.log.debug("Disposing of worker process %s", this.worker.pid);
493
+ this.disposeTask = new ExpirableTask(TIMEOUT_FOR_DISPOSE);
494
+ this.send({ kind: 2 });
495
+ try {
496
+ await this.disposeTask.promise;
497
+ } finally {
498
+ this.log.debug("Kill %s", this.worker.pid);
499
+ await kill(this.worker.pid);
500
+ }
501
+ }
502
+ }
503
+ logUnidentifiedMessage(message) {
504
+ this.log.error(`Received unidentified message ${message}`);
505
+ }
506
+ };
507
+ //#endregion
508
+ //#region src/checker/checker-worker.ts
509
+ var CheckerWorker = class {
510
+ innerCheckers;
511
+ static inject = tokens(commonTokens.options, pluginCreator);
512
+ constructor(options, pluginCreator) {
513
+ this.innerCheckers = new Map(options.checkers.map((name) => [name, pluginCreator.create(PluginKind.Checker, name)]));
514
+ }
515
+ async init() {
516
+ for (const [name, checker] of this.innerCheckers.entries()) try {
517
+ await checker.init();
518
+ } catch (error) {
519
+ throw new StrykerError(`An error occurred during initialization of the "${name}" checker`, error);
520
+ }
521
+ }
522
+ async check(checkerName, mutants) {
523
+ return this.perform(checkerName, (checker) => checker.check(mutants));
524
+ }
525
+ async group(checkerName, mutants) {
526
+ return this.perform(checkerName, (checker) => checker.group?.(mutants) ?? mutants.map(({ id }) => [id]));
527
+ }
528
+ perform(checkerName, act) {
529
+ const checker = this.innerCheckers.get(checkerName);
530
+ if (checker) return act(checker);
531
+ else throw new Error(`Checker ${checkerName} does not exist!`);
532
+ }
533
+ };
534
+ //#endregion
535
+ //#region src/checker/checker-child-process-proxy.ts
536
+ var CheckerChildProcessProxy = class {
537
+ childProcess;
538
+ constructor(options, fileDescriptions, pluginModulePaths, loggingServerAddress, getLogger, idGenerator) {
539
+ this.childProcess = ChildProcessProxy.create(new URL("./checker-worker.mjs", import.meta.url).toString(), loggingServerAddress, options, fileDescriptions, pluginModulePaths, process.cwd(), CheckerWorker, options.checkerNodeArgs, getLogger, idGenerator);
540
+ }
541
+ async dispose() {
542
+ await this.childProcess.dispose();
543
+ }
544
+ async init() {
545
+ await this.childProcess.proxy.init();
546
+ }
547
+ async check(checkerName, mutants) {
548
+ return this.childProcess.proxy.check(checkerName, mutants);
549
+ }
550
+ async group(checkerName, mutants) {
551
+ return this.childProcess.proxy.group(checkerName, mutants);
552
+ }
553
+ };
554
+ //#endregion
555
+ //#region src/checker/checker-retry-decorator.ts
556
+ var CheckerRetryDecorator = class extends ResourceDecorator {
557
+ log;
558
+ constructor(producer, log) {
559
+ super(producer);
560
+ this.log = log;
561
+ }
562
+ async check(checkerName, mutants) {
563
+ return this.tryAction(() => this.innerResource.check(checkerName, mutants));
564
+ }
565
+ async group(checkerName, mutants) {
566
+ return this.tryAction(() => this.innerResource.group(checkerName, mutants));
567
+ }
568
+ async tryAction(act) {
569
+ try {
570
+ return await act();
571
+ } catch (error) {
572
+ if (error instanceof ChildProcessCrashedError) {
573
+ if (error instanceof OutOfMemoryError) this.log.warn(`Checker process [${error.pid}] ran out of memory. Retrying in a new process.`);
574
+ else this.log.warn(`Checker process [${error.pid}] crashed with exit code ${error.exitCode}. Retrying in a new process.`, error);
575
+ await this.recover();
576
+ return act();
577
+ } else throw error;
578
+ }
579
+ }
580
+ };
581
+ //#endregion
582
+ //#region src/checker/checker-factory.ts
583
+ createCheckerFactory.inject = tokens(commonTokens.options, commonTokens.fileDescriptions, loggingServerAddress, pluginModulePaths, commonTokens.getLogger, workerIdGenerator);
584
+ function createCheckerFactory(options, fileDescriptions, loggingServerAddress, pluginModulePaths, getLogger, idGenerator) {
585
+ return () => new CheckerFacade(() => new CheckerRetryDecorator(() => new CheckerChildProcessProxy(options, fileDescriptions, pluginModulePaths, loggingServerAddress, getLogger, idGenerator), getLogger(CheckerRetryDecorator.name)));
586
+ }
587
+ //#endregion
588
+ export { ChildProcessCrashedError as a, createTestRunnerPool as c, OutOfMemoryError as i, ConcurrencyTokenProvider as l, CheckerWorker as n, ResourceDecorator as o, ChildProcessProxy as r, createCheckerPool as s, createCheckerFactory as t };
@@ -0,0 +1,108 @@
1
+ import "./index-CyOSz6LC.mjs";
2
+ import { FileDescriptions, Mutant, PartialStrykerOptions, StrykerOptions } from "@systemfsoftware/stryker-js-plugin-api/core";
3
+ import "@systemfsoftware/stryker-js-plugin-api/plugin";
4
+ import { Disposable, Injector as Injector$1 } from "typed-inject";
5
+ import { Schema } from "effect";
6
+ import { Logger } from "@systemfsoftware/stryker-js-plugin-api/logging";
7
+ import "rxjs";
8
+ import { TestRunner } from "@systemfsoftware/stryker-js-plugin-api/test-runner";
9
+ import { CheckResult } from "@systemfsoftware/stryker-js-plugin-api/check";
10
+ //#region src/logging/log-level.d.ts
11
+ /**
12
+ * Re-export of @systemfsoftware/stryker-js-plugin-api/core's LogLevel const enum as a local runtime object.
13
+ * The upstream uses `const enum` which rolldown/oxc doesn't inline at bundle time,
14
+ * and which has no runtime emit.
15
+ *
16
+ * Using a const object + type pattern instead of an enum keeps the type structurally
17
+ * compatible with the upstream's LogLevel (both are the same string literal union),
18
+ * so values from @systemfsoftware/stryker-js-plugin-api/core types are assignable without casting.
19
+ */
20
+ declare const LogLevel: {
21
+ readonly Trace: 'trace';
22
+ readonly Debug: 'debug';
23
+ readonly Information: 'info';
24
+ readonly Warning: 'warn';
25
+ readonly Error: 'error';
26
+ readonly Fatal: 'fatal';
27
+ readonly Off: 'off';
28
+ };
29
+ type LogLevel = (typeof LogLevel)[keyof typeof LogLevel];
30
+ //#endregion
31
+ //#region src/logging/logging-event.schema.d.ts
32
+ /**
33
+ * The wire format of a {@link LoggingEvent}; worker processes serialize
34
+ * events over the logging socket, the server deserializes them.
35
+ */
36
+ declare const SerializedLoggingEventSchema: Schema.Struct<{
37
+ readonly startTime: Schema.String;
38
+ readonly categoryName: Schema.String;
39
+ readonly message: Schema.String;
40
+ readonly level: Schema.Literals<readonly ["trace", "debug", "info", "warn", "error", "fatal", "off"]>;
41
+ readonly pid: Schema.Finite;
42
+ }>;
43
+ type SerializedLoggingEvent = Schema.Schema.Type<typeof SerializedLoggingEventSchema>;
44
+ //#endregion
45
+ //#region src/logging/logging-event.d.ts
46
+ declare class LoggingEvent {
47
+ #private;
48
+ readonly startTime: Date;
49
+ readonly categoryName: string;
50
+ readonly data: unknown[];
51
+ readonly level: LogLevel;
52
+ readonly pid: number;
53
+ private constructor();
54
+ static create(categoryName: string, level: LogLevel, data: unknown[]): LoggingEvent;
55
+ format(): string;
56
+ formatColorized(): string;
57
+ static deserialize(ser: SerializedLoggingEvent): LoggingEvent;
58
+ serialize(): SerializedLoggingEvent;
59
+ }
60
+ //#endregion
61
+ //#region src/logging/logging-sink.d.ts
62
+ interface LoggingSink {
63
+ log(event: LoggingEvent): void;
64
+ isEnabled(level: LogLevel): boolean;
65
+ }
66
+ //#endregion
67
+ //#region src/logging/logging-backend.d.ts
68
+ /**
69
+ * The logging backend that handles the actual logging. So to both a file and the stdout, stderr.
70
+ */
71
+ declare class LoggingBackend implements LoggingSink, Disposable {
72
+ #private;
73
+ activeStdoutLevel: LogLevel;
74
+ activeFileLevel: LogLevel;
75
+ showColors: boolean;
76
+ static readonly inject: readonly ["loggerConsoleOut", "loggerShowColors"];
77
+ constructor(consoleOut: NodeJS.WritableStream, showColors: boolean);
78
+ log(event: LoggingEvent): void;
79
+ isEnabled(level: LogLevel): boolean;
80
+ get activeLogLevel(): LogLevel;
81
+ get priority(): 0 | 1 | 2 | 3 | 4 | 5 | 6;
82
+ /**
83
+ * `allowConsoleColors` is deliberately not read here (R8). It defaults to
84
+ * `true` in the schema, so honouring it would re-enable colour on every run
85
+ * and defeat the machine-mode contract; colour is decided once, from the
86
+ * resolved mode and `NO_COLOR`, and injected at construction.
87
+ */
88
+ configure({ logLevel, fileLogLevel }: PartialStrykerOptions): void;
89
+ dispose(): Promise<void>;
90
+ }
91
+ //#endregion
92
+ //#region src/logging/logging-server.d.ts
93
+ interface LoggingServerAddress {
94
+ port: number;
95
+ }
96
+ //#endregion
97
+ //#region src/worker-pool/pool.d.ts
98
+ interface Resource extends Partial<Disposable> {
99
+ init?(): Promise<void>;
100
+ }
101
+ //#endregion
102
+ //#region src/checker/checker-resource.d.ts
103
+ interface CheckerResource extends Resource {
104
+ check(checkerName: string, mutant: Mutant[]): Promise<Record<string, CheckResult>>;
105
+ group(checkerName: string, mutants: Mutant[]): Promise<string[][]>;
106
+ }
107
+ //#endregion
108
+ export { LoggingServerAddress as n, LoggingBackend as r, CheckerResource as t };