@mutineerjs/mutineer 0.6.0 → 0.8.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.
Files changed (69) hide show
  1. package/README.md +32 -15
  2. package/dist/bin/__tests__/mutineer.spec.js +67 -2
  3. package/dist/bin/mutineer.d.ts +6 -1
  4. package/dist/bin/mutineer.js +58 -2
  5. package/dist/core/__tests__/schemata.spec.d.ts +1 -0
  6. package/dist/core/__tests__/schemata.spec.js +165 -0
  7. package/dist/core/schemata.d.ts +22 -0
  8. package/dist/core/schemata.js +236 -0
  9. package/dist/mutators/__tests__/operator.spec.js +97 -1
  10. package/dist/mutators/__tests__/registry.spec.js +8 -0
  11. package/dist/mutators/operator.d.ts +8 -0
  12. package/dist/mutators/operator.js +58 -1
  13. package/dist/mutators/registry.js +9 -1
  14. package/dist/mutators/utils.d.ts +2 -0
  15. package/dist/mutators/utils.js +58 -1
  16. package/dist/runner/__tests__/args.spec.js +89 -1
  17. package/dist/runner/__tests__/cache.spec.js +65 -8
  18. package/dist/runner/__tests__/cleanup.spec.js +37 -0
  19. package/dist/runner/__tests__/coverage-resolver.spec.js +5 -0
  20. package/dist/runner/__tests__/discover.spec.js +128 -0
  21. package/dist/runner/__tests__/orchestrator.spec.js +332 -2
  22. package/dist/runner/__tests__/pool-executor.spec.js +107 -1
  23. package/dist/runner/__tests__/ts-checker.spec.d.ts +1 -0
  24. package/dist/runner/__tests__/ts-checker.spec.js +115 -0
  25. package/dist/runner/args.d.ts +18 -0
  26. package/dist/runner/args.js +37 -0
  27. package/dist/runner/cache.d.ts +19 -3
  28. package/dist/runner/cache.js +14 -7
  29. package/dist/runner/cleanup.d.ts +3 -1
  30. package/dist/runner/cleanup.js +19 -2
  31. package/dist/runner/coverage-resolver.js +1 -1
  32. package/dist/runner/discover.d.ts +1 -1
  33. package/dist/runner/discover.js +30 -20
  34. package/dist/runner/orchestrator.d.ts +1 -0
  35. package/dist/runner/orchestrator.js +114 -19
  36. package/dist/runner/pool-executor.d.ts +7 -0
  37. package/dist/runner/pool-executor.js +29 -7
  38. package/dist/runner/shared/__tests__/mutant-paths.spec.js +30 -1
  39. package/dist/runner/shared/index.d.ts +1 -1
  40. package/dist/runner/shared/index.js +1 -1
  41. package/dist/runner/shared/mutant-paths.d.ts +17 -0
  42. package/dist/runner/shared/mutant-paths.js +24 -0
  43. package/dist/runner/ts-checker-worker.d.ts +5 -0
  44. package/dist/runner/ts-checker-worker.js +66 -0
  45. package/dist/runner/ts-checker.d.ts +36 -0
  46. package/dist/runner/ts-checker.js +210 -0
  47. package/dist/runner/types.d.ts +2 -0
  48. package/dist/runner/vitest/__tests__/adapter.spec.js +41 -0
  49. package/dist/runner/vitest/__tests__/plugin.spec.js +151 -0
  50. package/dist/runner/vitest/__tests__/pool.spec.js +85 -0
  51. package/dist/runner/vitest/__tests__/worker-runtime.spec.js +126 -0
  52. package/dist/runner/vitest/adapter.js +14 -9
  53. package/dist/runner/vitest/plugin.d.ts +3 -0
  54. package/dist/runner/vitest/plugin.js +49 -11
  55. package/dist/runner/vitest/pool.d.ts +4 -1
  56. package/dist/runner/vitest/pool.js +25 -4
  57. package/dist/runner/vitest/worker-runtime.d.ts +1 -0
  58. package/dist/runner/vitest/worker-runtime.js +57 -18
  59. package/dist/runner/vitest/worker.mjs +10 -0
  60. package/dist/types/config.d.ts +16 -0
  61. package/dist/types/mutant.d.ts +5 -2
  62. package/dist/utils/CompileErrors.d.ts +7 -0
  63. package/dist/utils/CompileErrors.js +24 -0
  64. package/dist/utils/__tests__/CompileErrors.spec.d.ts +1 -0
  65. package/dist/utils/__tests__/CompileErrors.spec.js +96 -0
  66. package/dist/utils/__tests__/summary.spec.js +126 -2
  67. package/dist/utils/summary.d.ts +23 -1
  68. package/dist/utils/summary.js +63 -3
  69. package/package.json +2 -1
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import os from 'node:os';
5
- import { clearCacheOnStart, saveCacheAtomic, decodeCacheKey, keyForTests, hash, readMutantCache, } from '../cache.js';
5
+ import { clearCacheOnStart, saveCacheAtomic, decodeCacheKey, keyForTests, hash, readMutantCache, getCacheFilename, } from '../cache.js';
6
6
  let tmpDir;
7
7
  beforeEach(async () => {
8
8
  tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-cache-'));
@@ -10,9 +10,19 @@ beforeEach(async () => {
10
10
  afterEach(async () => {
11
11
  await fs.rm(tmpDir, { recursive: true, force: true });
12
12
  });
13
+ describe('getCacheFilename', () => {
14
+ it('returns default filename when no shard', () => {
15
+ expect(getCacheFilename()).toBe('.mutineer-cache.json');
16
+ expect(getCacheFilename(undefined)).toBe('.mutineer-cache.json');
17
+ });
18
+ it('returns shard-namespaced filename when shard provided', () => {
19
+ expect(getCacheFilename({ index: 1, total: 2 })).toBe('.mutineer-cache-shard-1-of-2.json');
20
+ expect(getCacheFilename({ index: 3, total: 4 })).toBe('.mutineer-cache-shard-3-of-4.json');
21
+ });
22
+ });
13
23
  describe('clearCacheOnStart', () => {
14
24
  it('removes the cache file if it exists', async () => {
15
- const cacheFile = path.join(tmpDir, '.mutate-cache.json');
25
+ const cacheFile = path.join(tmpDir, '.mutineer-cache.json');
16
26
  await fs.writeFile(cacheFile, '{}');
17
27
  await clearCacheOnStart(tmpDir);
18
28
  await expect(fs.access(cacheFile)).rejects.toThrow();
@@ -20,6 +30,19 @@ describe('clearCacheOnStart', () => {
20
30
  it('does not throw if cache file does not exist', async () => {
21
31
  await expect(clearCacheOnStart(tmpDir)).resolves.toBeUndefined();
22
32
  });
33
+ it('removes shard-specific cache file', async () => {
34
+ const shardFile = path.join(tmpDir, '.mutineer-cache-shard-1-of-2.json');
35
+ await fs.writeFile(shardFile, '{}');
36
+ await clearCacheOnStart(tmpDir, { index: 1, total: 2 });
37
+ await expect(fs.access(shardFile)).rejects.toThrow();
38
+ });
39
+ it('does not remove default cache when shard is specified', async () => {
40
+ const defaultFile = path.join(tmpDir, '.mutineer-cache.json');
41
+ await fs.writeFile(defaultFile, '{}');
42
+ await clearCacheOnStart(tmpDir, { index: 1, total: 2 });
43
+ // default file should still exist
44
+ await expect(fs.access(defaultFile)).resolves.toBeUndefined();
45
+ });
23
46
  });
24
47
  describe('saveCacheAtomic', () => {
25
48
  it('writes cache data to the file', async () => {
@@ -33,7 +56,7 @@ describe('saveCacheAtomic', () => {
33
56
  },
34
57
  };
35
58
  await saveCacheAtomic(tmpDir, cache);
36
- const content = await fs.readFile(path.join(tmpDir, '.mutate-cache.json'), 'utf8');
59
+ const content = await fs.readFile(path.join(tmpDir, '.mutineer-cache.json'), 'utf8');
37
60
  expect(JSON.parse(content)).toEqual(cache);
38
61
  });
39
62
  it('overwrites existing cache', async () => {
@@ -48,9 +71,25 @@ describe('saveCacheAtomic', () => {
48
71
  },
49
72
  };
50
73
  await saveCacheAtomic(tmpDir, newCache);
51
- const content = await fs.readFile(path.join(tmpDir, '.mutate-cache.json'), 'utf8');
74
+ const content = await fs.readFile(path.join(tmpDir, '.mutineer-cache.json'), 'utf8');
52
75
  expect(JSON.parse(content)).toEqual(newCache);
53
76
  });
77
+ it('writes to shard-named file when shard is provided', async () => {
78
+ const cache = {
79
+ k: {
80
+ status: 'killed',
81
+ file: 'x.ts',
82
+ line: 1,
83
+ col: 0,
84
+ mutator: 'm',
85
+ },
86
+ };
87
+ await saveCacheAtomic(tmpDir, cache, { index: 2, total: 3 });
88
+ const content = await fs.readFile(path.join(tmpDir, '.mutineer-cache-shard-2-of-3.json'), 'utf8');
89
+ expect(JSON.parse(content)).toEqual(cache);
90
+ // default file should NOT exist
91
+ await expect(fs.access(path.join(tmpDir, '.mutineer-cache.json'))).rejects.toThrow();
92
+ });
54
93
  });
55
94
  describe('decodeCacheKey', () => {
56
95
  it('decodes a full cache key', () => {
@@ -128,6 +167,24 @@ describe('readMutantCache', () => {
128
167
  const result = await readMutantCache(tmpDir);
129
168
  expect(result).toEqual({});
130
169
  });
170
+ it('reads from shard-named file when shard is provided', async () => {
171
+ const cache = {
172
+ 'k:v:f.ts:1,0:m': {
173
+ status: 'killed',
174
+ file: 'f.ts',
175
+ line: 1,
176
+ col: 0,
177
+ mutator: 'm',
178
+ },
179
+ };
180
+ await fs.writeFile(path.join(tmpDir, '.mutineer-cache-shard-1-of-2.json'), JSON.stringify(cache));
181
+ const result = await readMutantCache(tmpDir, { index: 1, total: 2 });
182
+ expect(result['k:v:f.ts:1,0:m'].status).toBe('killed');
183
+ });
184
+ it('returns empty object when shard file does not exist', async () => {
185
+ const result = await readMutantCache(tmpDir, { index: 2, total: 4 });
186
+ expect(result).toEqual({});
187
+ });
131
188
  it('reads and normalizes object-format cache entries', async () => {
132
189
  const cache = {
133
190
  'testsig:codesig:file.ts:1,0:flip': {
@@ -138,7 +195,7 @@ describe('readMutantCache', () => {
138
195
  mutator: 'flip',
139
196
  },
140
197
  };
141
- await fs.writeFile(path.join(tmpDir, '.mutate-cache.json'), JSON.stringify(cache));
198
+ await fs.writeFile(path.join(tmpDir, '.mutineer-cache.json'), JSON.stringify(cache));
142
199
  const result = await readMutantCache(tmpDir);
143
200
  expect(result['testsig:codesig:file.ts:1,0:flip']).toEqual({
144
201
  status: 'killed',
@@ -152,14 +209,14 @@ describe('readMutantCache', () => {
152
209
  const cache = {
153
210
  'testsig:codesig:file.ts:1,0:flip': 'killed',
154
211
  };
155
- await fs.writeFile(path.join(tmpDir, '.mutate-cache.json'), JSON.stringify(cache));
212
+ await fs.writeFile(path.join(tmpDir, '.mutineer-cache.json'), JSON.stringify(cache));
156
213
  const result = await readMutantCache(tmpDir);
157
214
  const entry = result['testsig:codesig:file.ts:1,0:flip'];
158
215
  expect(entry.status).toBe('killed');
159
216
  expect(entry.mutator).toBe('flip');
160
217
  });
161
218
  it('returns empty object for invalid JSON', async () => {
162
- await fs.writeFile(path.join(tmpDir, '.mutate-cache.json'), 'not json');
219
+ await fs.writeFile(path.join(tmpDir, '.mutineer-cache.json'), 'not json');
163
220
  const result = await readMutantCache(tmpDir);
164
221
  expect(result).toEqual({});
165
222
  });
@@ -169,7 +226,7 @@ describe('readMutantCache', () => {
169
226
  status: 'escaped',
170
227
  },
171
228
  };
172
- await fs.writeFile(path.join(tmpDir, '.mutate-cache.json'), JSON.stringify(cache));
229
+ await fs.writeFile(path.join(tmpDir, '.mutineer-cache.json'), JSON.stringify(cache));
173
230
  const result = await readMutantCache(tmpDir);
174
231
  const entry = result['testsig:codesig:file.ts:5,3:mut'];
175
232
  expect(entry.status).toBe('escaped');
@@ -18,6 +18,13 @@ describe('cleanupMutineerDirs', () => {
18
18
  await cleanupMutineerDirs(tmpDir);
19
19
  await expect(fs.access(mutDir)).rejects.toThrow();
20
20
  });
21
+ it('removes root-level __mutineer__ directory', async () => {
22
+ const rootMutDir = path.join(tmpDir, '__mutineer__');
23
+ await fs.mkdir(rootMutDir, { recursive: true });
24
+ await fs.writeFile(path.join(rootMutDir, 'setup.mjs'), 'content');
25
+ await cleanupMutineerDirs(tmpDir);
26
+ await expect(fs.access(rootMutDir)).rejects.toThrow();
27
+ });
21
28
  it('removes nested __mutineer__ directories', async () => {
22
29
  const dir1 = path.join(tmpDir, 'src', 'a', '__mutineer__');
23
30
  const dir2 = path.join(tmpDir, 'src', 'b', '__mutineer__');
@@ -38,4 +45,34 @@ describe('cleanupMutineerDirs', () => {
38
45
  const stat = await fs.stat(srcDir);
39
46
  expect(stat.isDirectory()).toBe(true);
40
47
  });
48
+ it('does not remove cache files by default', async () => {
49
+ const cacheFile = path.join(tmpDir, '.mutineer-cache.json');
50
+ await fs.writeFile(cacheFile, '{}');
51
+ await cleanupMutineerDirs(tmpDir);
52
+ await expect(fs.access(cacheFile)).resolves.toBeUndefined();
53
+ });
54
+ it('removes .mutineer-cache*.json files when includeCacheFiles is true', async () => {
55
+ const cacheFile = path.join(tmpDir, '.mutineer-cache.json');
56
+ const shardFile = path.join(tmpDir, '.mutineer-cache-shard-1-of-2.json');
57
+ await fs.writeFile(cacheFile, '{}');
58
+ await fs.writeFile(shardFile, '{}');
59
+ await cleanupMutineerDirs(tmpDir, { includeCacheFiles: true });
60
+ await expect(fs.access(cacheFile)).rejects.toThrow();
61
+ await expect(fs.access(shardFile)).rejects.toThrow();
62
+ });
63
+ it('removes legacy .mutate-cache*.json files for migration when includeCacheFiles is true', async () => {
64
+ const legacyCache = path.join(tmpDir, '.mutate-cache.json');
65
+ const legacyShard = path.join(tmpDir, '.mutate-cache-shard-2-of-4.json');
66
+ await fs.writeFile(legacyCache, '{}');
67
+ await fs.writeFile(legacyShard, '{}');
68
+ await cleanupMutineerDirs(tmpDir, { includeCacheFiles: true });
69
+ await expect(fs.access(legacyCache)).rejects.toThrow();
70
+ await expect(fs.access(legacyShard)).rejects.toThrow();
71
+ });
72
+ it('removes .mutineer-cache*.json.tmp temp files when includeCacheFiles is true', async () => {
73
+ const tmpFile = path.join(tmpDir, '.mutineer-cache.json.tmp');
74
+ await fs.writeFile(tmpFile, '{}');
75
+ await cleanupMutineerDirs(tmpDir, { includeCacheFiles: true });
76
+ await expect(fs.access(tmpFile)).rejects.toThrow();
77
+ });
41
78
  });
@@ -16,6 +16,11 @@ function makeOpts(overrides = {}) {
16
16
  minKillPercent: undefined,
17
17
  runner: 'vitest',
18
18
  timeout: undefined,
19
+ reportFormat: 'text',
20
+ shard: undefined,
21
+ typescriptCheck: undefined,
22
+ vitestProject: undefined,
23
+ skipBaseline: false,
19
24
  ...overrides,
20
25
  };
21
26
  }
@@ -114,6 +114,134 @@ describe('autoDiscoverTargetsAndTests', () => {
114
114
  await fs.rm(tmpDir, { recursive: true, force: true });
115
115
  }
116
116
  });
117
+ it('calls onProgress at least twice with informational messages', async () => {
118
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-discover-progress-'));
119
+ const srcDir = path.join(tmpDir, 'src');
120
+ const moduleFile = path.join(srcDir, 'foo.ts');
121
+ const testFile = path.join(srcDir, 'foo.test.ts');
122
+ await fs.mkdir(srcDir, { recursive: true });
123
+ await fs.writeFile(moduleFile, 'export const foo = 1\n', 'utf8');
124
+ const importLine = ['im', 'port { foo } from "./foo"'].join('');
125
+ await fs.writeFile(testFile, `${importLine}\nconsole.log(foo)\n`, 'utf8');
126
+ const messages = [];
127
+ try {
128
+ await autoDiscoverTargetsAndTests(tmpDir, { testPatterns: ['**/*.test.ts'] }, (msg) => messages.push(msg));
129
+ expect(messages.length).toBeGreaterThanOrEqual(2);
130
+ expect(messages.some((m) => m.includes('test file'))).toBe(true);
131
+ expect(messages.some((m) => m.includes('Discovery complete'))).toBe(true);
132
+ }
133
+ finally {
134
+ await fs.rm(tmpDir, { recursive: true, force: true });
135
+ }
136
+ });
137
+ it('shared dep imported by 2 tests appears in testMap for both', async () => {
138
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-discover-shared-'));
139
+ const srcDir = path.join(tmpDir, 'src');
140
+ const sharedDep = path.join(srcDir, 'shared.ts');
141
+ const test1 = path.join(srcDir, 'a.test.ts');
142
+ const test2 = path.join(srcDir, 'b.test.ts');
143
+ await fs.mkdir(srcDir, { recursive: true });
144
+ await fs.writeFile(sharedDep, 'export const shared = 1\n', 'utf8');
145
+ const importShared = ['im', 'port { shared } from "./shared"'].join('');
146
+ await fs.writeFile(test1, `${importShared}\n`, 'utf8');
147
+ await fs.writeFile(test2, `${importShared}\n`, 'utf8');
148
+ try {
149
+ const { testMap } = await autoDiscoverTargetsAndTests(tmpDir, {
150
+ testPatterns: ['**/*.test.ts'],
151
+ });
152
+ const sharedAbs = normalizePath(sharedDep);
153
+ const test1Abs = normalizePath(test1);
154
+ const test2Abs = normalizePath(test2);
155
+ expect(testMap.get(sharedAbs)?.has(test1Abs)).toBe(true);
156
+ expect(testMap.get(sharedAbs)?.has(test2Abs)).toBe(true);
157
+ }
158
+ finally {
159
+ await fs.rm(tmpDir, { recursive: true, force: true });
160
+ }
161
+ });
162
+ it('diamond graph: shared grandchild discovered with no duplicates', async () => {
163
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-discover-diamond-'));
164
+ const srcDir = path.join(tmpDir, 'src');
165
+ const fileA = path.join(srcDir, 'A.ts');
166
+ const fileB = path.join(srcDir, 'B.ts');
167
+ const fileC = path.join(srcDir, 'C.ts');
168
+ const fileD = path.join(srcDir, 'D.ts');
169
+ const testFile = path.join(srcDir, 'test.test.ts');
170
+ await fs.mkdir(srcDir, { recursive: true });
171
+ await fs.writeFile(fileD, 'export const d = 4\n', 'utf8');
172
+ const importD = ['im', 'port { d } from "./D"'].join('');
173
+ await fs.writeFile(fileB, `${importD}\nexport const b = 2\n`, 'utf8');
174
+ await fs.writeFile(fileC, `${importD}\nexport const c = 3\n`, 'utf8');
175
+ const importB = ['im', 'port { b } from "./B"'].join('');
176
+ const importC = ['im', 'port { c } from "./C"'].join('');
177
+ await fs.writeFile(fileA, `${importB}\n${importC}\nexport const a = 1\n`, 'utf8');
178
+ const importA = ['im', 'port { a } from "./A"'].join('');
179
+ await fs.writeFile(testFile, `${importA}\n`, 'utf8');
180
+ try {
181
+ const { testMap } = await autoDiscoverTargetsAndTests(tmpDir, {
182
+ testPatterns: ['**/*.test.ts'],
183
+ });
184
+ const dAbs = normalizePath(fileD);
185
+ const testAbs = normalizePath(testFile);
186
+ expect(testMap.get(dAbs)?.has(testAbs)).toBe(true);
187
+ // D should only be in testMap once (Set ensures no duplicates)
188
+ expect(testMap.get(dAbs)?.size).toBe(1);
189
+ }
190
+ finally {
191
+ await fs.rm(tmpDir, { recursive: true, force: true });
192
+ }
193
+ });
194
+ it('deep chain: deepest file is discovered correctly', async () => {
195
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-discover-deep-'));
196
+ const srcDir = path.join(tmpDir, 'src');
197
+ await fs.mkdir(srcDir, { recursive: true });
198
+ // chain: test -> f1 -> f2 -> f3 -> f4 -> f5
199
+ const files = Array.from({ length: 5 }, (_, i) => path.join(srcDir, `f${i + 1}.ts`));
200
+ const testFile = path.join(srcDir, 'chain.test.ts');
201
+ await fs.writeFile(files[4], 'export const f5 = 5\n', 'utf8');
202
+ for (let i = 3; i >= 0; i--) {
203
+ const importNext = ['im', `port { f${i + 2} } from "./f${i + 2}"`].join('');
204
+ await fs.writeFile(files[i], `${importNext}\nexport const f${i + 1} = ${i + 1}\n`, 'utf8');
205
+ }
206
+ const importF1 = ['im', 'port { f1 } from "./f1"'].join('');
207
+ await fs.writeFile(testFile, `${importF1}\n`, 'utf8');
208
+ try {
209
+ const { testMap } = await autoDiscoverTargetsAndTests(tmpDir, {
210
+ testPatterns: ['**/*.test.ts'],
211
+ });
212
+ const f5Abs = normalizePath(files[4]);
213
+ const testAbs = normalizePath(testFile);
214
+ expect(testMap.get(f5Abs)?.has(testAbs)).toBe(true);
215
+ }
216
+ finally {
217
+ await fs.rm(tmpDir, { recursive: true, force: true });
218
+ }
219
+ });
220
+ it('2 tests directly importing same file both appear in directTestMap', async () => {
221
+ const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-discover-direct2-'));
222
+ const srcDir = path.join(tmpDir, 'src');
223
+ const sharedDep = path.join(srcDir, 'shared.ts');
224
+ const test1 = path.join(srcDir, 'a.test.ts');
225
+ const test2 = path.join(srcDir, 'b.test.ts');
226
+ await fs.mkdir(srcDir, { recursive: true });
227
+ await fs.writeFile(sharedDep, 'export const shared = 1\n', 'utf8');
228
+ const importShared = ['im', 'port { shared } from "./shared"'].join('');
229
+ await fs.writeFile(test1, `${importShared}\n`, 'utf8');
230
+ await fs.writeFile(test2, `${importShared}\n`, 'utf8');
231
+ try {
232
+ const { directTestMap } = await autoDiscoverTargetsAndTests(tmpDir, {
233
+ testPatterns: ['**/*.test.ts'],
234
+ });
235
+ const sharedAbs = normalizePath(sharedDep);
236
+ const test1Abs = normalizePath(test1);
237
+ const test2Abs = normalizePath(test2);
238
+ expect(directTestMap.get(sharedAbs)?.has(test1Abs)).toBe(true);
239
+ expect(directTestMap.get(sharedAbs)?.has(test2Abs)).toBe(true);
240
+ }
241
+ finally {
242
+ await fs.rm(tmpDir, { recursive: true, force: true });
243
+ }
244
+ });
117
245
  it('ignores test files when collecting mutate targets', async () => {
118
246
  const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-discover-'));
119
247
  const srcDir = path.join(tmpDir, 'src');