module-spec 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.2.0
4
4
 
5
+ - `mutate` counts only the module's core, shell and door tests. An adapter's test and a
6
+ contract test prove the adapter against the outside and are slow, so a mutant that only
7
+ they catch now lives until a test through the door catches it. On factory's chat module
8
+ those tests were 39 of 40 seconds of test time, rerun for every core mutant.
9
+ - Under vitest, `mutate` keeps each module's verdicts in
10
+ `node_modules/.cache/module-spec/mutate/`, and a rerun tests again only the mutants whose
11
+ code or covering tests changed.
5
12
  - `mutate` changes only `core/`, `shell/` and the door, as the spec says. Before,
6
13
  it also changed files in no part, such as an app's `main.ts` or a
7
14
  `vitest.config.ts`.
@@ -40,8 +40,19 @@ function runnerConfig(root, tests) {
40
40
  };
41
41
  }
42
42
 
43
+ /**
44
+ * Under vitest, Stryker keeps each mutant's verdict and the tests that covered it, so a rerun tests again only the
45
+ * mutants whose code or covering tests changed. The command runner reports no tests, so a weakened test would keep
46
+ * its old verdict there, and it reruns everything. The file sits under node_modules/.cache, which no project commits.
47
+ */
48
+ function incrementalConfig(root, module) {
49
+ if (testRunnerOf(root) !== "vitest") return {};
50
+ const file = `${module.dir.replaceAll("/", "_")}.json`;
51
+ return { incremental: true, incrementalFile: path.join(root, "node_modules", ".cache", "module-spec", "mutate", file) };
52
+ }
53
+
43
54
  /** Mutation testing by the kit's own StrykerJS, in a sandbox under the system temp folder. */
44
- export async function mutate({ root, files, tests }) {
55
+ export async function mutate({ root, module, files, tests }) {
45
56
  const work = fs.mkdtempSync(path.join(os.tmpdir(), "module-spec-stryker-"));
46
57
  try {
47
58
  const report = path.join(work, "report.json");
@@ -58,6 +69,7 @@ export async function mutate({ root, files, tests }) {
58
69
  // runners read TypeScript without it, so the rewrite is skipped by naming a file that does not exist.
59
70
  tsconfigFile: "module-spec-skips-the-tsconfig-rewrite.json",
60
71
  logLevel: "error",
72
+ ...incrementalConfig(root, module),
61
73
  }));
62
74
  const dir = kitPackageDir("@stryker-mutator/core");
63
75
  const bin = path.join(dir, JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).bin.stryker);
package/lib/mutation.js CHANGED
@@ -15,13 +15,19 @@ export const MUTATION_REPORT = "mutation-report.txt";
15
15
  */
16
16
  const MUTATED = new Set(["core", "shell"]);
17
17
 
18
- /** The files mutation testing changes in a module, and the module's own tests, which must notice. */
18
+ /**
19
+ * The files mutation testing changes in a module, and the module's own tests, which must notice. An adapter's test
20
+ * proves the adapter against the outside and is slow, while the code it reaches is proven through the door, so it
21
+ * does not count.
22
+ */
19
23
  export function mutationTargets(module) {
20
24
  const files = [];
21
25
  const tests = [];
22
26
  for (const file of module.files) {
23
27
  const place = placeOf(module, file);
24
- if (place.test) tests.push(file);
28
+ if (place.test) {
29
+ if (place.part !== "adapters" && !place.contractTest) tests.push(file);
30
+ }
25
31
  else if (place.door || MUTATED.has(place.part)) files.push(file);
26
32
  }
27
33
  return { files, tests };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "module-spec",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "How every project splits its code into modules, with the checks that enforce it. The why behind each rule: spec/.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@12.10.1",
package/spec/tests.md CHANGED
@@ -130,8 +130,10 @@ code does. Mutation testing catches it, because every mutant lives.
130
130
  hold only types, not `adapters/`, whose contract tests need the real
131
131
  service, and not `view/`, which holds no rules
132
132
  ([F3](frontend.md#f3-a-view-holds-no-rules)).
133
- - **Which tests count:** only the module's own. A mutant that only another
134
- module's test catches still lives.
133
+ - **Which tests count:** only the module's own, and not its adapters' tests
134
+ or contract tests. Those prove an adapter against the outside and are slow.
135
+ The core they reach is proven through the door. A mutant that only another
136
+ module's test, or an adapter's test, catches still lives.
135
137
  - **Zero live mutants:** `module-spec mutate` fails on any live mutant, and
136
138
  names it, even on a module's first run.
137
139
  - **When no test can tell:** some mutants behave exactly like the code, such