@systemfsoftware/stryker-js-vitest-runner 2.1.0 → 4.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 +95 -0
- package/dist/index.mjs +314 -387
- package/package.json +8 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,100 @@
|
|
|
1
1
|
# @systemfsoftware/stryker-js-vitest-runner
|
|
2
2
|
|
|
3
|
+
## 4.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- The packages are renamed. `plugin-api` is now `@systemfsoftware/stryker-js`, the
|
|
8
|
+
language every plugin is written against. `mutation-run` is split: the run
|
|
9
|
+
itself is `@systemfsoftware/stryker-js-engine`
|
|
10
|
+
(host-neutral, no Node on its manifest) and the Node process entries are
|
|
11
|
+
`@systemfsoftware/stryker-js-cli`, which owns the worker files and the runtime
|
|
12
|
+
gate. `mutation-report` is now `@systemfsoftware/stryker-js-html-reporter`.
|
|
13
|
+
`@systemfsoftware/stryker-js-platform-node` is never published — do not install
|
|
14
|
+
it. Install the new names and change your imports.
|
|
15
|
+
|
|
16
|
+
Options types moved. `StrykerOptions`, `PartialStrykerOptions` and `LogLevel` are
|
|
17
|
+
imported from the `Schema` export; `Mutant`, `MutantStatus`, `Position` and
|
|
18
|
+
`Location` from the `Mutant` export. Point a config's `extends` at the language
|
|
19
|
+
package's `Schema` export.
|
|
20
|
+
|
|
21
|
+
`MutantStatus` accepts one spelling per outcome: `Killed`, `Survived`,
|
|
22
|
+
`NoCoverage`, `Timeout`, `CompileError`, `RuntimeError`, `Ignored` and `Pending`.
|
|
23
|
+
The lowercase and abbreviated forms — `killed`, `timedOut`, `noCoverage` and the
|
|
24
|
+
rest — are gone. A comparison against a removed spelling never matched the value
|
|
25
|
+
the reporter actually produced, so check any status comparison you wrote.
|
|
26
|
+
|
|
27
|
+
Statuses, plugin kinds, exit classes and AST formats are string literal unions
|
|
28
|
+
rather than enums, so read them as their string values. Member access such as
|
|
29
|
+
`ExitClass.VerdictFail` no longer resolves.
|
|
30
|
+
|
|
31
|
+
A plugin no longer receives a logger, and the logger port is gone. Plugins log
|
|
32
|
+
through Effect, and the host decides where that output goes.
|
|
33
|
+
|
|
34
|
+
The bundled base preset is gone. A config inherits from the language package's
|
|
35
|
+
`Schema` export and states the thresholds, reporters and plugins it wants; you no
|
|
36
|
+
longer silently inherit a package manager, a plugin list or a break threshold.
|
|
37
|
+
|
|
38
|
+
- effect is now a required peer dependency. Install it alongside these packages. They previously bundled their own copy, which meant two Effect instances in one process and services that could not find each other across the boundary.
|
|
39
|
+
|
|
40
|
+
### Minor Changes
|
|
41
|
+
|
|
42
|
+
- Machine-readable mutation progress is a newline-delimited JSON file next to the HTML and JSON reports, not the console.
|
|
43
|
+
|
|
44
|
+
The console prints bounded progress prose: phase names, a count line, at most twenty surviving mutants, and a verdict. Killed mutants now advance that count. Child test runs during mutation no longer print per-test output or GitHub workflow commands.
|
|
45
|
+
|
|
46
|
+
If you parsed the console as JSON lines, read the stream file instead. A hard kill can leave that file without a closing verdict line.
|
|
47
|
+
|
|
48
|
+
### Patch Changes
|
|
49
|
+
|
|
50
|
+
- New version is published through npm trusted publishing, so it carries a provenance attestation you can verify.
|
|
51
|
+
|
|
52
|
+
- Peer Effect requirement advances to 4.0.0-rc.112. No API changes.
|
|
53
|
+
|
|
54
|
+
- A mutation run writes the JSON report the `json` reporter is configured to produce.
|
|
55
|
+
|
|
56
|
+
Vitest no longer reprints its full summary for every mutant.
|
|
57
|
+
|
|
58
|
+
- Each of these packages now has a README, so its registry page says what the package is, how
|
|
59
|
+
to install it, and what to import or register — previously the page was blank. The lint
|
|
60
|
+
plugins show the configuration line that enables what they recommend.
|
|
61
|
+
|
|
62
|
+
`@systemfsoftware/stryker-js-html-reporter` also carries its licence text
|
|
63
|
+
|
|
64
|
+
- The shared helpers package is gone. Nothing installs it any more, and the
|
|
65
|
+
handful of helpers worth sharing now live in the plugin contract next to the
|
|
66
|
+
types they serve:
|
|
67
|
+
|
|
68
|
+
- `strykerReportBugUrl`, `normalizeFileName`, `propertyPath`, `errorToString`
|
|
69
|
+
and `isErrnoException` from `@systemfsoftware/stryker-js/core`
|
|
70
|
+
- `noopLogger` from `@systemfsoftware/stryker-js/logging`
|
|
71
|
+
- `testFilesProvided` from `@systemfsoftware/stryker-js/test-runner`
|
|
72
|
+
|
|
73
|
+
If you imported any of those, change the specifier. Everything else it exported
|
|
74
|
+
had no consumer and is removed: use `Predicate.isNotNullish` from Effect in place
|
|
75
|
+
of `notEmpty`, and `RegExp.escape` in place of `escapeRegExp`.
|
|
76
|
+
|
|
77
|
+
- These packages no longer install dependencies they never imported, so installing them pulls less into your tree.
|
|
78
|
+
|
|
79
|
+
`tslib` is gone from all six. The mutation runner additionally stops installing `lodash.groupby`, `semver` and `source-map`, and the command line interface stops installing `@effect/platform-node-shared`. Nothing exported changes.
|
|
80
|
+
|
|
81
|
+
- Published packages no longer carry build artifacts left over from earlier builds. One package was shipping about a megabyte of bundled test-runner internals this way.
|
|
82
|
+
|
|
83
|
+
- Updated dependencies:
|
|
84
|
+
- @systemfsoftware/stryker-js@2.0.0
|
|
85
|
+
|
|
86
|
+
## 3.0.0
|
|
87
|
+
|
|
88
|
+
### Major Changes
|
|
89
|
+
|
|
90
|
+
- The dry-run decision moved to a plain kernel (dryRunCell removed); the mutant-run decision splits into branded MutantKilled|MutantSurvived|MutantTimeout|MutantDryError variants
|
|
91
|
+
|
|
92
|
+
### Patch Changes
|
|
93
|
+
|
|
94
|
+
- Updated dependencies:
|
|
95
|
+
- @systemfsoftware/effect-cell-types@6.0.0
|
|
96
|
+
- @systemfsoftware/stryker-js@1.0.0
|
|
97
|
+
|
|
3
98
|
## 2.1.0
|
|
4
99
|
|
|
5
100
|
### Minor Changes
|
package/dist/index.mjs
CHANGED
|
@@ -7,81 +7,61 @@ import { Cell, Workflow } from "@systemfsoftware/effect-cell-types";
|
|
|
7
7
|
import { Module } from "@systemfsoftware/stryker-js/Module";
|
|
8
8
|
import { INSTRUMENTER_CONSTANTS, errorToString, normalizeFileName } from "@systemfsoftware/stryker-js/Mutant";
|
|
9
9
|
import { TestRunner, TestRunnerFailed, testFilesProvided } from "@systemfsoftware/stryker-js/TestRunner";
|
|
10
|
+
import * as Context from "effect/Context";
|
|
10
11
|
import * as FileSystem from "effect/FileSystem";
|
|
11
|
-
import
|
|
12
|
+
import * as Match from "effect/Match";
|
|
12
13
|
import * as Option from "effect/Option";
|
|
13
14
|
import * as Path from "effect/Path";
|
|
14
15
|
import * as Ref from "effect/Ref";
|
|
15
16
|
import * as Result from "effect/Result";
|
|
16
17
|
import { Effect } from "effect";
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
exports: S.optional(S.Record(S.String, ExportEntry))
|
|
54
|
-
}), [S.Record(S.String, S.Unknown)]);
|
|
55
|
-
/**
|
|
56
|
-
* The dynamically imported project-local `vitest/node` module. The runtime
|
|
57
|
-
* check only asserts object-likeness: the module namespace is whatever the
|
|
58
|
-
* resolved package exports, and the consumers tolerate a missing
|
|
59
|
-
* `createVitest` via their own fallbacks.
|
|
60
|
-
*/
|
|
61
|
-
const VitestNodeModuleSchema = S.declare((input) => input !== null && typeof input === "object" && !Array.isArray(input), { description: "The project-local vitest/node module" });
|
|
62
|
-
/** The `package.json` document of a resolved vitest package. */
|
|
63
|
-
const VitestPackageSchema = S.Struct({ version: S.String });
|
|
64
|
-
//#endregion
|
|
65
|
-
//#region src/VitestDryRun.workflow.ts
|
|
66
|
-
/**
|
|
67
|
-
* VitestDryRun workflow — pure result-mapping for the dry-run phase.
|
|
68
|
-
*
|
|
69
|
-
* Every function here is pure: it maps raw vitest task payloads to
|
|
70
|
-
* Stryker run results without touching the filesystem or spawning
|
|
71
|
-
* processes. Impure orchestration lives in `Runner.ts`.
|
|
72
|
-
*/
|
|
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
|
+
};
|
|
73
54
|
var VitestDryRunCommand$1 = class extends S.TaggedClass()("VitestDryRunCommand", {
|
|
74
55
|
rawTests: S.Array(S.Unknown),
|
|
75
56
|
projectRoot: S.String,
|
|
76
57
|
hasExternalError: S.Boolean,
|
|
77
58
|
externalErrorText: S.String
|
|
78
59
|
}) {};
|
|
79
|
-
var VitestDryRunOutput
|
|
60
|
+
var VitestDryRunOutput = class extends S.TaggedClass()("VitestDryRunOutput", {
|
|
80
61
|
status: S.Literals(["Complete", "Error"]),
|
|
81
62
|
testsJson: S.String,
|
|
82
63
|
errorMessage: S.optional(S.String)
|
|
83
64
|
}) {};
|
|
84
|
-
S.TaggedError()("VitestDryRunError", { message: S.String });
|
|
85
65
|
const recordOption$1 = (value) => S.decodeUnknownOption(S.Record(S.String, S.Unknown))(value);
|
|
86
66
|
const getStringField$1 = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "string"));
|
|
87
67
|
const getNumberField$1 = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "number"));
|
|
@@ -130,12 +110,6 @@ const toRawTestIdRaw$1 = (test) => {
|
|
|
130
110
|
onSome: (file) => Option.getOrElse(Option.fromNullishOr(getFilepath$1(file)), () => "unknown.js")
|
|
131
111
|
})}#${collectTestNameRaw$1(test)}`;
|
|
132
112
|
};
|
|
133
|
-
/**
|
|
134
|
-
* A test id is `<file>#<test name>`, and the file is reported relative to the
|
|
135
|
-
* project root so an id is stable across machines and sandbox directories.
|
|
136
|
-
* Vitest reports an absolute path, so the root prefix is stripped here rather
|
|
137
|
-
* than resolved — a decision body has no path service and needs none.
|
|
138
|
-
*/
|
|
139
113
|
const normalizeTestIdRaw$1 = (id, projectRoot) => {
|
|
140
114
|
const hash = id.indexOf("#");
|
|
141
115
|
if (hash === -1) return id;
|
|
@@ -220,65 +194,146 @@ const convertTestRaw$1 = (test, projectRoot) => {
|
|
|
220
194
|
status
|
|
221
195
|
})));
|
|
222
196
|
};
|
|
223
|
-
const decideVitestDryRun$1 = (command) => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
}))), Match.orElse(() => Result.succeed(VitestDryRunOutput$1.make({
|
|
237
|
-
status: "Complete",
|
|
238
|
-
testsJson: JSON.stringify(tests),
|
|
239
|
-
errorMessage: void 0
|
|
240
|
-
}))));
|
|
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();
|
|
241
210
|
};
|
|
242
|
-
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);
|
|
243
270
|
//#endregion
|
|
244
|
-
//#region src/
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
reason: S.optional(S.String)
|
|
269
|
-
}) {};
|
|
270
|
-
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 });
|
|
271
295
|
var VitestDryRunCommand = class extends S.TaggedClass()("VitestDryRunCommand", {
|
|
272
296
|
rawTests: S.Array(S.Unknown),
|
|
273
297
|
projectRoot: S.String,
|
|
274
298
|
hasExternalError: S.Boolean,
|
|
275
299
|
externalErrorText: S.String
|
|
276
300
|
}) {};
|
|
277
|
-
var
|
|
278
|
-
|
|
301
|
+
var DryRunComplete = class extends S.TaggedClass()("Complete", { testsJson: S.String }) {};
|
|
302
|
+
var DryRunExternalError = class extends S.TaggedClass()("Error", {
|
|
279
303
|
testsJson: S.String,
|
|
280
|
-
errorMessage: S.
|
|
304
|
+
errorMessage: S.String
|
|
281
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" });
|
|
282
337
|
const recordOption = (value) => S.decodeUnknownOption(S.Record(S.String, S.Unknown))(value);
|
|
283
338
|
const getStringField = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "string"));
|
|
284
339
|
const getNumberField = (record, key) => Option.fromNullishOr(record[key]).pipe(Option.filter((v) => typeof v === "number"));
|
|
@@ -327,12 +382,6 @@ const toRawTestIdRaw = (test) => {
|
|
|
327
382
|
onSome: (file) => Option.getOrElse(Option.fromNullishOr(getFilepath(file)), () => "unknown.js")
|
|
328
383
|
})}#${collectTestNameRaw(test)}`;
|
|
329
384
|
};
|
|
330
|
-
/**
|
|
331
|
-
* A test id is `<file>#<test name>`, and the file is reported relative to the
|
|
332
|
-
* project root so an id is stable across machines and sandbox directories.
|
|
333
|
-
* Vitest reports an absolute path, so the root prefix is stripped here rather
|
|
334
|
-
* than resolved — a decision body has no path service and needs none.
|
|
335
|
-
*/
|
|
336
385
|
const normalizeTestIdRaw = (id, projectRoot) => {
|
|
337
386
|
const hash = id.indexOf("#");
|
|
338
387
|
if (hash === -1) return id;
|
|
@@ -417,134 +466,14 @@ const convertTestRaw = (test, projectRoot) => {
|
|
|
417
466
|
status
|
|
418
467
|
})));
|
|
419
468
|
};
|
|
420
|
-
const decideVitestDryRun = (command) =>
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
status: "Complete",
|
|
426
|
-
testsJson: JSON.stringify(tests),
|
|
427
|
-
errorMessage: void 0
|
|
428
|
-
}))));
|
|
429
|
-
/**
|
|
430
|
-
* The hit-limit cutoff: `Some` iff both numbers are present AND the count
|
|
431
|
-
* strictly exceeds the limit. The boundary is `>` and not `>=` on purpose —
|
|
432
|
-
* `hitCount === hitLimit` is the last permitted hit, not one too many.
|
|
433
|
-
*/
|
|
434
|
-
const hitLimitReason = (hitCount, hitLimit) => {
|
|
435
|
-
if (hitCount === void 0 || hitLimit === void 0) return Option.none();
|
|
436
|
-
if (hitCount > hitLimit) return Option.some(`Hit limit reached (${hitCount}/${hitLimit})`);
|
|
437
|
-
return Option.none();
|
|
438
|
-
};
|
|
439
|
-
const decideVitestMutantRun = (command) => Match.value(hitLimitReason(command.hitCount, command.hitLimit)).pipe(Match.when(Option.isSome, (hit) => Result.succeed(VitestMutantRunOutput.make({
|
|
440
|
-
status: "Timeout",
|
|
441
|
-
testsJson: "[]",
|
|
442
|
-
errorMessage: void 0,
|
|
443
|
-
killerIds: void 0,
|
|
444
|
-
failureMessage: void 0,
|
|
445
|
-
reason: hit.value
|
|
446
|
-
}))), Match.when(Option.isNone, () => {
|
|
447
|
-
const dryOut = decideVitestDryRun(VitestDryRunCommand.make({
|
|
448
|
-
rawTests: command.rawTests,
|
|
449
|
-
projectRoot: command.projectRoot,
|
|
450
|
-
hasExternalError: command.hasExternalError,
|
|
451
|
-
externalErrorText: command.externalErrorText
|
|
452
|
-
}));
|
|
453
|
-
return Match.value(Result.isFailure(dryOut)).pipe(Match.when(true, () => Result.fail(new VitestMutantRunError({ message: "dry run mapping failed" }))), Match.when(false, () => {
|
|
454
|
-
const dry = Result.getOrElse(dryOut, () => VitestDryRunOutput.make({
|
|
455
|
-
status: "Complete",
|
|
456
|
-
testsJson: "[]",
|
|
457
|
-
errorMessage: void 0
|
|
458
|
-
}));
|
|
459
|
-
return Match.value(dry.status === "Error").pipe(Match.when(true, () => Result.succeed(VitestMutantRunOutput.make({
|
|
460
|
-
status: "Error",
|
|
461
|
-
testsJson: "[]",
|
|
462
|
-
errorMessage: dry.errorMessage,
|
|
463
|
-
killerIds: void 0,
|
|
464
|
-
failureMessage: void 0
|
|
465
|
-
}))), Match.when(false, () => {
|
|
466
|
-
const testsOption = Option.liftThrowable((input) => JSON.parse(input))(dry.testsJson).pipe(Option.filter((v) => Array.isArray(v)));
|
|
467
|
-
const killed = Option.getOrElse(testsOption, () => []).filter((t) => t.status === "failed");
|
|
468
|
-
return Match.value(killed.length > 0).pipe(Match.when(true, () => Match.value(command.reportAllKillers).pipe(Match.when(true, () => {
|
|
469
|
-
const firstKiller = Option.fromNullishOr(killed[0]);
|
|
470
|
-
const failureMessage = Option.match(firstKiller, {
|
|
471
|
-
onNone: () => void 0,
|
|
472
|
-
onSome: (k) => k.failureMessage
|
|
473
|
-
});
|
|
474
|
-
return Result.succeed(VitestMutantRunOutput.make({
|
|
475
|
-
status: "Killed",
|
|
476
|
-
testsJson: dry.testsJson,
|
|
477
|
-
errorMessage: void 0,
|
|
478
|
-
killerIds: killed.map((t) => t.id),
|
|
479
|
-
failureMessage
|
|
480
|
-
}));
|
|
481
|
-
}), Match.when(false, () => {
|
|
482
|
-
const first = Option.fromNullishOr(killed[0]);
|
|
483
|
-
const failureMessage = Option.match(first, {
|
|
484
|
-
onNone: () => void 0,
|
|
485
|
-
onSome: (k) => k.failureMessage
|
|
486
|
-
});
|
|
487
|
-
return Match.value(Option.isSome(first)).pipe(Match.when(true, () => Result.succeed(VitestMutantRunOutput.make({
|
|
488
|
-
status: "Killed",
|
|
489
|
-
testsJson: dry.testsJson,
|
|
490
|
-
errorMessage: void 0,
|
|
491
|
-
killerIds: Option.match(first, {
|
|
492
|
-
onNone: () => void 0,
|
|
493
|
-
onSome: (k) => [k.id]
|
|
494
|
-
}),
|
|
495
|
-
failureMessage
|
|
496
|
-
}))), Match.when(false, () => Result.succeed(VitestMutantRunOutput.make({
|
|
497
|
-
status: "Killed",
|
|
498
|
-
testsJson: dry.testsJson,
|
|
499
|
-
errorMessage: void 0,
|
|
500
|
-
killerIds: Option.getOrUndefined(Option.match(first, {
|
|
501
|
-
onNone: () => Option.none(),
|
|
502
|
-
onSome: (k) => Option.some([k.id])
|
|
503
|
-
})),
|
|
504
|
-
failureMessage
|
|
505
|
-
}))), Match.exhaustive);
|
|
506
|
-
}), Match.exhaustive)), Match.when(false, () => Result.succeed(VitestMutantRunOutput.make({
|
|
507
|
-
status: "Survived",
|
|
508
|
-
testsJson: dry.testsJson,
|
|
509
|
-
errorMessage: void 0,
|
|
510
|
-
killerIds: void 0,
|
|
511
|
-
failureMessage: void 0
|
|
512
|
-
}))), Match.exhaustive);
|
|
513
|
-
}), Match.exhaustive);
|
|
514
|
-
}), Match.exhaustive);
|
|
515
|
-
}), Match.exhaustive);
|
|
516
|
-
const vitestMutantRunWorkflow = Workflow.make(VitestMutantRunCommand, decideVitestMutantRun);
|
|
517
|
-
//#endregion
|
|
518
|
-
//#region src/Runner.ts
|
|
519
|
-
function fromTestId(id) {
|
|
520
|
-
const [file, ...name] = id.split("#");
|
|
521
|
-
return {
|
|
522
|
-
file,
|
|
523
|
-
test: name.join("#")
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
function normalizeTestId(id, projectRoot, pathService) {
|
|
527
|
-
const { file, test } = fromTestId(id);
|
|
528
|
-
return `${normalizeFileName(pathService.relative(projectRoot, file))}#${test}`;
|
|
529
|
-
}
|
|
530
|
-
function normalizeCoverage(rawCoverage, projectRoot, pathService) {
|
|
531
|
-
return {
|
|
532
|
-
perTest: Object.fromEntries(Object.entries(rawCoverage.perTest).map(([rawTestId, coverageData]) => [normalizeTestId(rawTestId, projectRoot, pathService), coverageData])),
|
|
533
|
-
static: rawCoverage.static
|
|
534
|
-
};
|
|
535
|
-
}
|
|
536
|
-
function collectTestsFromSuite(suite) {
|
|
537
|
-
return suite.tasks.flatMap((task) => {
|
|
538
|
-
if (task.type === "suite") return collectTestsFromSuite(task);
|
|
539
|
-
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}`
|
|
540
474
|
});
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (error instanceof Error && "code" in error) return typeof Reflect.get(error, "code") === "string";
|
|
544
|
-
return false;
|
|
545
|
-
}
|
|
546
|
-
/** @see https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/node/errors.ts */
|
|
547
|
-
const VITEST_ERROR_CODES = Object.freeze({ FILES_NOT_FOUND: "VITEST_FILES_NOT_FOUND" });
|
|
475
|
+
return DryRunComplete.make({ testsJson: JSON.stringify(tests) });
|
|
476
|
+
};
|
|
548
477
|
const SOURCE_CONDITION = "@systemfsoftware/source";
|
|
549
478
|
const sourceTargetOf = (entry) => {
|
|
550
479
|
if (typeof entry === "string") return (() => {
|
|
@@ -592,12 +521,6 @@ const readSandboxSelfAliases = (projectRoot) => Effect$1.gen(function* () {
|
|
|
592
521
|
onSome: (manifest) => sandboxSelfAliases(manifest, projectRoot, pathService)
|
|
593
522
|
});
|
|
594
523
|
});
|
|
595
|
-
/**
|
|
596
|
-
* Vite records a package specifier as a bare dep. Vitest related-mode then
|
|
597
|
-
* joins that specifier onto the project root, misses the file, and reports
|
|
598
|
-
* zero tests. Returning the sandbox source path from `resolveId` makes the
|
|
599
|
-
* dep a real filesystem path related-mode can walk.
|
|
600
|
-
*/
|
|
601
524
|
const sandboxSelfPlugin = (aliases) => ({
|
|
602
525
|
name: "stryker-sandbox-self-exports",
|
|
603
526
|
enforce: "pre",
|
|
@@ -924,169 +847,173 @@ const makeVitestRunnerLayer = (input) => Layer.effect(TestRunner, Effect$1.gen(f
|
|
|
924
847
|
})()
|
|
925
848
|
};
|
|
926
849
|
});
|
|
927
|
-
const
|
|
928
|
-
(
|
|
929
|
-
|
|
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);
|
|
930
973
|
const filter = (() => {
|
|
931
974
|
if (hasTestFiles) return {
|
|
932
|
-
testFiles: [...
|
|
975
|
+
testFiles: [...options.testFiles ?? []],
|
|
933
976
|
relatedFiles: (() => {
|
|
934
|
-
if (
|
|
977
|
+
if (options.files !== void 0) return [...options.files];
|
|
935
978
|
})()
|
|
936
979
|
};
|
|
937
980
|
return { relatedFiles: (() => {
|
|
938
|
-
if (
|
|
981
|
+
if (options.files !== void 0) return [...options.files];
|
|
939
982
|
})() };
|
|
940
983
|
})();
|
|
941
984
|
const { rawTests, hasExternalError, externalErrorText } = yield* collectRaw(filter);
|
|
942
|
-
|
|
985
|
+
const decision = decideVitestDryRun(new VitestDryRunCommand({
|
|
943
986
|
rawTests,
|
|
944
987
|
projectRoot: input.sandboxDirectory,
|
|
945
988
|
hasExternalError,
|
|
946
989
|
externalErrorText
|
|
947
|
-
};
|
|
948
|
-
|
|
949
|
-
rawTests: raw.rawTests,
|
|
950
|
-
projectRoot: raw.projectRoot,
|
|
951
|
-
hasExternalError: raw.hasExternalError,
|
|
952
|
-
externalErrorText: raw.externalErrorText
|
|
953
|
-
}))), Cell.decide(vitestDryRunWorkflow), Cell.encode((outcome) => Result.match(outcome, {
|
|
954
|
-
onFailure: (e) => ({
|
|
990
|
+
}));
|
|
991
|
+
const result = Match.value(decision).pipe(Match.tag("Error", (error) => ({
|
|
955
992
|
status: "error",
|
|
956
|
-
errorMessage:
|
|
957
|
-
}),
|
|
958
|
-
|
|
959
|
-
const raw = JSON.parse(out.testsJson);
|
|
993
|
+
errorMessage: error.errorMessage
|
|
994
|
+
})), Match.tag("Complete", (complete) => {
|
|
995
|
+
const raw = JSON.parse(complete.testsJson);
|
|
960
996
|
let tests;
|
|
961
997
|
if (Array.isArray(raw)) tests = raw.filter(isTestResultLike);
|
|
962
998
|
else tests = [];
|
|
963
|
-
if (out.status === "Error") return {
|
|
964
|
-
status: "error",
|
|
965
|
-
errorMessage: out.errorMessage ?? "unknown"
|
|
966
|
-
};
|
|
967
999
|
return {
|
|
968
1000
|
status: "complete",
|
|
969
1001
|
tests
|
|
970
1002
|
};
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
if (output.status === "complete") {
|
|
1003
|
+
}), Match.exhaustive);
|
|
1004
|
+
if (result.status === "complete") {
|
|
974
1005
|
const mutantCoverage = yield* readMutantCoverage.pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
|
|
975
1006
|
runnerName: "vitest",
|
|
976
1007
|
phase: "dryRun",
|
|
977
1008
|
cause: errorToString(cause)
|
|
978
1009
|
})));
|
|
979
1010
|
if (mutantCoverage !== void 0) return {
|
|
980
|
-
...
|
|
1011
|
+
...result,
|
|
981
1012
|
mutantCoverage
|
|
982
1013
|
};
|
|
983
1014
|
}
|
|
984
|
-
return
|
|
985
|
-
})))
|
|
986
|
-
const mutantRunDescription = pipe(Cell.read((command) => Effect$1.gen(function* () {
|
|
987
|
-
const ctx = yield* requireCtx;
|
|
988
|
-
ctx.provide("mode", "mutant");
|
|
989
|
-
ctx.provide("hitLimit", command.hitLimit);
|
|
990
|
-
ctx.provide("mutantActivation", command.mutantActivation);
|
|
991
|
-
ctx.provide("activeMutant", command.activeMutant.id);
|
|
992
|
-
const { rawTests, hasExternalError, externalErrorText } = yield* collectRaw({
|
|
993
|
-
testIds: (() => {
|
|
994
|
-
if (command.testFilter !== void 0) return [...command.testFilter];
|
|
995
|
-
})(),
|
|
996
|
-
relatedFiles: [command.sandboxFileName]
|
|
997
|
-
});
|
|
998
|
-
const hitCount = yield* readHitCount.pipe(Effect$1.mapError((cause) => new TestRunnerFailed({
|
|
999
|
-
runnerName: "vitest",
|
|
1000
|
-
phase: "mutantRun",
|
|
1001
|
-
cause: errorToString(cause)
|
|
1002
|
-
})), Effect$1.option, Effect$1.map(Option.getOrUndefined));
|
|
1003
|
-
const reportAllKillers = (() => {
|
|
1004
|
-
if (typeof input.options.disableBail === "boolean") return input.options.disableBail;
|
|
1005
|
-
return false;
|
|
1006
|
-
})();
|
|
1007
|
-
if (hitCount === void 0) return {
|
|
1008
|
-
rawTests,
|
|
1009
|
-
projectRoot: input.sandboxDirectory,
|
|
1010
|
-
hasExternalError,
|
|
1011
|
-
externalErrorText,
|
|
1012
|
-
hitLimit: command.hitLimit,
|
|
1013
|
-
reportAllKillers
|
|
1014
|
-
};
|
|
1015
|
-
return {
|
|
1016
|
-
rawTests,
|
|
1017
|
-
projectRoot: input.sandboxDirectory,
|
|
1018
|
-
hasExternalError,
|
|
1019
|
-
externalErrorText,
|
|
1020
|
-
hitCount,
|
|
1021
|
-
hitLimit: command.hitLimit,
|
|
1022
|
-
reportAllKillers
|
|
1023
|
-
};
|
|
1024
|
-
})), Cell.decode((raw) => {
|
|
1025
|
-
const base = {
|
|
1026
|
-
rawTests: raw.rawTests,
|
|
1027
|
-
projectRoot: raw.projectRoot,
|
|
1028
|
-
hasExternalError: raw.hasExternalError,
|
|
1029
|
-
externalErrorText: raw.externalErrorText,
|
|
1030
|
-
reportAllKillers: raw.reportAllKillers
|
|
1031
|
-
};
|
|
1032
|
-
if (raw.hitCount !== void 0) {
|
|
1033
|
-
if (raw.hitLimit !== void 0) return Result.succeed(new VitestMutantRunCommand({
|
|
1034
|
-
...base,
|
|
1035
|
-
hitCount: raw.hitCount,
|
|
1036
|
-
hitLimit: raw.hitLimit
|
|
1037
|
-
}));
|
|
1038
|
-
return Result.succeed(new VitestMutantRunCommand({
|
|
1039
|
-
...base,
|
|
1040
|
-
hitCount: raw.hitCount
|
|
1041
|
-
}));
|
|
1042
|
-
}
|
|
1043
|
-
if (raw.hitLimit !== void 0) return Result.succeed(new VitestMutantRunCommand({
|
|
1044
|
-
...base,
|
|
1045
|
-
hitLimit: raw.hitLimit
|
|
1046
|
-
}));
|
|
1047
|
-
return Result.succeed(new VitestMutantRunCommand(base));
|
|
1048
|
-
}), Cell.decide(vitestMutantRunWorkflow), Cell.encode((outcome) => Result.match(outcome, {
|
|
1049
|
-
onFailure: (e) => ({
|
|
1050
|
-
status: "error",
|
|
1051
|
-
errorMessage: e.message
|
|
1052
|
-
}),
|
|
1053
|
-
onSuccess: (out) => {
|
|
1054
|
-
let parsed;
|
|
1055
|
-
try {
|
|
1056
|
-
const raw = JSON.parse(out.testsJson);
|
|
1057
|
-
if (Array.isArray(raw)) parsed = raw.filter(isIdRecord);
|
|
1058
|
-
else parsed = [];
|
|
1059
|
-
} catch {
|
|
1060
|
-
parsed = [];
|
|
1061
|
-
}
|
|
1062
|
-
const nrOfTests = parsed.length;
|
|
1063
|
-
if (out.status === "Error") return {
|
|
1064
|
-
status: "error",
|
|
1065
|
-
errorMessage: out.errorMessage ?? "unknown"
|
|
1066
|
-
};
|
|
1067
|
-
if (out.status === "Timeout") {
|
|
1068
|
-
if (out.reason === void 0) return { status: "timeout" };
|
|
1069
|
-
return {
|
|
1070
|
-
status: "timeout",
|
|
1071
|
-
reason: out.reason
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
if (out.status === "Killed") return {
|
|
1075
|
-
status: "killed",
|
|
1076
|
-
failureMessage: out.failureMessage ?? "",
|
|
1077
|
-
killedBy: (() => {
|
|
1078
|
-
if (out.killerIds !== void 0) return [...out.killerIds];
|
|
1079
|
-
return [];
|
|
1080
|
-
})(),
|
|
1081
|
-
nrOfTests
|
|
1082
|
-
};
|
|
1083
|
-
return {
|
|
1084
|
-
status: "survived",
|
|
1085
|
-
nrOfTests
|
|
1086
|
-
};
|
|
1087
|
-
}
|
|
1088
|
-
})), Cell.write((output) => Effect$1.succeed(output)));
|
|
1089
|
-
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) => (() => {
|
|
1090
1017
|
if (cause instanceof TestRunnerFailed) return cause;
|
|
1091
1018
|
return new TestRunnerFailed({
|
|
1092
1019
|
runnerName: "vitest",
|
|
@@ -1094,7 +1021,7 @@ const makeVitestRunnerLayer = (input) => Layer.effect(TestRunner, Effect$1.gen(f
|
|
|
1094
1021
|
cause: errorToString(cause)
|
|
1095
1022
|
});
|
|
1096
1023
|
})()));
|
|
1097
|
-
const mutantRun = (options) => Cell.
|
|
1024
|
+
const mutantRun = (options) => Cell.run(mutantRunCell, options).pipe(Effect$1.provideService(VitestHarness, harnessImpl), Effect$1.mapError((cause) => (() => {
|
|
1098
1025
|
if (cause instanceof TestRunnerFailed) return cause;
|
|
1099
1026
|
return new TestRunnerFailed({
|
|
1100
1027
|
runnerName: "vitest",
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@systemfsoftware/stryker-js-vitest-runner",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/systemfsoftware/systemfsoftware.git",
|
|
7
|
-
"directory": "packages/
|
|
7
|
+
"directory": "packages/stryker-js/stryker-js-vitest-runner"
|
|
8
8
|
},
|
|
9
|
-
"homepage": "https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/
|
|
9
|
+
"homepage": "https://github.com/systemfsoftware/systemfsoftware/tree/main/packages/stryker-js/stryker-js-vitest-runner#readme",
|
|
10
10
|
"bugs": "https://github.com/systemfsoftware/systemfsoftware/issues",
|
|
11
11
|
"description": "Vitest test-runner plugin for Stryker",
|
|
12
12
|
"type": "module",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"coverageAnalysis": "perTest"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@systemfsoftware/effect-cell-types": "^
|
|
35
|
-
"@systemfsoftware/stryker-js": "^
|
|
34
|
+
"@systemfsoftware/effect-cell-types": "^6.0.1",
|
|
35
|
+
"@systemfsoftware/stryker-js": "^2.0.0"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
38
|
"effect": "^4.0.0-rc.112",
|
|
@@ -50,12 +50,11 @@
|
|
|
50
50
|
"tsdown": "^0.22.14",
|
|
51
51
|
"typescript": "^7",
|
|
52
52
|
"vitest": "^4",
|
|
53
|
-
"@systemfsoftware/
|
|
54
|
-
"@systemfsoftware/effect-gherkin-spec": "^4.0.0",
|
|
53
|
+
"@systemfsoftware/effect-gherkin-spec": "^4.0.1",
|
|
55
54
|
"@systemfsoftware/oxlint-config": "^0.1.0",
|
|
56
|
-
"@systemfsoftware/stryker-js-platform-node": "^0.2.0",
|
|
57
55
|
"@systemfsoftware/tsconfig": "^1.3.3",
|
|
58
|
-
"@systemfsoftware/vitest-config": "^0.1.0"
|
|
56
|
+
"@systemfsoftware/vitest-config": "^0.1.0",
|
|
57
|
+
"@systemfsoftware/all": "^1.1.3"
|
|
59
58
|
},
|
|
60
59
|
"publishConfig": {
|
|
61
60
|
"provenance": true
|