@systemfsoftware/stryker-js-vitest-runner 2.0.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/index.mjs +357 -419
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @systemfsoftware/stryker-js-vitest-runner
|
|
2
2
|
|
|
3
|
+
## 3.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- The dry-run decision moved to a plain kernel (dryRunCell removed); the mutant-run decision splits into branded MutantKilled|MutantSurvived|MutantTimeout|MutantDryError variants
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies:
|
|
12
|
+
- @systemfsoftware/effect-cell-types@6.0.0
|
|
13
|
+
- @systemfsoftware/stryker-js@1.0.0
|
|
14
|
+
|
|
15
|
+
## 2.1.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- Plugin code can now ask for the module-loader service instead of touching the host module API: it exposes the same `createRequire`/`isBuiltin` surface as the host module module, and the Node engine supplies it automatically. Plugins that declare their environment through the engine's plugin declaration get the service without extra wiring; upgrades should move the engine and its plugins in the same release.
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Refreshed builds on the platform-services dependency graph; the packages no longer reach for host builtins directly. No CLI flags or option names change.
|
|
24
|
+
|
|
25
|
+
- Updated dependencies:
|
|
26
|
+
- @systemfsoftware/stryker-js@0.2.0
|
|
27
|
+
|
|
3
28
|
## 2.0.2
|
|
4
29
|
|
|
5
30
|
### Patch Changes
|
package/dist/index.mjs
CHANGED
|
@@ -2,89 +2,66 @@ import { RunConfiguration, SandboxDirectory, declarePlugin } from "@systemfsoftw
|
|
|
2
2
|
import * as Effect$1 from "effect/Effect";
|
|
3
3
|
import * as Layer from "effect/Layer";
|
|
4
4
|
import * as S from "effect/Schema";
|
|
5
|
-
import { createRequire } from "module";
|
|
6
|
-
import { pathToFileURL } from "node:url";
|
|
7
|
-
import path from "path";
|
|
8
|
-
import { fileURLToPath } from "url";
|
|
9
5
|
import { createVitest } from "vitest/node";
|
|
10
6
|
import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
7
|
+
import { Module } from "@systemfsoftware/stryker-js/Module";
|
|
11
8
|
import { INSTRUMENTER_CONSTANTS, errorToString, normalizeFileName } from "@systemfsoftware/stryker-js/Mutant";
|
|
12
9
|
import { TestRunner, TestRunnerFailed, testFilesProvided } from "@systemfsoftware/stryker-js/TestRunner";
|
|
10
|
+
import * as Context from "effect/Context";
|
|
13
11
|
import * as FileSystem from "effect/FileSystem";
|
|
14
|
-
import
|
|
12
|
+
import * as Match from "effect/Match";
|
|
15
13
|
import * as Option from "effect/Option";
|
|
16
14
|
import * as Path from "effect/Path";
|
|
17
15
|
import * as Ref from "effect/Ref";
|
|
18
16
|
import * as Result from "effect/Result";
|
|
19
17
|
import { Effect } from "effect";
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
exports: S.optional(S.Record(S.String, ExportEntry))
|
|
57
|
-
}), [S.Record(S.String, S.Unknown)]);
|
|
58
|
-
/**
|
|
59
|
-
* The dynamically imported project-local `vitest/node` module. The runtime
|
|
60
|
-
* check only asserts object-likeness: the module namespace is whatever the
|
|
61
|
-
* resolved package exports, and the consumers tolerate a missing
|
|
62
|
-
* `createVitest` via their own fallbacks.
|
|
63
|
-
*/
|
|
64
|
-
const VitestNodeModuleSchema = S.declare((input) => input !== null && typeof input === "object" && !Array.isArray(input), { description: "The project-local vitest/node module" });
|
|
65
|
-
/** The `package.json` document of a resolved vitest package. */
|
|
66
|
-
const VitestPackageSchema = S.Struct({ version: S.String });
|
|
67
|
-
//#endregion
|
|
68
|
-
//#region src/VitestDryRun.workflow.ts
|
|
69
|
-
/**
|
|
70
|
-
* VitestDryRun workflow — pure result-mapping for the dry-run phase.
|
|
71
|
-
*
|
|
72
|
-
* Every function here is pure: it maps raw vitest task payloads to
|
|
73
|
-
* Stryker run results without touching the filesystem or spawning
|
|
74
|
-
* processes. Impure orchestration lives in `Runner.ts`.
|
|
75
|
-
*/
|
|
18
|
+
//#region src/interpret-vitest-run.workflow.ts
|
|
19
|
+
var VitestMutantRunCommand = class extends S.TaggedClass()("VitestMutantRunCommand", {
|
|
20
|
+
rawTests: S.Array(S.Unknown),
|
|
21
|
+
projectRoot: S.String,
|
|
22
|
+
hasExternalError: S.Boolean,
|
|
23
|
+
externalErrorText: S.String,
|
|
24
|
+
hitCount: S.optional(S.Finite),
|
|
25
|
+
hitLimit: S.optional(S.Finite),
|
|
26
|
+
reportAllKillers: S.Boolean
|
|
27
|
+
}) {};
|
|
28
|
+
const VitestMutantRunTypeId = Symbol.for("@systemfsoftware/stryker-js-vitest-runner/VitestMutantRun");
|
|
29
|
+
var MutantKilled = class extends S.TaggedClass()("Killed", {
|
|
30
|
+
testsJson: S.String,
|
|
31
|
+
killerIds: S.optional(S.Array(S.String)),
|
|
32
|
+
failureMessage: S.optional(S.String)
|
|
33
|
+
}) {
|
|
34
|
+
[VitestMutantRunTypeId] = VitestMutantRunTypeId;
|
|
35
|
+
};
|
|
36
|
+
var MutantSurvived = class extends S.TaggedClass()("Survived", { testsJson: S.String }) {
|
|
37
|
+
[VitestMutantRunTypeId] = VitestMutantRunTypeId;
|
|
38
|
+
};
|
|
39
|
+
var MutantTimeout = class extends S.TaggedClass()("Timeout", {
|
|
40
|
+
testsJson: S.String,
|
|
41
|
+
reason: S.optional(S.String)
|
|
42
|
+
}) {
|
|
43
|
+
[VitestMutantRunTypeId] = VitestMutantRunTypeId;
|
|
44
|
+
};
|
|
45
|
+
var MutantDryError = class extends S.TaggedClass()("Error", {
|
|
46
|
+
testsJson: S.String,
|
|
47
|
+
errorMessage: S.optional(S.String)
|
|
48
|
+
}) {
|
|
49
|
+
[VitestMutantRunTypeId] = VitestMutantRunTypeId;
|
|
50
|
+
};
|
|
51
|
+
var VitestMutantRunError = class extends S.TaggedError()("VitestMutantRunError", { message: S.String }) {
|
|
52
|
+
[VitestMutantRunTypeId] = VitestMutantRunTypeId;
|
|
53
|
+
};
|
|
76
54
|
var VitestDryRunCommand$1 = class extends S.TaggedClass()("VitestDryRunCommand", {
|
|
77
55
|
rawTests: S.Array(S.Unknown),
|
|
78
56
|
projectRoot: S.String,
|
|
79
57
|
hasExternalError: S.Boolean,
|
|
80
58
|
externalErrorText: S.String
|
|
81
59
|
}) {};
|
|
82
|
-
var VitestDryRunOutput
|
|
60
|
+
var VitestDryRunOutput = class extends S.TaggedClass()("VitestDryRunOutput", {
|
|
83
61
|
status: S.Literals(["Complete", "Error"]),
|
|
84
62
|
testsJson: S.String,
|
|
85
63
|
errorMessage: S.optional(S.String)
|
|
86
64
|
}) {};
|
|
87
|
-
S.TaggedError()("VitestDryRunError", { message: S.String });
|
|
88
65
|
const recordOption$1 = (value) => S.decodeUnknownOption(S.Record(S.String, S.Unknown))(value);
|
|
89
66
|
const getStringField$1 = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "string"));
|
|
90
67
|
const getNumberField$1 = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "number"));
|
|
@@ -133,12 +110,6 @@ const toRawTestIdRaw$1 = (test) => {
|
|
|
133
110
|
onSome: (file) => Option.getOrElse(Option.fromNullishOr(getFilepath$1(file)), () => "unknown.js")
|
|
134
111
|
})}#${collectTestNameRaw$1(test)}`;
|
|
135
112
|
};
|
|
136
|
-
/**
|
|
137
|
-
* A test id is `<file>#<test name>`, and the file is reported relative to the
|
|
138
|
-
* project root so an id is stable across machines and sandbox directories.
|
|
139
|
-
* Vitest reports an absolute path, so the root prefix is stripped here rather
|
|
140
|
-
* than resolved — a decision body has no path service and needs none.
|
|
141
|
-
*/
|
|
142
113
|
const normalizeTestIdRaw$1 = (id, projectRoot) => {
|
|
143
114
|
const hash = id.indexOf("#");
|
|
144
115
|
if (hash === -1) return id;
|
|
@@ -223,65 +194,146 @@ const convertTestRaw$1 = (test, projectRoot) => {
|
|
|
223
194
|
status
|
|
224
195
|
})));
|
|
225
196
|
};
|
|
226
|
-
const decideVitestDryRun$1 = (command) => {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}))), Match.orElse(() => Result.succeed(VitestDryRunOutput$1.make({
|
|
240
|
-
status: "Complete",
|
|
241
|
-
testsJson: JSON.stringify(tests),
|
|
242
|
-
errorMessage: void 0
|
|
243
|
-
}))));
|
|
197
|
+
const decideVitestDryRun$1 = (command) => Match.value(command.rawTests.map((t) => convertTestRaw$1(t, command.projectRoot))).pipe(Match.when((tests) => tests.some((t) => t.status === "failed") === false && command.hasExternalError === true, (tests) => Result.succeed(VitestDryRunOutput.make({
|
|
198
|
+
status: "Error",
|
|
199
|
+
testsJson: JSON.stringify(tests),
|
|
200
|
+
errorMessage: `An error occurred outside of a test run: ${command.externalErrorText}`
|
|
201
|
+
}))), Match.orElse((tests) => Result.succeed(VitestDryRunOutput.make({
|
|
202
|
+
status: "Complete",
|
|
203
|
+
testsJson: JSON.stringify(tests),
|
|
204
|
+
errorMessage: void 0
|
|
205
|
+
}))));
|
|
206
|
+
const hitLimitReason = (hitCount, hitLimit) => {
|
|
207
|
+
if (hitCount === void 0 || hitLimit === void 0) return Option.none();
|
|
208
|
+
if (hitCount > hitLimit) return Option.some(`Hit limit reached (${hitCount}/${hitLimit})`);
|
|
209
|
+
return Option.none();
|
|
244
210
|
};
|
|
245
|
-
const
|
|
211
|
+
const decideVitestMutantRun = (command) => Match.value(hitLimitReason(command.hitCount, command.hitLimit)).pipe(Match.when(Option.isSome, (hit) => Result.succeed(MutantTimeout.make({
|
|
212
|
+
testsJson: "[]",
|
|
213
|
+
reason: hit.value
|
|
214
|
+
}))), Match.when(Option.isNone, () => {
|
|
215
|
+
const dryOut = decideVitestDryRun$1(VitestDryRunCommand$1.make({
|
|
216
|
+
rawTests: command.rawTests,
|
|
217
|
+
projectRoot: command.projectRoot,
|
|
218
|
+
hasExternalError: command.hasExternalError,
|
|
219
|
+
externalErrorText: command.externalErrorText
|
|
220
|
+
}));
|
|
221
|
+
return Match.value(Result.isFailure(dryOut)).pipe(Match.when(true, () => Result.fail(new VitestMutantRunError({ message: "dry run mapping failed" }))), Match.when(false, () => {
|
|
222
|
+
const dry = Result.getOrElse(dryOut, () => VitestDryRunOutput.make({
|
|
223
|
+
status: "Complete",
|
|
224
|
+
testsJson: "[]",
|
|
225
|
+
errorMessage: void 0
|
|
226
|
+
}));
|
|
227
|
+
return Match.value(dry.status === "Error").pipe(Match.when(true, () => Result.succeed(MutantDryError.make({
|
|
228
|
+
testsJson: "[]",
|
|
229
|
+
errorMessage: dry.errorMessage
|
|
230
|
+
}))), Match.when(false, () => {
|
|
231
|
+
const testsOption = Option.liftThrowable((input) => JSON.parse(input))(dry.testsJson).pipe(Option.filter((v) => Array.isArray(v)));
|
|
232
|
+
const killed = Option.getOrElse(testsOption, () => []).filter((t) => t.status === "failed");
|
|
233
|
+
return Match.value(killed.length > 0).pipe(Match.when(true, () => Match.value(command.reportAllKillers).pipe(Match.when(true, () => {
|
|
234
|
+
const firstKiller = Option.fromNullishOr(killed[0]);
|
|
235
|
+
const failureMessage = Option.match(firstKiller, {
|
|
236
|
+
onNone: () => void 0,
|
|
237
|
+
onSome: (k) => k.failureMessage
|
|
238
|
+
});
|
|
239
|
+
return Result.succeed(MutantKilled.make({
|
|
240
|
+
testsJson: dry.testsJson,
|
|
241
|
+
killerIds: killed.map((t) => t.id),
|
|
242
|
+
failureMessage
|
|
243
|
+
}));
|
|
244
|
+
}), Match.when(false, () => {
|
|
245
|
+
const first = Option.fromNullishOr(killed[0]);
|
|
246
|
+
const failureMessage = Option.match(first, {
|
|
247
|
+
onNone: () => void 0,
|
|
248
|
+
onSome: (k) => k.failureMessage
|
|
249
|
+
});
|
|
250
|
+
return Match.value(Option.isSome(first)).pipe(Match.when(true, () => Result.succeed(MutantKilled.make({
|
|
251
|
+
testsJson: dry.testsJson,
|
|
252
|
+
killerIds: Option.match(first, {
|
|
253
|
+
onNone: () => void 0,
|
|
254
|
+
onSome: (k) => [k.id]
|
|
255
|
+
}),
|
|
256
|
+
failureMessage
|
|
257
|
+
}))), Match.when(false, () => Result.succeed(MutantKilled.make({
|
|
258
|
+
testsJson: dry.testsJson,
|
|
259
|
+
killerIds: Option.getOrUndefined(Option.match(first, {
|
|
260
|
+
onNone: () => Option.none(),
|
|
261
|
+
onSome: (k) => Option.some([k.id])
|
|
262
|
+
})),
|
|
263
|
+
failureMessage
|
|
264
|
+
}))), Match.exhaustive);
|
|
265
|
+
}), Match.exhaustive)), Match.when(false, () => Result.succeed(MutantSurvived.make({ testsJson: dry.testsJson }))), Match.exhaustive);
|
|
266
|
+
}), Match.exhaustive);
|
|
267
|
+
}), Match.exhaustive);
|
|
268
|
+
}), Match.exhaustive);
|
|
269
|
+
const interpretVitestRun = Workflow.make(VitestMutantRunCommand, decideVitestMutantRun);
|
|
246
270
|
//#endregion
|
|
247
|
-
//#region src/
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
reason: S.optional(S.String)
|
|
272
|
-
}) {};
|
|
273
|
-
var VitestMutantRunError = class extends S.TaggedError()("VitestMutantRunError", { message: S.String }) {};
|
|
271
|
+
//#region src/Runner.schema.ts
|
|
272
|
+
const VitestRunnerOptionsSchema = S.Struct({
|
|
273
|
+
dir: S.optional(S.String),
|
|
274
|
+
related: S.optional(S.Boolean).pipe(S.withDecodingDefault(Effect.succeed(true))),
|
|
275
|
+
configFile: S.optional(S.String)
|
|
276
|
+
});
|
|
277
|
+
const VitestSectionSchema = S.optional(VitestRunnerOptionsSchema).pipe(S.withDecodingDefault(Effect.succeed({ related: true })));
|
|
278
|
+
const HitCountMetaSchema = S.Struct({ hitCount: S.optional(S.Finite) });
|
|
279
|
+
const MutantCoverageMetaSchema = S.Struct({ mutantCoverage: S.optional(S.Struct({
|
|
280
|
+
static: S.Record(S.String, S.Finite),
|
|
281
|
+
perTest: S.Record(S.String, S.Record(S.String, S.Finite))
|
|
282
|
+
})) });
|
|
283
|
+
const MutantCoverageShapeSchema = S.Struct({
|
|
284
|
+
static: S.Record(S.String, S.Finite),
|
|
285
|
+
perTest: S.Record(S.String, S.Record(S.String, S.Finite))
|
|
286
|
+
});
|
|
287
|
+
var CoverageDecodeFailed = class extends S.TaggedError()("CoverageDecodeFailed", { cause: S.Unknown }) {};
|
|
288
|
+
const ExportEntry = S.Union([S.String, S.Record(S.String, S.Unknown)]);
|
|
289
|
+
const PackageManifest = S.StructWithRest(S.Struct({
|
|
290
|
+
name: S.optional(S.String),
|
|
291
|
+
exports: S.optional(S.Record(S.String, ExportEntry))
|
|
292
|
+
}), [S.Record(S.String, S.Unknown)]);
|
|
293
|
+
const VitestNodeModuleSchema = S.declare((input) => input !== null && typeof input === "object" && !Array.isArray(input), { description: "The project-local vitest/node module" });
|
|
294
|
+
const VitestPackageSchema = S.Struct({ version: S.String });
|
|
274
295
|
var VitestDryRunCommand = class extends S.TaggedClass()("VitestDryRunCommand", {
|
|
275
296
|
rawTests: S.Array(S.Unknown),
|
|
276
297
|
projectRoot: S.String,
|
|
277
298
|
hasExternalError: S.Boolean,
|
|
278
299
|
externalErrorText: S.String
|
|
279
300
|
}) {};
|
|
280
|
-
var
|
|
281
|
-
|
|
301
|
+
var DryRunComplete = class extends S.TaggedClass()("Complete", { testsJson: S.String }) {};
|
|
302
|
+
var DryRunExternalError = class extends S.TaggedClass()("Error", {
|
|
282
303
|
testsJson: S.String,
|
|
283
|
-
errorMessage: S.
|
|
304
|
+
errorMessage: S.String
|
|
284
305
|
}) {};
|
|
306
|
+
//#endregion
|
|
307
|
+
//#region src/Runner.ts
|
|
308
|
+
var VitestHarness = class extends Context.Service()("VitestHarness") {};
|
|
309
|
+
function fromTestId(id) {
|
|
310
|
+
const [file, ...name] = id.split("#");
|
|
311
|
+
return {
|
|
312
|
+
file,
|
|
313
|
+
test: name.join("#")
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
function normalizeTestId(id, projectRoot, pathService) {
|
|
317
|
+
const { file, test } = fromTestId(id);
|
|
318
|
+
return `${normalizeFileName(pathService.relative(projectRoot, file))}#${test}`;
|
|
319
|
+
}
|
|
320
|
+
function normalizeCoverage(rawCoverage, projectRoot, pathService) {
|
|
321
|
+
return {
|
|
322
|
+
perTest: Object.fromEntries(Object.entries(rawCoverage.perTest).map(([rawTestId, coverageData]) => [normalizeTestId(rawTestId, projectRoot, pathService), coverageData])),
|
|
323
|
+
static: rawCoverage.static
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function collectTestsFromSuite(suite) {
|
|
327
|
+
return suite.tasks.flatMap((task) => {
|
|
328
|
+
if (task.type === "suite") return collectTestsFromSuite(task);
|
|
329
|
+
return task;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
function isErrorCodeError(error) {
|
|
333
|
+
if (error instanceof Error && "code" in error) return typeof Reflect.get(error, "code") === "string";
|
|
334
|
+
return false;
|
|
335
|
+
}
|
|
336
|
+
const VITEST_ERROR_CODES = Object.freeze({ FILES_NOT_FOUND: "VITEST_FILES_NOT_FOUND" });
|
|
285
337
|
const recordOption = (value) => S.decodeUnknownOption(S.Record(S.String, S.Unknown))(value);
|
|
286
338
|
const getStringField = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "string"));
|
|
287
339
|
const getNumberField = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "number"));
|
|
@@ -330,12 +382,6 @@ const toRawTestIdRaw = (test) => {
|
|
|
330
382
|
onSome: (file) => Option.getOrElse(Option.fromNullishOr(getFilepath(file)), () => "unknown.js")
|
|
331
383
|
})}#${collectTestNameRaw(test)}`;
|
|
332
384
|
};
|
|
333
|
-
/**
|
|
334
|
-
* A test id is `<file>#<test name>`, and the file is reported relative to the
|
|
335
|
-
* project root so an id is stable across machines and sandbox directories.
|
|
336
|
-
* Vitest reports an absolute path, so the root prefix is stripped here rather
|
|
337
|
-
* than resolved — a decision body has no path service and needs none.
|
|
338
|
-
*/
|
|
339
385
|
const normalizeTestIdRaw = (id, projectRoot) => {
|
|
340
386
|
const hash = id.indexOf("#");
|
|
341
387
|
if (hash === -1) return id;
|
|
@@ -420,134 +466,14 @@ const convertTestRaw = (test, projectRoot) => {
|
|
|
420
466
|
status
|
|
421
467
|
})));
|
|
422
468
|
};
|
|
423
|
-
const decideVitestDryRun = (command) =>
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
status: "Complete",
|
|
429
|
-
testsJson: JSON.stringify(tests),
|
|
430
|
-
errorMessage: void 0
|
|
431
|
-
}))));
|
|
432
|
-
/**
|
|
433
|
-
* The hit-limit cutoff: `Some` iff both numbers are present AND the count
|
|
434
|
-
* strictly exceeds the limit. The boundary is `>` and not `>=` on purpose —
|
|
435
|
-
* `hitCount === hitLimit` is the last permitted hit, not one too many.
|
|
436
|
-
*/
|
|
437
|
-
const hitLimitReason = (hitCount, hitLimit) => {
|
|
438
|
-
if (hitCount === void 0 || hitLimit === void 0) return Option.none();
|
|
439
|
-
if (hitCount > hitLimit) return Option.some(`Hit limit reached (${hitCount}/${hitLimit})`);
|
|
440
|
-
return Option.none();
|
|
441
|
-
};
|
|
442
|
-
const decideVitestMutantRun = (command) => Match.value(hitLimitReason(command.hitCount, command.hitLimit)).pipe(Match.when(Option.isSome, (hit) => Result.succeed(VitestMutantRunOutput.make({
|
|
443
|
-
status: "Timeout",
|
|
444
|
-
testsJson: "[]",
|
|
445
|
-
errorMessage: void 0,
|
|
446
|
-
killerIds: void 0,
|
|
447
|
-
failureMessage: void 0,
|
|
448
|
-
reason: hit.value
|
|
449
|
-
}))), Match.when(Option.isNone, () => {
|
|
450
|
-
const dryOut = decideVitestDryRun(VitestDryRunCommand.make({
|
|
451
|
-
rawTests: command.rawTests,
|
|
452
|
-
projectRoot: command.projectRoot,
|
|
453
|
-
hasExternalError: command.hasExternalError,
|
|
454
|
-
externalErrorText: command.externalErrorText
|
|
455
|
-
}));
|
|
456
|
-
return Match.value(Result.isFailure(dryOut)).pipe(Match.when(true, () => Result.fail(new VitestMutantRunError({ message: "dry run mapping failed" }))), Match.when(false, () => {
|
|
457
|
-
const dry = Result.getOrElse(dryOut, () => VitestDryRunOutput.make({
|
|
458
|
-
status: "Complete",
|
|
459
|
-
testsJson: "[]",
|
|
460
|
-
errorMessage: void 0
|
|
461
|
-
}));
|
|
462
|
-
return Match.value(dry.status === "Error").pipe(Match.when(true, () => Result.succeed(VitestMutantRunOutput.make({
|
|
463
|
-
status: "Error",
|
|
464
|
-
testsJson: "[]",
|
|
465
|
-
errorMessage: dry.errorMessage,
|
|
466
|
-
killerIds: void 0,
|
|
467
|
-
failureMessage: void 0
|
|
468
|
-
}))), Match.when(false, () => {
|
|
469
|
-
const testsOption = Option.liftThrowable((input) => JSON.parse(input))(dry.testsJson).pipe(Option.filter((v) => Array.isArray(v)));
|
|
470
|
-
const killed = Option.getOrElse(testsOption, () => []).filter((t) => t.status === "failed");
|
|
471
|
-
return Match.value(killed.length > 0).pipe(Match.when(true, () => Match.value(command.reportAllKillers).pipe(Match.when(true, () => {
|
|
472
|
-
const firstKiller = Option.fromNullishOr(killed[0]);
|
|
473
|
-
const failureMessage = Option.match(firstKiller, {
|
|
474
|
-
onNone: () => void 0,
|
|
475
|
-
onSome: (k) => k.failureMessage
|
|
476
|
-
});
|
|
477
|
-
return Result.succeed(VitestMutantRunOutput.make({
|
|
478
|
-
status: "Killed",
|
|
479
|
-
testsJson: dry.testsJson,
|
|
480
|
-
errorMessage: void 0,
|
|
481
|
-
killerIds: killed.map((t) => t.id),
|
|
482
|
-
failureMessage
|
|
483
|
-
}));
|
|
484
|
-
}), Match.when(false, () => {
|
|
485
|
-
const first = Option.fromNullishOr(killed[0]);
|
|
486
|
-
const failureMessage = Option.match(first, {
|
|
487
|
-
onNone: () => void 0,
|
|
488
|
-
onSome: (k) => k.failureMessage
|
|
489
|
-
});
|
|
490
|
-
return Match.value(Option.isSome(first)).pipe(Match.when(true, () => Result.succeed(VitestMutantRunOutput.make({
|
|
491
|
-
status: "Killed",
|
|
492
|
-
testsJson: dry.testsJson,
|
|
493
|
-
errorMessage: void 0,
|
|
494
|
-
killerIds: Option.match(first, {
|
|
495
|
-
onNone: () => void 0,
|
|
496
|
-
onSome: (k) => [k.id]
|
|
497
|
-
}),
|
|
498
|
-
failureMessage
|
|
499
|
-
}))), Match.when(false, () => Result.succeed(VitestMutantRunOutput.make({
|
|
500
|
-
status: "Killed",
|
|
501
|
-
testsJson: dry.testsJson,
|
|
502
|
-
errorMessage: void 0,
|
|
503
|
-
killerIds: Option.getOrUndefined(Option.match(first, {
|
|
504
|
-
onNone: () => Option.none(),
|
|
505
|
-
onSome: (k) => Option.some([k.id])
|
|
506
|
-
})),
|
|
507
|
-
failureMessage
|
|
508
|
-
}))), Match.exhaustive);
|
|
509
|
-
}), Match.exhaustive)), Match.when(false, () => Result.succeed(VitestMutantRunOutput.make({
|
|
510
|
-
status: "Survived",
|
|
511
|
-
testsJson: dry.testsJson,
|
|
512
|
-
errorMessage: void 0,
|
|
513
|
-
killerIds: void 0,
|
|
514
|
-
failureMessage: void 0
|
|
515
|
-
}))), Match.exhaustive);
|
|
516
|
-
}), Match.exhaustive);
|
|
517
|
-
}), Match.exhaustive);
|
|
518
|
-
}), Match.exhaustive);
|
|
519
|
-
const vitestMutantRunWorkflow = Workflow.make(VitestMutantRunCommand, decideVitestMutantRun);
|
|
520
|
-
//#endregion
|
|
521
|
-
//#region src/Runner.ts
|
|
522
|
-
function fromTestId(id) {
|
|
523
|
-
const [file, ...name] = id.split("#");
|
|
524
|
-
return {
|
|
525
|
-
file,
|
|
526
|
-
test: name.join("#")
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
function normalizeTestId(id, projectRoot, pathService) {
|
|
530
|
-
const { file, test } = fromTestId(id);
|
|
531
|
-
return `${normalizeFileName(pathService.relative(projectRoot, file))}#${test}`;
|
|
532
|
-
}
|
|
533
|
-
function normalizeCoverage(rawCoverage, projectRoot, pathService) {
|
|
534
|
-
return {
|
|
535
|
-
perTest: Object.fromEntries(Object.entries(rawCoverage.perTest).map(([rawTestId, coverageData]) => [normalizeTestId(rawTestId, projectRoot, pathService), coverageData])),
|
|
536
|
-
static: rawCoverage.static
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
function collectTestsFromSuite(suite) {
|
|
540
|
-
return suite.tasks.flatMap((task) => {
|
|
541
|
-
if (task.type === "suite") return collectTestsFromSuite(task);
|
|
542
|
-
return task;
|
|
469
|
+
const decideVitestDryRun = (command) => {
|
|
470
|
+
const tests = command.rawTests.map((t) => convertTestRaw(t, command.projectRoot));
|
|
471
|
+
if (tests.some((t) => t.status === "failed") === false && command.hasExternalError) return DryRunExternalError.make({
|
|
472
|
+
testsJson: JSON.stringify(tests),
|
|
473
|
+
errorMessage: `An error occurred outside of a test run: ${command.externalErrorText}`
|
|
543
474
|
});
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
if (error instanceof Error && "code" in error) return typeof Reflect.get(error, "code") === "string";
|
|
547
|
-
return false;
|
|
548
|
-
}
|
|
549
|
-
/** @see https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/errors.ts */
|
|
550
|
-
const VITEST_ERROR_CODES = Object.freeze({ FILES_NOT_FOUND: "VITEST_FILES_NOT_FOUND" });
|
|
475
|
+
return DryRunComplete.make({ testsJson: JSON.stringify(tests) });
|
|
476
|
+
};
|
|
551
477
|
const SOURCE_CONDITION = "@systemfsoftware/source";
|
|
552
478
|
const sourceTargetOf = (entry) => {
|
|
553
479
|
if (typeof entry === "string") return (() => {
|
|
@@ -595,12 +521,6 @@ const readSandboxSelfAliases = (projectRoot) => Effect$1.gen(function* () {
|
|
|
595
521
|
onSome: (manifest) => sandboxSelfAliases(manifest, projectRoot, pathService)
|
|
596
522
|
});
|
|
597
523
|
});
|
|
598
|
-
/**
|
|
599
|
-
* Vite records a package specifier as a bare dep. Vitest related-mode then
|
|
600
|
-
* joins that specifier onto the project root, misses the file, and reports
|
|
601
|
-
* zero tests. Returning the sandbox source path from `resolveId` makes the
|
|
602
|
-
* dep a real filesystem path related-mode can walk.
|
|
603
|
-
*/
|
|
604
524
|
const sandboxSelfPlugin = (aliases) => ({
|
|
605
525
|
name: "stryker-sandbox-self-exports",
|
|
606
526
|
enforce: "pre",
|
|
@@ -608,27 +528,39 @@ const sandboxSelfPlugin = (aliases) => ({
|
|
|
608
528
|
for (const alias of aliases) if (alias.find.test(source)) return alias.replacement;
|
|
609
529
|
}
|
|
610
530
|
});
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
const resolveVitest =
|
|
614
|
-
|
|
615
|
-
const
|
|
616
|
-
const
|
|
617
|
-
const
|
|
531
|
+
const isRunnerTestSuite = (value) => typeof value === "object" && value !== null && "tasks" in value && Array.isArray(Reflect.get(value, "tasks"));
|
|
532
|
+
const STRYKER_SETUP_URL = new URL("./stryker-setup.mjs", import.meta.url);
|
|
533
|
+
const resolveVitest = (_dir) => Effect$1.gen(function* () {
|
|
534
|
+
const fallback = Effect$1.gen(function* () {
|
|
535
|
+
const pathService = yield* Path.Path;
|
|
536
|
+
const fs = yield* FileSystem.FileSystem;
|
|
537
|
+
const urlString = import.meta.resolve("vitest/package.json");
|
|
538
|
+
const packageJsonPath = yield* pathService.fromFileUrl(new URL(urlString));
|
|
539
|
+
const content = yield* fs.readFileString(packageJsonPath);
|
|
540
|
+
const parsed = JSON.parse(content);
|
|
541
|
+
const decoded = yield* S.decodeUnknownEffect(VitestPackageSchema)(parsed);
|
|
618
542
|
return {
|
|
619
|
-
createVitest
|
|
620
|
-
version:
|
|
543
|
+
createVitest,
|
|
544
|
+
version: decoded.version
|
|
621
545
|
};
|
|
622
|
-
}
|
|
623
|
-
|
|
546
|
+
}).pipe(Effect$1.orDie);
|
|
547
|
+
return yield* Effect$1.gen(function* () {
|
|
548
|
+
const module = yield* Module;
|
|
549
|
+
const pathService = yield* Path.Path;
|
|
550
|
+
const fs = yield* FileSystem.FileSystem;
|
|
551
|
+
const requireFromProject = module.createRequire(pathService.join(_dir, "package.json"));
|
|
552
|
+
const imported = requireFromProject("vitest/node");
|
|
553
|
+
const decodedNode = yield* S.decodeUnknownEffect(VitestNodeModuleSchema)(imported);
|
|
554
|
+
const packageJsonPath = requireFromProject.resolve("vitest/package.json");
|
|
555
|
+
const content = yield* fs.readFileString(packageJsonPath);
|
|
556
|
+
const parsed = JSON.parse(content);
|
|
557
|
+
const decodedPackage = yield* S.decodeUnknownEffect(VitestPackageSchema)(parsed);
|
|
624
558
|
return {
|
|
625
|
-
createVitest,
|
|
626
|
-
version:
|
|
559
|
+
createVitest: decodedNode.createVitest,
|
|
560
|
+
version: decodedPackage.version
|
|
627
561
|
};
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
const isRunnerTestSuite = (value) => typeof value === "object" && value !== null && "tasks" in value && Array.isArray(Reflect.get(value, "tasks"));
|
|
631
|
-
const STRYKER_SETUP = fileURLToPath(new URL("./stryker-setup.mjs", import.meta.url));
|
|
562
|
+
}).pipe(Effect$1.catchCause(() => fallback), Effect$1.catchDefect(() => fallback));
|
|
563
|
+
}).pipe(Effect$1.orDie);
|
|
632
564
|
const shouldUseSuiteMetaSecondArg = (version) => {
|
|
633
565
|
const parts = version.split(".");
|
|
634
566
|
const major = Number(parts[0] ?? "0");
|
|
@@ -704,6 +636,7 @@ const makeVitestRunnerLayer = (input) => Layer.effect(TestRunner, Effect$1.gen(f
|
|
|
704
636
|
});
|
|
705
637
|
const fsService = yield* FileSystem.FileSystem;
|
|
706
638
|
const pathService = yield* Path.Path;
|
|
639
|
+
const moduleService = yield* Module;
|
|
707
640
|
const getState = Ref.get(stateRef);
|
|
708
641
|
const requireCtx = Effect$1.gen(function* () {
|
|
709
642
|
const state = yield* getState;
|
|
@@ -739,20 +672,21 @@ const makeVitestRunnerLayer = (input) => Layer.effect(TestRunner, Effect$1.gen(f
|
|
|
739
672
|
...s,
|
|
740
673
|
localSetupFile
|
|
741
674
|
}));
|
|
742
|
-
yield*
|
|
675
|
+
const defaultSetupPath = yield* pathService.fromFileUrl(STRYKER_SETUP_URL).pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
|
|
743
676
|
runnerName: "vitest",
|
|
744
677
|
phase: "init",
|
|
745
678
|
cause: errorToString(cause)
|
|
746
679
|
})));
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
680
|
+
yield* fsService.copyFile(input.setupFilePath ?? defaultSetupPath, localSetupFile).pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
|
|
681
|
+
runnerName: "vitest",
|
|
682
|
+
phase: "init",
|
|
683
|
+
cause: errorToString(cause)
|
|
684
|
+
})));
|
|
685
|
+
const { createVitest, version } = yield* (input.resolveVitestFor ?? resolveVitest)(projectRoot).pipe(Effect$1.provideService(Module, moduleService), Effect$1.provideService(FileSystem.FileSystem, fsService), Effect$1.provideService(Path.Path, pathService), Effect$1.catchDefect((cause) => Effect$1.fail(new TestRunnerFailed({
|
|
686
|
+
runnerName: "vitest",
|
|
687
|
+
phase: "init",
|
|
688
|
+
cause: errorToString(cause)
|
|
689
|
+
}))));
|
|
756
690
|
const namespace = input.globalNamespace ?? INSTRUMENTER_CONSTANTS.NAMESPACE;
|
|
757
691
|
const scanDir = (() => {
|
|
758
692
|
if (typeof options.vitest.dir === "string") return pathService.resolve(projectRoot, options.vitest.dir);
|
|
@@ -803,7 +737,7 @@ const makeVitestRunnerLayer = (input) => Layer.effect(TestRunner, Effect$1.gen(f
|
|
|
803
737
|
return new TestRunnerFailed({
|
|
804
738
|
runnerName: "vitest",
|
|
805
739
|
phase: "init",
|
|
806
|
-
cause
|
|
740
|
+
cause: errorToString(cause)
|
|
807
741
|
});
|
|
808
742
|
})()));
|
|
809
743
|
const resetContext = Effect$1.gen(function* () {
|
|
@@ -913,169 +847,173 @@ const makeVitestRunnerLayer = (input) => Layer.effect(TestRunner, Effect$1.gen(f
|
|
|
913
847
|
})()
|
|
914
848
|
};
|
|
915
849
|
});
|
|
916
|
-
const
|
|
917
|
-
(
|
|
918
|
-
|
|
850
|
+
const harnessImpl = {
|
|
851
|
+
setMode: (mode) => Effect$1.gen(function* () {
|
|
852
|
+
(yield* requireCtx).provide("mode", mode);
|
|
853
|
+
}),
|
|
854
|
+
provide: (key, value) => Effect$1.gen(function* () {
|
|
855
|
+
const ctx = yield* requireCtx;
|
|
856
|
+
if (key === "hitLimit") {
|
|
857
|
+
if (typeof value === "number" || value === void 0) ctx.provide("hitLimit", value);
|
|
858
|
+
else ctx.provide("hitLimit", void 0);
|
|
859
|
+
} else if (key === "mutantActivation") {
|
|
860
|
+
if (value === "runtime" || value === "static") ctx.provide("mutantActivation", value);
|
|
861
|
+
} else if (typeof value === "string") ctx.provide("activeMutant", value);
|
|
862
|
+
})
|
|
863
|
+
};
|
|
864
|
+
const mutantRunCell = Cell.layer({
|
|
865
|
+
read: (command) => Effect$1.gen(function* () {
|
|
866
|
+
const harness = yield* VitestHarness;
|
|
867
|
+
yield* harness.setMode("mutant");
|
|
868
|
+
yield* harness.provide("hitLimit", command.hitLimit);
|
|
869
|
+
yield* harness.provide("mutantActivation", command.mutantActivation);
|
|
870
|
+
yield* harness.provide("activeMutant", command.activeMutant.id);
|
|
871
|
+
const { rawTests, hasExternalError, externalErrorText } = yield* collectRaw({
|
|
872
|
+
testIds: (() => {
|
|
873
|
+
if (command.testFilter !== void 0) return [...command.testFilter];
|
|
874
|
+
})(),
|
|
875
|
+
relatedFiles: [command.sandboxFileName]
|
|
876
|
+
});
|
|
877
|
+
const hitCount = yield* readHitCount.pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
|
|
878
|
+
runnerName: "vitest",
|
|
879
|
+
phase: "mutantRun",
|
|
880
|
+
cause: errorToString(cause)
|
|
881
|
+
})), Effect$1.option, Effect$1.map(Option.getOrUndefined));
|
|
882
|
+
const reportAllKillers = (() => {
|
|
883
|
+
if (typeof input.options.disableBail === "boolean") return input.options.disableBail;
|
|
884
|
+
return false;
|
|
885
|
+
})();
|
|
886
|
+
if (hitCount === void 0) return {
|
|
887
|
+
rawTests,
|
|
888
|
+
projectRoot: input.sandboxDirectory,
|
|
889
|
+
hasExternalError,
|
|
890
|
+
externalErrorText,
|
|
891
|
+
hitLimit: command.hitLimit,
|
|
892
|
+
reportAllKillers
|
|
893
|
+
};
|
|
894
|
+
return {
|
|
895
|
+
rawTests,
|
|
896
|
+
projectRoot: input.sandboxDirectory,
|
|
897
|
+
hasExternalError,
|
|
898
|
+
externalErrorText,
|
|
899
|
+
hitCount,
|
|
900
|
+
hitLimit: command.hitLimit,
|
|
901
|
+
reportAllKillers
|
|
902
|
+
};
|
|
903
|
+
}),
|
|
904
|
+
decode: (raw) => {
|
|
905
|
+
const base = {
|
|
906
|
+
rawTests: raw.rawTests,
|
|
907
|
+
projectRoot: raw.projectRoot,
|
|
908
|
+
hasExternalError: raw.hasExternalError,
|
|
909
|
+
externalErrorText: raw.externalErrorText,
|
|
910
|
+
reportAllKillers: raw.reportAllKillers
|
|
911
|
+
};
|
|
912
|
+
if (raw.hitCount !== void 0) {
|
|
913
|
+
if (raw.hitLimit !== void 0) return Result.succeed(new VitestMutantRunCommand({
|
|
914
|
+
...base,
|
|
915
|
+
hitCount: raw.hitCount,
|
|
916
|
+
hitLimit: raw.hitLimit
|
|
917
|
+
}));
|
|
918
|
+
return Result.succeed(new VitestMutantRunCommand({
|
|
919
|
+
...base,
|
|
920
|
+
hitCount: raw.hitCount
|
|
921
|
+
}));
|
|
922
|
+
}
|
|
923
|
+
if (raw.hitLimit !== void 0) return Result.succeed(new VitestMutantRunCommand({
|
|
924
|
+
...base,
|
|
925
|
+
hitLimit: raw.hitLimit
|
|
926
|
+
}));
|
|
927
|
+
return Result.succeed(new VitestMutantRunCommand(base));
|
|
928
|
+
},
|
|
929
|
+
decide: interpretVitestRun,
|
|
930
|
+
encode: (outcome) => Result.match(outcome, {
|
|
931
|
+
onFailure: (e) => ({
|
|
932
|
+
status: "error",
|
|
933
|
+
errorMessage: e.message
|
|
934
|
+
}),
|
|
935
|
+
onSuccess: (out) => {
|
|
936
|
+
const nrOfTests = () => {
|
|
937
|
+
try {
|
|
938
|
+
const raw = JSON.parse(out.testsJson);
|
|
939
|
+
if (Array.isArray(raw)) return raw.filter(isIdRecord).length;
|
|
940
|
+
return 0;
|
|
941
|
+
} catch {
|
|
942
|
+
return 0;
|
|
943
|
+
}
|
|
944
|
+
};
|
|
945
|
+
return Match.value(out).pipe(Match.tag("Error", (error) => ({
|
|
946
|
+
status: "error",
|
|
947
|
+
errorMessage: error.errorMessage ?? "unknown"
|
|
948
|
+
})), Match.tag("Timeout", (timeout) => (() => {
|
|
949
|
+
if (timeout.reason === void 0) return { status: "timeout" };
|
|
950
|
+
return {
|
|
951
|
+
status: "timeout",
|
|
952
|
+
reason: timeout.reason
|
|
953
|
+
};
|
|
954
|
+
})()), Match.tag("Killed", (killed) => ({
|
|
955
|
+
status: "killed",
|
|
956
|
+
failureMessage: killed.failureMessage ?? "",
|
|
957
|
+
killedBy: (() => {
|
|
958
|
+
if (killed.killerIds !== void 0) return [...killed.killerIds];
|
|
959
|
+
return [];
|
|
960
|
+
})(),
|
|
961
|
+
nrOfTests: nrOfTests()
|
|
962
|
+
})), Match.tag("Survived", () => ({
|
|
963
|
+
status: "survived",
|
|
964
|
+
nrOfTests: nrOfTests()
|
|
965
|
+
})), Match.exhaustive);
|
|
966
|
+
}
|
|
967
|
+
}),
|
|
968
|
+
write: (output, _raw) => Effect$1.succeed(output)
|
|
969
|
+
});
|
|
970
|
+
const dryRun = (options) => Effect$1.gen(function* () {
|
|
971
|
+
yield* (yield* VitestHarness).setMode("dry-run");
|
|
972
|
+
const hasTestFiles = testFilesProvided(options);
|
|
919
973
|
const filter = (() => {
|
|
920
974
|
if (hasTestFiles) return {
|
|
921
|
-
testFiles: [...
|
|
975
|
+
testFiles: [...options.testFiles ?? []],
|
|
922
976
|
relatedFiles: (() => {
|
|
923
|
-
if (
|
|
977
|
+
if (options.files !== void 0) return [...options.files];
|
|
924
978
|
})()
|
|
925
979
|
};
|
|
926
980
|
return { relatedFiles: (() => {
|
|
927
|
-
if (
|
|
981
|
+
if (options.files !== void 0) return [...options.files];
|
|
928
982
|
})() };
|
|
929
983
|
})();
|
|
930
984
|
const { rawTests, hasExternalError, externalErrorText } = yield* collectRaw(filter);
|
|
931
|
-
|
|
985
|
+
const decision = decideVitestDryRun(new VitestDryRunCommand({
|
|
932
986
|
rawTests,
|
|
933
987
|
projectRoot: input.sandboxDirectory,
|
|
934
988
|
hasExternalError,
|
|
935
989
|
externalErrorText
|
|
936
|
-
};
|
|
937
|
-
|
|
938
|
-
rawTests: raw.rawTests,
|
|
939
|
-
projectRoot: raw.projectRoot,
|
|
940
|
-
hasExternalError: raw.hasExternalError,
|
|
941
|
-
externalErrorText: raw.externalErrorText
|
|
942
|
-
}))), Cell.decide(vitestDryRunWorkflow), Cell.encode((outcome) => Result.match(outcome, {
|
|
943
|
-
onFailure: (e) => ({
|
|
990
|
+
}));
|
|
991
|
+
const result = Match.value(decision).pipe(Match.tag("Error", (error) => ({
|
|
944
992
|
status: "error",
|
|
945
|
-
errorMessage:
|
|
946
|
-
}),
|
|
947
|
-
|
|
948
|
-
const raw = JSON.parse(out.testsJson);
|
|
993
|
+
errorMessage: error.errorMessage
|
|
994
|
+
})), Match.tag("Complete", (complete) => {
|
|
995
|
+
const raw = JSON.parse(complete.testsJson);
|
|
949
996
|
let tests;
|
|
950
997
|
if (Array.isArray(raw)) tests = raw.filter(isTestResultLike);
|
|
951
998
|
else tests = [];
|
|
952
|
-
if (out.status === "Error") return {
|
|
953
|
-
status: "error",
|
|
954
|
-
errorMessage: out.errorMessage ?? "unknown"
|
|
955
|
-
};
|
|
956
999
|
return {
|
|
957
1000
|
status: "complete",
|
|
958
1001
|
tests
|
|
959
1002
|
};
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
if (output.status === "complete") {
|
|
1003
|
+
}), Match.exhaustive);
|
|
1004
|
+
if (result.status === "complete") {
|
|
963
1005
|
const mutantCoverage = yield* readMutantCoverage.pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
|
|
964
1006
|
runnerName: "vitest",
|
|
965
1007
|
phase: "dryRun",
|
|
966
1008
|
cause: errorToString(cause)
|
|
967
1009
|
})));
|
|
968
1010
|
if (mutantCoverage !== void 0) return {
|
|
969
|
-
...
|
|
1011
|
+
...result,
|
|
970
1012
|
mutantCoverage
|
|
971
1013
|
};
|
|
972
1014
|
}
|
|
973
|
-
return
|
|
974
|
-
})))
|
|
975
|
-
const mutantRunDescription = pipe(Cell.read((command) => Effect$1.gen(function* () {
|
|
976
|
-
const ctx = yield* requireCtx;
|
|
977
|
-
ctx.provide("mode", "mutant");
|
|
978
|
-
ctx.provide("hitLimit", command.hitLimit);
|
|
979
|
-
ctx.provide("mutantActivation", command.mutantActivation);
|
|
980
|
-
ctx.provide("activeMutant", command.activeMutant.id);
|
|
981
|
-
const { rawTests, hasExternalError, externalErrorText } = yield* collectRaw({
|
|
982
|
-
testIds: (() => {
|
|
983
|
-
if (command.testFilter !== void 0) return [...command.testFilter];
|
|
984
|
-
})(),
|
|
985
|
-
relatedFiles: [command.sandboxFileName]
|
|
986
|
-
});
|
|
987
|
-
const hitCount = yield* readHitCount.pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
|
|
988
|
-
runnerName: "vitest",
|
|
989
|
-
phase: "mutantRun",
|
|
990
|
-
cause: errorToString(cause)
|
|
991
|
-
})), Effect$1.option, Effect$1.map(Option.getOrUndefined));
|
|
992
|
-
const reportAllKillers = (() => {
|
|
993
|
-
if (typeof input.options.disableBail === "boolean") return input.options.disableBail;
|
|
994
|
-
return false;
|
|
995
|
-
})();
|
|
996
|
-
if (hitCount === void 0) return {
|
|
997
|
-
rawTests,
|
|
998
|
-
projectRoot: input.sandboxDirectory,
|
|
999
|
-
hasExternalError,
|
|
1000
|
-
externalErrorText,
|
|
1001
|
-
hitLimit: command.hitLimit,
|
|
1002
|
-
reportAllKillers
|
|
1003
|
-
};
|
|
1004
|
-
return {
|
|
1005
|
-
rawTests,
|
|
1006
|
-
projectRoot: input.sandboxDirectory,
|
|
1007
|
-
hasExternalError,
|
|
1008
|
-
externalErrorText,
|
|
1009
|
-
hitCount,
|
|
1010
|
-
hitLimit: command.hitLimit,
|
|
1011
|
-
reportAllKillers
|
|
1012
|
-
};
|
|
1013
|
-
})), Cell.decode((raw) => {
|
|
1014
|
-
const base = {
|
|
1015
|
-
rawTests: raw.rawTests,
|
|
1016
|
-
projectRoot: raw.projectRoot,
|
|
1017
|
-
hasExternalError: raw.hasExternalError,
|
|
1018
|
-
externalErrorText: raw.externalErrorText,
|
|
1019
|
-
reportAllKillers: raw.reportAllKillers
|
|
1020
|
-
};
|
|
1021
|
-
if (raw.hitCount !== void 0) {
|
|
1022
|
-
if (raw.hitLimit !== void 0) return Result.succeed(new VitestMutantRunCommand({
|
|
1023
|
-
...base,
|
|
1024
|
-
hitCount: raw.hitCount,
|
|
1025
|
-
hitLimit: raw.hitLimit
|
|
1026
|
-
}));
|
|
1027
|
-
return Result.succeed(new VitestMutantRunCommand({
|
|
1028
|
-
...base,
|
|
1029
|
-
hitCount: raw.hitCount
|
|
1030
|
-
}));
|
|
1031
|
-
}
|
|
1032
|
-
if (raw.hitLimit !== void 0) return Result.succeed(new VitestMutantRunCommand({
|
|
1033
|
-
...base,
|
|
1034
|
-
hitLimit: raw.hitLimit
|
|
1035
|
-
}));
|
|
1036
|
-
return Result.succeed(new VitestMutantRunCommand(base));
|
|
1037
|
-
}), Cell.decide(vitestMutantRunWorkflow), Cell.encode((outcome) => Result.match(outcome, {
|
|
1038
|
-
onFailure: (e) => ({
|
|
1039
|
-
status: "error",
|
|
1040
|
-
errorMessage: e.message
|
|
1041
|
-
}),
|
|
1042
|
-
onSuccess: (out) => {
|
|
1043
|
-
let parsed;
|
|
1044
|
-
try {
|
|
1045
|
-
const raw = JSON.parse(out.testsJson);
|
|
1046
|
-
if (Array.isArray(raw)) parsed = raw.filter(isIdRecord);
|
|
1047
|
-
else parsed = [];
|
|
1048
|
-
} catch {
|
|
1049
|
-
parsed = [];
|
|
1050
|
-
}
|
|
1051
|
-
const nrOfTests = parsed.length;
|
|
1052
|
-
if (out.status === "Error") return {
|
|
1053
|
-
status: "error",
|
|
1054
|
-
errorMessage: out.errorMessage ?? "unknown"
|
|
1055
|
-
};
|
|
1056
|
-
if (out.status === "Timeout") {
|
|
1057
|
-
if (out.reason === void 0) return { status: "timeout" };
|
|
1058
|
-
return {
|
|
1059
|
-
status: "timeout",
|
|
1060
|
-
reason: out.reason
|
|
1061
|
-
};
|
|
1062
|
-
}
|
|
1063
|
-
if (out.status === "Killed") return {
|
|
1064
|
-
status: "killed",
|
|
1065
|
-
failureMessage: out.failureMessage ?? "",
|
|
1066
|
-
killedBy: (() => {
|
|
1067
|
-
if (out.killerIds !== void 0) return [...out.killerIds];
|
|
1068
|
-
return [];
|
|
1069
|
-
})(),
|
|
1070
|
-
nrOfTests
|
|
1071
|
-
};
|
|
1072
|
-
return {
|
|
1073
|
-
status: "survived",
|
|
1074
|
-
nrOfTests
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1077
|
-
})), Cell.write((output) => Effect$1.succeed(output)));
|
|
1078
|
-
const dryRun = (options) => Cell.apply(dryRunDescription, options).pipe(Effect$1.mapError((cause) => (() => {
|
|
1015
|
+
return result;
|
|
1016
|
+
}).pipe(Effect$1.provideService(VitestHarness, harnessImpl), Effect$1.mapError((cause) => (() => {
|
|
1079
1017
|
if (cause instanceof TestRunnerFailed) return cause;
|
|
1080
1018
|
return new TestRunnerFailed({
|
|
1081
1019
|
runnerName: "vitest",
|
|
@@ -1083,7 +1021,7 @@ const makeVitestRunnerLayer = (input) => Layer.effect(TestRunner, Effect$1.gen(f
|
|
|
1083
1021
|
cause: errorToString(cause)
|
|
1084
1022
|
});
|
|
1085
1023
|
})()));
|
|
1086
|
-
const mutantRun = (options) => Cell.
|
|
1024
|
+
const mutantRun = (options) => Cell.run(mutantRunCell, options).pipe(Effect$1.provideService(VitestHarness, harnessImpl), Effect$1.mapError((cause) => (() => {
|
|
1087
1025
|
if (cause instanceof TestRunnerFailed) return cause;
|
|
1088
1026
|
return new TestRunnerFailed({
|
|
1089
1027
|
runnerName: "vitest",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/stryker-js-vitest-runner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/systemfsoftware/systemfsoftware.git",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"coverageAnalysis": "perTest"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@systemfsoftware/
|
|
35
|
-
"@systemfsoftware/
|
|
34
|
+
"@systemfsoftware/stryker-js": "^1.0.0",
|
|
35
|
+
"@systemfsoftware/effect-cell-types": "^6.0.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"effect": "^4.0.0-rc.112",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"tsdown": "^0.22.14",
|
|
51
51
|
"typescript": "^7",
|
|
52
52
|
"vitest": "^4",
|
|
53
|
-
"@systemfsoftware/all": "^1.
|
|
53
|
+
"@systemfsoftware/all": "^1.1.2",
|
|
54
54
|
"@systemfsoftware/effect-gherkin-spec": "^4.0.0",
|
|
55
55
|
"@systemfsoftware/oxlint-config": "^0.1.0",
|
|
56
56
|
"@systemfsoftware/tsconfig": "^1.3.3",
|