@mutineerjs/mutineer 0.1.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/LICENSE +21 -0
- package/README.md +218 -0
- package/dist/admin/assets/index-B7nXq-e7.js +32 -0
- package/dist/admin/assets/index-B7nXq-e7.js.map +1 -0
- package/dist/admin/assets/index-BDQLkBUE.js +32 -0
- package/dist/admin/assets/index-BDQLkBUE.js.map +1 -0
- package/dist/admin/assets/index-DVkP-Tc7.css +1 -0
- package/dist/admin/index.html +13 -0
- package/dist/admin/server/admin.d.ts +6 -0
- package/dist/admin/server/admin.js +234 -0
- package/dist/bin/mutate-vitest.d.ts +2 -0
- package/dist/bin/mutate-vitest.js +90 -0
- package/dist/bin/mutineer.d.ts +2 -0
- package/dist/bin/mutineer.js +46 -0
- package/dist/core/__tests__/module.spec.d.ts +1 -0
- package/dist/core/__tests__/module.spec.js +6 -0
- package/dist/core/module.d.ts +11 -0
- package/dist/core/module.js +14 -0
- package/dist/core/sfc.d.ts +12 -0
- package/dist/core/sfc.js +54 -0
- package/dist/core/types.d.ts +6 -0
- package/dist/core/types.js +1 -0
- package/dist/core/variant-utils.d.ts +30 -0
- package/dist/core/variant-utils.js +54 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/mutators/__tests__/registry.spec.d.ts +1 -0
- package/dist/mutators/__tests__/registry.spec.js +43 -0
- package/dist/mutators/__tests__/utils.spec.d.ts +1 -0
- package/dist/mutators/__tests__/utils.spec.js +15 -0
- package/dist/mutators/registry.d.ts +37 -0
- package/dist/mutators/registry.js +101 -0
- package/dist/mutators/types.d.ts +39 -0
- package/dist/mutators/types.js +7 -0
- package/dist/mutators/utils.d.ts +37 -0
- package/dist/mutators/utils.js +151 -0
- package/dist/plugin/viteMutate.d.ts +15 -0
- package/dist/plugin/viteMutate.js +52 -0
- package/dist/plugin/vitest.setup.d.ts +47 -0
- package/dist/plugin/vitest.setup.js +118 -0
- package/dist/plugin/withVitest.d.ts +13 -0
- package/dist/plugin/withVitest.js +30 -0
- package/dist/runner/__tests__/discover.spec.d.ts +1 -0
- package/dist/runner/__tests__/discover.spec.js +59 -0
- package/dist/runner/__tests__/orchestrator.spec.d.ts +1 -0
- package/dist/runner/__tests__/orchestrator.spec.js +55 -0
- package/dist/runner/adapters/__tests__/jest.spec.d.ts +1 -0
- package/dist/runner/adapters/__tests__/jest.spec.js +88 -0
- package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.d.ts +1 -0
- package/dist/runner/adapters/__tests__/vitest-worker-runtime.spec.js +59 -0
- package/dist/runner/adapters/__tests__/vitest.spec.d.ts +1 -0
- package/dist/runner/adapters/__tests__/vitest.spec.js +118 -0
- package/dist/runner/adapters/index.d.ts +10 -0
- package/dist/runner/adapters/index.js +9 -0
- package/dist/runner/adapters/jest/__tests__/index.spec.d.ts +1 -0
- package/dist/runner/adapters/jest/__tests__/index.spec.js +88 -0
- package/dist/runner/adapters/jest/index.d.ts +24 -0
- package/dist/runner/adapters/jest/index.js +216 -0
- package/dist/runner/adapters/jest/worker-runtime.d.ts +37 -0
- package/dist/runner/adapters/jest/worker-runtime.js +171 -0
- package/dist/runner/adapters/jest-worker-runtime.d.ts +37 -0
- package/dist/runner/adapters/jest-worker-runtime.js +171 -0
- package/dist/runner/adapters/jest.d.ts +24 -0
- package/dist/runner/adapters/jest.js +216 -0
- package/dist/runner/adapters/types.d.ts +89 -0
- package/dist/runner/adapters/types.js +8 -0
- package/dist/runner/adapters/vitest/__tests__/index.spec.d.ts +1 -0
- package/dist/runner/adapters/vitest/__tests__/index.spec.js +118 -0
- package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.d.ts +1 -0
- package/dist/runner/adapters/vitest/__tests__/worker-runtime.spec.js +59 -0
- package/dist/runner/adapters/vitest/index.d.ts +33 -0
- package/dist/runner/adapters/vitest/index.js +267 -0
- package/dist/runner/adapters/vitest/worker-runtime.d.ts +25 -0
- package/dist/runner/adapters/vitest/worker-runtime.js +118 -0
- package/dist/runner/adapters/vitest-worker-runtime.d.ts +25 -0
- package/dist/runner/adapters/vitest-worker-runtime.js +118 -0
- package/dist/runner/adapters/vitest.d.ts +33 -0
- package/dist/runner/adapters/vitest.js +267 -0
- package/dist/runner/args.d.ts +50 -0
- package/dist/runner/args.js +123 -0
- package/dist/runner/cache.d.ts +38 -0
- package/dist/runner/cache.js +118 -0
- package/dist/runner/changed.d.ts +22 -0
- package/dist/runner/changed.js +210 -0
- package/dist/runner/cleanup.d.ts +4 -0
- package/dist/runner/cleanup.js +21 -0
- package/dist/runner/config.d.ts +13 -0
- package/dist/runner/config.js +94 -0
- package/dist/runner/discover.d.ts +7 -0
- package/dist/runner/discover.js +258 -0
- package/dist/runner/jest/__tests__/adapter.spec.d.ts +1 -0
- package/dist/runner/jest/__tests__/adapter.spec.js +110 -0
- package/dist/runner/jest/adapter.d.ts +24 -0
- package/dist/runner/jest/adapter.js +191 -0
- package/dist/runner/jest/index.d.ts +8 -0
- package/dist/runner/jest/index.js +7 -0
- package/dist/runner/jest/pool.d.ts +47 -0
- package/dist/runner/jest/pool.js +307 -0
- package/dist/runner/jest/resolver.cjs +61 -0
- package/dist/runner/jest/resolver.d.cts +11 -0
- package/dist/runner/jest/worker-runtime.d.ts +30 -0
- package/dist/runner/jest/worker-runtime.js +98 -0
- package/dist/runner/jest/worker.d.mts +1 -0
- package/dist/runner/jest/worker.mjs +55 -0
- package/dist/runner/orchestrator.d.ts +13 -0
- package/dist/runner/orchestrator.js +387 -0
- package/dist/runner/pool/__tests__/index.spec.d.ts +1 -0
- package/dist/runner/pool/__tests__/index.spec.js +83 -0
- package/dist/runner/pool/__tests__/pool-plugin.spec.d.ts +1 -0
- package/dist/runner/pool/__tests__/pool-plugin.spec.js +59 -0
- package/dist/runner/pool/__tests__/pool-redirect-loader.spec.d.ts +1 -0
- package/dist/runner/pool/__tests__/pool-redirect-loader.spec.js +78 -0
- package/dist/runner/pool/index.d.ts +8 -0
- package/dist/runner/pool/index.js +9 -0
- package/dist/runner/pool/jest/pool.d.ts +52 -0
- package/dist/runner/pool/jest/pool.js +309 -0
- package/dist/runner/pool/jest/worker.d.mts +1 -0
- package/dist/runner/pool/jest/worker.mjs +60 -0
- package/dist/runner/pool/jest-pool.d.ts +52 -0
- package/dist/runner/pool/jest-pool.js +309 -0
- package/dist/runner/pool/jest-worker.d.mts +1 -0
- package/dist/runner/pool/jest-worker.mjs +60 -0
- package/dist/runner/pool/plugin.d.ts +18 -0
- package/dist/runner/pool/plugin.js +60 -0
- package/dist/runner/pool/pool-plugin.d.ts +18 -0
- package/dist/runner/pool/pool-plugin.js +60 -0
- package/dist/runner/pool/pool-redirect-loader.d.ts +19 -0
- package/dist/runner/pool/pool-redirect-loader.js +116 -0
- package/dist/runner/pool/pool-redirect-loader.mjs +146 -0
- package/dist/runner/pool/redirect-loader.d.ts +19 -0
- package/dist/runner/pool/redirect-loader.js +116 -0
- package/dist/runner/pool/vitest/pool.d.ts +70 -0
- package/dist/runner/pool/vitest/pool.js +376 -0
- package/dist/runner/pool/vitest/worker.d.mts +15 -0
- package/dist/runner/pool/vitest/worker.mjs +96 -0
- package/dist/runner/pool/vitest-worker.d.mts +15 -0
- package/dist/runner/pool/vitest-worker.mjs +96 -0
- package/dist/runner/shared/index.d.ts +9 -0
- package/dist/runner/shared/index.js +8 -0
- package/dist/runner/shared/mutant-paths.d.ts +15 -0
- package/dist/runner/shared/mutant-paths.js +30 -0
- package/dist/runner/shared/redirect-state.d.ts +45 -0
- package/dist/runner/shared/redirect-state.js +50 -0
- package/dist/runner/shared-module-redirect.d.ts +56 -0
- package/dist/runner/shared-module-redirect.js +84 -0
- package/dist/runner/types.d.ts +88 -0
- package/dist/runner/types.js +8 -0
- package/dist/runner/variants.d.ts +21 -0
- package/dist/runner/variants.js +66 -0
- package/dist/runner/vitest/__tests__/adapter.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/adapter.spec.js +131 -0
- package/dist/runner/vitest/__tests__/plugin.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/plugin.spec.js +65 -0
- package/dist/runner/vitest/__tests__/pool.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/pool.spec.js +106 -0
- package/dist/runner/vitest/__tests__/redirect-loader.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/redirect-loader.spec.js +87 -0
- package/dist/runner/vitest/__tests__/worker-runtime.spec.d.ts +1 -0
- package/dist/runner/vitest/__tests__/worker-runtime.spec.js +75 -0
- package/dist/runner/vitest/adapter.d.ts +33 -0
- package/dist/runner/vitest/adapter.js +277 -0
- package/dist/runner/vitest/index.d.ts +11 -0
- package/dist/runner/vitest/index.js +10 -0
- package/dist/runner/vitest/plugin.d.ts +12 -0
- package/dist/runner/vitest/plugin.js +49 -0
- package/dist/runner/vitest/pool.d.ts +65 -0
- package/dist/runner/vitest/pool.js +376 -0
- package/dist/runner/vitest/redirect-loader.d.ts +30 -0
- package/dist/runner/vitest/redirect-loader.js +123 -0
- package/dist/runner/vitest/worker-runtime.d.ts +16 -0
- package/dist/runner/vitest/worker-runtime.js +105 -0
- package/dist/runner/vitest/worker.d.mts +15 -0
- package/dist/runner/vitest/worker.mjs +92 -0
- package/dist/types/api.d.ts +20 -0
- package/dist/types/api.js +1 -0
- package/dist/types/config.d.ts +48 -0
- package/dist/types/config.js +1 -0
- package/dist/types/index.d.ts +13 -0
- package/dist/types/index.js +11 -0
- package/dist/types/mutant.d.ts +44 -0
- package/dist/types/mutant.js +7 -0
- package/dist/utils/PoolSpinner.d.ts +5 -0
- package/dist/utils/PoolSpinner.js +6 -0
- package/dist/utils/ProgressBar.d.ts +11 -0
- package/dist/utils/ProgressBar.js +9 -0
- package/dist/utils/__tests__/coverage.spec.d.ts +1 -0
- package/dist/utils/__tests__/coverage.spec.js +91 -0
- package/dist/utils/__tests__/progress.spec.d.ts +1 -0
- package/dist/utils/__tests__/progress.spec.js +50 -0
- package/dist/utils/__tests__/summary.spec.d.ts +1 -0
- package/dist/utils/__tests__/summary.spec.js +54 -0
- package/dist/utils/coverage.d.ts +57 -0
- package/dist/utils/coverage.js +204 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/logger.js +18 -0
- package/dist/utils/progress.d.ts +25 -0
- package/dist/utils/progress.js +90 -0
- package/dist/utils/summary.d.ts +12 -0
- package/dist/utils/summary.js +107 -0
- package/package.json +59 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared redirect state management for mutation testing.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a centralized way to manage the redirect configuration
|
|
5
|
+
* that tells test runners to load mutant code instead of the original source.
|
|
6
|
+
*
|
|
7
|
+
* Both Jest and Vitest adapters use this to coordinate file redirection during
|
|
8
|
+
* test execution.
|
|
9
|
+
*/
|
|
10
|
+
declare global {
|
|
11
|
+
var __mutineer_redirect__: {
|
|
12
|
+
from: string | null;
|
|
13
|
+
to: string | null;
|
|
14
|
+
} | undefined;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Configuration for redirecting file imports/requires.
|
|
18
|
+
*/
|
|
19
|
+
export interface RedirectConfig {
|
|
20
|
+
/** Absolute path to the original source file */
|
|
21
|
+
readonly from: string;
|
|
22
|
+
/** Absolute path to the mutant file */
|
|
23
|
+
readonly to: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the global redirect state.
|
|
27
|
+
* Must be called once at module load time.
|
|
28
|
+
*/
|
|
29
|
+
export declare function initializeRedirectState(): void;
|
|
30
|
+
/**
|
|
31
|
+
* Set the active redirect configuration.
|
|
32
|
+
*
|
|
33
|
+
* @param config - The redirect configuration
|
|
34
|
+
*/
|
|
35
|
+
export declare function setRedirect(config: RedirectConfig): void;
|
|
36
|
+
/**
|
|
37
|
+
* Get the current redirect configuration.
|
|
38
|
+
*
|
|
39
|
+
* @returns The redirect config if one is active, null otherwise
|
|
40
|
+
*/
|
|
41
|
+
export declare function getRedirect(): RedirectConfig | null;
|
|
42
|
+
/**
|
|
43
|
+
* Clear the redirect configuration.
|
|
44
|
+
*/
|
|
45
|
+
export declare function clearRedirect(): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared redirect state management for mutation testing.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a centralized way to manage the redirect configuration
|
|
5
|
+
* that tells test runners to load mutant code instead of the original source.
|
|
6
|
+
*
|
|
7
|
+
* Both Jest and Vitest adapters use this to coordinate file redirection during
|
|
8
|
+
* test execution.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Initialize the global redirect state.
|
|
12
|
+
* Must be called once at module load time.
|
|
13
|
+
*/
|
|
14
|
+
export function initializeRedirectState() {
|
|
15
|
+
globalThis.__mutineer_redirect__ = { from: null, to: null };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Set the active redirect configuration.
|
|
19
|
+
*
|
|
20
|
+
* @param config - The redirect configuration
|
|
21
|
+
*/
|
|
22
|
+
export function setRedirect(config) {
|
|
23
|
+
globalThis.__mutineer_redirect__ = {
|
|
24
|
+
from: config.from,
|
|
25
|
+
to: config.to,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the current redirect configuration.
|
|
30
|
+
*
|
|
31
|
+
* @returns The redirect config if one is active, null otherwise
|
|
32
|
+
*/
|
|
33
|
+
export function getRedirect() {
|
|
34
|
+
const redirect = globalThis.__mutineer_redirect__;
|
|
35
|
+
if (!redirect?.from || !redirect?.to) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
from: redirect.from,
|
|
40
|
+
to: redirect.to,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Clear the redirect configuration.
|
|
45
|
+
*/
|
|
46
|
+
export function clearRedirect() {
|
|
47
|
+
globalThis.__mutineer_redirect__ = { from: null, to: null };
|
|
48
|
+
}
|
|
49
|
+
// Initialize on module load
|
|
50
|
+
initializeRedirectState();
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared module redirection utilities for both Jest and Vitest runners.
|
|
3
|
+
*
|
|
4
|
+
* This module provides common functionality for intercepting module resolution
|
|
5
|
+
* and redirecting imports to mutated versions of source files.
|
|
6
|
+
*/
|
|
7
|
+
export interface MutantRedirect {
|
|
8
|
+
from: string | null;
|
|
9
|
+
to: string | null;
|
|
10
|
+
}
|
|
11
|
+
declare global {
|
|
12
|
+
var __mutineer_redirect__: MutantRedirect | undefined;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get the path where a mutant should be written.
|
|
16
|
+
* Mutants are stored in __mutineer__/ subdirectories alongside source files.
|
|
17
|
+
*
|
|
18
|
+
* @param originalFile - The original source file path
|
|
19
|
+
* @param mutantId - The mutation ID
|
|
20
|
+
* @returns Path to the mutant file in __mutineer__/ directory
|
|
21
|
+
*/
|
|
22
|
+
export declare function getMutantFilePath(originalFile: string, mutantId: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* Get the current redirect configuration from globalThis.
|
|
25
|
+
* Used by both Jest and Vitest to check if a module should be redirected.
|
|
26
|
+
*
|
|
27
|
+
* @returns Object with normalized from/to paths, or null if no redirect is active
|
|
28
|
+
*/
|
|
29
|
+
export declare function getActiveRedirect(): {
|
|
30
|
+
from: string;
|
|
31
|
+
to: string;
|
|
32
|
+
} | null;
|
|
33
|
+
/**
|
|
34
|
+
* Initialize the global redirect state.
|
|
35
|
+
* Should be called once when the runtime starts.
|
|
36
|
+
*/
|
|
37
|
+
export declare function initializeRedirect(): void;
|
|
38
|
+
/**
|
|
39
|
+
* Set a redirect for module resolution.
|
|
40
|
+
*
|
|
41
|
+
* @param originalFile - The original source file (absolute path)
|
|
42
|
+
* @param mutantPath - The mutant file path
|
|
43
|
+
*/
|
|
44
|
+
export declare function setRedirect(originalFile: string, mutantPath: string): void;
|
|
45
|
+
/**
|
|
46
|
+
* Clear the current redirect.
|
|
47
|
+
*/
|
|
48
|
+
export declare function clearRedirect(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a given file path matches the current redirect source.
|
|
51
|
+
* Handles path normalization and extension matching.
|
|
52
|
+
*
|
|
53
|
+
* @param filePath - The file path to check
|
|
54
|
+
* @returns true if this file should be redirected
|
|
55
|
+
*/
|
|
56
|
+
export declare function shouldRedirectPath(filePath: string): boolean;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared module redirection utilities for both Jest and Vitest runners.
|
|
3
|
+
*
|
|
4
|
+
* This module provides common functionality for intercepting module resolution
|
|
5
|
+
* and redirecting imports to mutated versions of source files.
|
|
6
|
+
*/
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
/**
|
|
10
|
+
* Get the path where a mutant should be written.
|
|
11
|
+
* Mutants are stored in __mutineer__/ subdirectories alongside source files.
|
|
12
|
+
*
|
|
13
|
+
* @param originalFile - The original source file path
|
|
14
|
+
* @param mutantId - The mutation ID
|
|
15
|
+
* @returns Path to the mutant file in __mutineer__/ directory
|
|
16
|
+
*/
|
|
17
|
+
export function getMutantFilePath(originalFile, mutantId) {
|
|
18
|
+
const dir = path.dirname(originalFile);
|
|
19
|
+
const ext = path.extname(originalFile);
|
|
20
|
+
const basename = path.basename(originalFile, ext);
|
|
21
|
+
const mutineerDir = path.join(dir, '__mutineer__');
|
|
22
|
+
if (!fs.existsSync(mutineerDir)) {
|
|
23
|
+
fs.mkdirSync(mutineerDir, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
const idMatch = mutantId.match(/#(\d+)$/);
|
|
26
|
+
const suffix = idMatch ? idMatch[1] : mutantId.replace(/[^a-zA-Z0-9]/g, '_').slice(0, 20);
|
|
27
|
+
return path.join(mutineerDir, `${basename}_${suffix}${ext}`);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get the current redirect configuration from globalThis.
|
|
31
|
+
* Used by both Jest and Vitest to check if a module should be redirected.
|
|
32
|
+
*
|
|
33
|
+
* @returns Object with normalized from/to paths, or null if no redirect is active
|
|
34
|
+
*/
|
|
35
|
+
export function getActiveRedirect() {
|
|
36
|
+
const redirect = globalThis.__mutineer_redirect__;
|
|
37
|
+
if (!redirect?.from || !redirect?.to) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
from: path.resolve(redirect.from),
|
|
42
|
+
to: redirect.to,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Initialize the global redirect state.
|
|
47
|
+
* Should be called once when the runtime starts.
|
|
48
|
+
*/
|
|
49
|
+
export function initializeRedirect() {
|
|
50
|
+
globalThis.__mutineer_redirect__ = { from: null, to: null };
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Set a redirect for module resolution.
|
|
54
|
+
*
|
|
55
|
+
* @param originalFile - The original source file (absolute path)
|
|
56
|
+
* @param mutantPath - The mutant file path
|
|
57
|
+
*/
|
|
58
|
+
export function setRedirect(originalFile, mutantPath) {
|
|
59
|
+
globalThis.__mutineer_redirect__ = {
|
|
60
|
+
from: path.resolve(originalFile),
|
|
61
|
+
to: mutantPath,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Clear the current redirect.
|
|
66
|
+
*/
|
|
67
|
+
export function clearRedirect() {
|
|
68
|
+
globalThis.__mutineer_redirect__ = { from: null, to: null };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if a given file path matches the current redirect source.
|
|
72
|
+
* Handles path normalization and extension matching.
|
|
73
|
+
*
|
|
74
|
+
* @param filePath - The file path to check
|
|
75
|
+
* @returns true if this file should be redirected
|
|
76
|
+
*/
|
|
77
|
+
export function shouldRedirectPath(filePath) {
|
|
78
|
+
const redirect = getActiveRedirect();
|
|
79
|
+
if (!redirect) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const normalized = path.resolve(filePath);
|
|
83
|
+
return normalized === redirect.from;
|
|
84
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Runner Adapter Interface
|
|
3
|
+
*
|
|
4
|
+
* This module defines the interface that all test runner adapters must implement.
|
|
5
|
+
* Adapters abstract test runner-specific details (Vitest, Jest, etc.) from the
|
|
6
|
+
* mutation testing orchestrator.
|
|
7
|
+
*/
|
|
8
|
+
import type { MutineerConfig } from '../types/config.js';
|
|
9
|
+
import type { MutantPayload, MutantRunResult } from '../types/mutant.js';
|
|
10
|
+
/**
|
|
11
|
+
* Options for initializing a test runner adapter.
|
|
12
|
+
*/
|
|
13
|
+
export interface TestRunnerAdapterOptions {
|
|
14
|
+
readonly cwd: string;
|
|
15
|
+
readonly concurrency: number;
|
|
16
|
+
readonly timeoutMs: number;
|
|
17
|
+
readonly config: MutineerConfig;
|
|
18
|
+
readonly cliArgs: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Coverage-related configuration detected from the test runner.
|
|
22
|
+
*/
|
|
23
|
+
export interface CoverageConfig {
|
|
24
|
+
/** Whether per-test coverage is enabled in the config */
|
|
25
|
+
readonly perTestEnabled: boolean;
|
|
26
|
+
/** Whether coverage is enabled in the config */
|
|
27
|
+
readonly coverageEnabled: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Interface that all test runner adapters must implement.
|
|
31
|
+
*
|
|
32
|
+
* A test runner adapter handles:
|
|
33
|
+
* - Running baseline tests before mutation testing
|
|
34
|
+
* - Running mutant tests (with code substitution)
|
|
35
|
+
* - Managing worker pools for parallel execution
|
|
36
|
+
* - Detecting coverage configuration
|
|
37
|
+
*/
|
|
38
|
+
export interface TestRunnerAdapter {
|
|
39
|
+
/**
|
|
40
|
+
* The name of the test runner (e.g., 'vitest', 'jest').
|
|
41
|
+
*/
|
|
42
|
+
readonly name: string;
|
|
43
|
+
/**
|
|
44
|
+
* Initialize the adapter (start worker pools, etc.).
|
|
45
|
+
* Must be called before running tests.
|
|
46
|
+
*/
|
|
47
|
+
init(concurrencyOverride?: number): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Run baseline tests to ensure they pass before mutation testing.
|
|
50
|
+
* @param tests - Array of test file paths to run
|
|
51
|
+
* @param options - Options for the baseline run
|
|
52
|
+
* @returns true if all tests pass, false otherwise
|
|
53
|
+
*/
|
|
54
|
+
runBaseline(tests: readonly string[], options: BaselineOptions): Promise<boolean>;
|
|
55
|
+
/**
|
|
56
|
+
* Run a single mutant against its associated tests.
|
|
57
|
+
* @param mutant - The mutation to test
|
|
58
|
+
* @param tests - Array of test file paths to run
|
|
59
|
+
* @returns Result indicating if the mutant was killed, escaped, or errored
|
|
60
|
+
*/
|
|
61
|
+
runMutant(mutant: MutantPayload, tests: readonly string[]): Promise<MutantRunResult>;
|
|
62
|
+
/**
|
|
63
|
+
* Shutdown the adapter (stop worker pools, cleanup, etc.).
|
|
64
|
+
*/
|
|
65
|
+
shutdown(): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* Check if the coverage provider is installed.
|
|
68
|
+
*/
|
|
69
|
+
hasCoverageProvider(): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Detect coverage configuration from the test runner config.
|
|
72
|
+
*/
|
|
73
|
+
detectCoverageConfig(): Promise<CoverageConfig>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Options for running baseline tests.
|
|
77
|
+
*/
|
|
78
|
+
export interface BaselineOptions {
|
|
79
|
+
/** Whether to collect coverage during baseline run */
|
|
80
|
+
readonly collectCoverage: boolean;
|
|
81
|
+
/** Whether to collect per-test coverage */
|
|
82
|
+
readonly perTestCoverage: boolean;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Factory function type for creating test runner adapters.
|
|
86
|
+
*/
|
|
87
|
+
export type TestRunnerAdapterFactory = (options: TestRunnerAdapterOptions) => TestRunnerAdapter;
|
|
88
|
+
export type { MutantPayload, MutantRunResult, MutantRunStatus, } from '../types/mutant.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variant Enumeration Module
|
|
3
|
+
*
|
|
4
|
+
* Functions for enumerating mutation variants from source files.
|
|
5
|
+
* Handles both regular modules and Vue SFC files.
|
|
6
|
+
*/
|
|
7
|
+
import type { MutateTarget } from '../types/config.js';
|
|
8
|
+
import type { MutantPayload } from '../types/mutant.js';
|
|
9
|
+
/**
|
|
10
|
+
* Get file path from target (handles both string and object forms).
|
|
11
|
+
*/
|
|
12
|
+
export declare function getTargetFile(t: MutateTarget): string;
|
|
13
|
+
/**
|
|
14
|
+
* Enumerate all mutation variants for a single target file.
|
|
15
|
+
*/
|
|
16
|
+
export declare function enumerateVariantsForTarget(root: string, t: MutateTarget, include?: readonly string[], exclude?: readonly string[], max?: number): Promise<MutantPayload[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Filter tests to only those that cover a specific line in a file.
|
|
19
|
+
*/
|
|
20
|
+
export declare function filterTestsByCoverage(perTest: Map<string, Map<string, Set<number>>>, tests: readonly string[], filePath: string, line: number): string[];
|
|
21
|
+
export type { Variant } from '../types/mutant.js';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Variant Enumeration Module
|
|
3
|
+
*
|
|
4
|
+
* Functions for enumerating mutation variants from source files.
|
|
5
|
+
* Handles both regular modules and Vue SFC files.
|
|
6
|
+
*/
|
|
7
|
+
import fs from 'node:fs/promises';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { mutateVueSfcScriptSetup } from '../core/sfc.js';
|
|
10
|
+
import { mutateModuleSource } from '../core/module.js';
|
|
11
|
+
import { createLogger } from '../utils/logger.js';
|
|
12
|
+
const log = createLogger('variants');
|
|
13
|
+
/**
|
|
14
|
+
* Get file path from target (handles both string and object forms).
|
|
15
|
+
*/
|
|
16
|
+
export function getTargetFile(t) {
|
|
17
|
+
return typeof t === 'string' ? t : t.file;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Enumerate all mutation variants for a single target file.
|
|
21
|
+
*/
|
|
22
|
+
export async function enumerateVariantsForTarget(root, t, include, exclude, max) {
|
|
23
|
+
// Normalize target: string → { file: string }, object → as-is
|
|
24
|
+
const file = typeof t === 'string' ? t : t.file;
|
|
25
|
+
const explicitKind = typeof t === 'string' ? undefined : t.kind;
|
|
26
|
+
const abs = path.isAbsolute(file) ? file : path.join(root, file);
|
|
27
|
+
const includeArr = include ? [...include] : undefined;
|
|
28
|
+
const excludeArr = exclude ? [...exclude] : undefined;
|
|
29
|
+
try {
|
|
30
|
+
const code = await fs.readFile(abs, 'utf8');
|
|
31
|
+
// Auto-detect kind from file extension if not specified
|
|
32
|
+
const kind = explicitKind ?? (abs.endsWith('.vue') ? 'vue:script-setup' : 'module');
|
|
33
|
+
const list = kind === 'vue:script-setup'
|
|
34
|
+
? mutateVueSfcScriptSetup(abs, code, includeArr, excludeArr, max)
|
|
35
|
+
: mutateModuleSource(code, includeArr, excludeArr, max);
|
|
36
|
+
return list.map((v, i) => ({
|
|
37
|
+
id: `${path.basename(abs)}#${i}`,
|
|
38
|
+
name: v.name,
|
|
39
|
+
file: abs,
|
|
40
|
+
code: v.code,
|
|
41
|
+
line: v.line,
|
|
42
|
+
col: v.col,
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const detail = typeof err === 'object' && err !== null && 'stack' in err
|
|
47
|
+
? err.stack
|
|
48
|
+
: err;
|
|
49
|
+
log.debug(`Failed to enumerate variants for ${abs}:`, detail);
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Filter tests to only those that cover a specific line in a file.
|
|
55
|
+
*/
|
|
56
|
+
export function filterTestsByCoverage(perTest, tests, filePath, line) {
|
|
57
|
+
return tests.filter((testPath) => {
|
|
58
|
+
const filesCovered = perTest.get(testPath);
|
|
59
|
+
if (!filesCovered)
|
|
60
|
+
return true;
|
|
61
|
+
const lines = filesCovered.get(filePath);
|
|
62
|
+
if (!lines)
|
|
63
|
+
return true;
|
|
64
|
+
return lines.has(line);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { createVitestAdapter, isCoverageRequestedInArgs } from '../adapter.js';
|
|
6
|
+
// Mock VitestPool to avoid spinning processes
|
|
7
|
+
var poolInstance = null;
|
|
8
|
+
var poolCtorOpts = null;
|
|
9
|
+
vi.mock('../pool.js', () => {
|
|
10
|
+
class MockPool {
|
|
11
|
+
constructor(opts) {
|
|
12
|
+
this.init = vi.fn();
|
|
13
|
+
this.run = vi.fn();
|
|
14
|
+
this.shutdown = vi.fn();
|
|
15
|
+
poolCtorOpts = opts;
|
|
16
|
+
poolInstance = this;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return { VitestPool: MockPool };
|
|
20
|
+
});
|
|
21
|
+
// Mock spawn for baseline runs
|
|
22
|
+
var spawnMock;
|
|
23
|
+
vi.mock('node:child_process', async () => {
|
|
24
|
+
const actual = await vi.importActual('node:child_process');
|
|
25
|
+
spawnMock = vi.fn();
|
|
26
|
+
return { ...actual, spawn: spawnMock };
|
|
27
|
+
});
|
|
28
|
+
function makeAdapter(opts = {}) {
|
|
29
|
+
return createVitestAdapter({
|
|
30
|
+
cwd: opts.cwd ?? process.cwd(),
|
|
31
|
+
concurrency: opts.concurrency ?? 2,
|
|
32
|
+
timeoutMs: opts.timeoutMs ?? 1000,
|
|
33
|
+
config: opts.config ?? { vitestConfig: undefined },
|
|
34
|
+
cliArgs: opts.cliArgs ?? ['--changed'],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
describe('Vitest adapter', () => {
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
vi.clearAllMocks();
|
|
40
|
+
});
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
vi.useRealTimers();
|
|
43
|
+
});
|
|
44
|
+
it('initializes pool with override concurrency', async () => {
|
|
45
|
+
const adapter = makeAdapter();
|
|
46
|
+
await adapter.init(5);
|
|
47
|
+
expect(poolInstance?.init).toHaveBeenCalledTimes(1);
|
|
48
|
+
expect(poolCtorOpts.concurrency).toBe(5);
|
|
49
|
+
});
|
|
50
|
+
it('maps pool result to mutant status', async () => {
|
|
51
|
+
poolInstance = null;
|
|
52
|
+
poolCtorOpts = null;
|
|
53
|
+
const adapter = makeAdapter();
|
|
54
|
+
await adapter.init();
|
|
55
|
+
poolInstance.run.mockResolvedValueOnce({ killed: true, durationMs: 10 });
|
|
56
|
+
const res = await adapter.runMutant({ id: '1', name: 'm', file: 'f', code: 'c', line: 1, col: 1 }, ['t']);
|
|
57
|
+
expect(res).toEqual({ status: 'killed', durationMs: 10, error: undefined });
|
|
58
|
+
});
|
|
59
|
+
it('maps pool timeout errors to timeout status', async () => {
|
|
60
|
+
const adapter = makeAdapter();
|
|
61
|
+
await adapter.init();
|
|
62
|
+
poolInstance.run.mockResolvedValueOnce({
|
|
63
|
+
killed: true,
|
|
64
|
+
durationMs: 15,
|
|
65
|
+
error: 'timeout',
|
|
66
|
+
});
|
|
67
|
+
const res = await adapter.runMutant({ id: '1', name: 'm', file: 'f', code: 'c', line: 1, col: 1 }, ['t']);
|
|
68
|
+
expect(res).toEqual({ status: 'timeout', durationMs: 15, error: 'timeout' });
|
|
69
|
+
});
|
|
70
|
+
it('maps pool errors to error status', async () => {
|
|
71
|
+
const adapter = makeAdapter();
|
|
72
|
+
await adapter.init();
|
|
73
|
+
poolInstance.run.mockResolvedValueOnce({
|
|
74
|
+
killed: true,
|
|
75
|
+
durationMs: 12,
|
|
76
|
+
error: 'crash',
|
|
77
|
+
});
|
|
78
|
+
const res = await adapter.runMutant({ id: '1', name: 'm', file: 'f', code: 'c', line: 1, col: 1 }, ['t']);
|
|
79
|
+
expect(res).toEqual({ status: 'error', durationMs: 12, error: 'crash' });
|
|
80
|
+
});
|
|
81
|
+
it('returns error status on pool throw', async () => {
|
|
82
|
+
const adapter = makeAdapter();
|
|
83
|
+
await adapter.init();
|
|
84
|
+
poolInstance.run.mockRejectedValueOnce(new Error('boom'));
|
|
85
|
+
const res = await adapter.runMutant({ id: '1', name: 'm', file: 'f', code: 'c', line: 1, col: 1 }, ['t']);
|
|
86
|
+
expect(res.status).toBe('error');
|
|
87
|
+
});
|
|
88
|
+
it('includes coverage args for baseline when requested', async () => {
|
|
89
|
+
const adapter = makeAdapter({ cliArgs: [] });
|
|
90
|
+
spawnMock.mockImplementationOnce(() => ({
|
|
91
|
+
on: (evt, cb) => {
|
|
92
|
+
if (evt === 'exit')
|
|
93
|
+
cb(0);
|
|
94
|
+
},
|
|
95
|
+
}));
|
|
96
|
+
const ok = await adapter.runBaseline(['test-a'], {
|
|
97
|
+
collectCoverage: true,
|
|
98
|
+
perTestCoverage: true,
|
|
99
|
+
});
|
|
100
|
+
expect(ok).toBe(true);
|
|
101
|
+
const call = spawnMock.mock.calls[0];
|
|
102
|
+
const args = call[1];
|
|
103
|
+
expect(args.join(' ')).toContain('--coverage.enabled=true');
|
|
104
|
+
expect(args.join(' ')).toContain('--coverage.perTest=true');
|
|
105
|
+
});
|
|
106
|
+
it('detects coverage config from vitest config file', async () => {
|
|
107
|
+
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-vitest-'));
|
|
108
|
+
const cfgPath = path.join(tmp, 'vitest.config.ts');
|
|
109
|
+
await fs.writeFile(cfgPath, 'export default { coverage: { enabled: true }, test: { coverage: { perTest: true } } }');
|
|
110
|
+
try {
|
|
111
|
+
const adapter = makeAdapter({
|
|
112
|
+
cwd: tmp,
|
|
113
|
+
config: { vitestConfig: 'vitest.config.ts' },
|
|
114
|
+
});
|
|
115
|
+
const coverage = await adapter.detectCoverageConfig();
|
|
116
|
+
expect(coverage.coverageEnabled).toBe(true);
|
|
117
|
+
expect(coverage.perTestEnabled).toBe(true);
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
await fs.rm(tmp, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('isCoverageRequestedInArgs', () => {
|
|
125
|
+
it('detects enabled coverage flags', () => {
|
|
126
|
+
expect(isCoverageRequestedInArgs(['--coverage'])).toBe(true);
|
|
127
|
+
expect(isCoverageRequestedInArgs(['--coverage.enabled=true'])).toBe(true);
|
|
128
|
+
expect(isCoverageRequestedInArgs(['--coverage.enabled=false'])).toBe(false);
|
|
129
|
+
expect(isCoverageRequestedInArgs(['--no-coverage'])).toBe(false);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { poolMutineerPlugin } from '../plugin.js';
|
|
6
|
+
function getLoadFn() {
|
|
7
|
+
const plugin = poolMutineerPlugin();
|
|
8
|
+
if (Array.isArray(plugin)) {
|
|
9
|
+
return plugin[0]?.load;
|
|
10
|
+
}
|
|
11
|
+
if (plugin && typeof plugin === 'object' && 'load' in plugin) {
|
|
12
|
+
return plugin.load;
|
|
13
|
+
}
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
describe('poolMutineerPlugin', () => {
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
;
|
|
19
|
+
globalThis.__mutineer_redirect__ = undefined;
|
|
20
|
+
});
|
|
21
|
+
it('returns null when no redirect is configured', () => {
|
|
22
|
+
const load = getLoadFn();
|
|
23
|
+
const result = load?.('/some/file.ts');
|
|
24
|
+
expect(result).toBeNull();
|
|
25
|
+
});
|
|
26
|
+
it('returns the mutated file when the id matches the redirect target', async () => {
|
|
27
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-plugin-'));
|
|
28
|
+
const fromPath = path.join(tmpDir, 'source.ts');
|
|
29
|
+
const mutatedPath = path.join(tmpDir, 'mutated.ts');
|
|
30
|
+
await fs.writeFile(fromPath, 'export const original = true\n', 'utf8');
|
|
31
|
+
await fs.writeFile(mutatedPath, 'export const mutated = true\n', 'utf8');
|
|
32
|
+
try {
|
|
33
|
+
;
|
|
34
|
+
globalThis.__mutineer_redirect__ = {
|
|
35
|
+
from: fromPath,
|
|
36
|
+
to: mutatedPath,
|
|
37
|
+
};
|
|
38
|
+
const load = getLoadFn();
|
|
39
|
+
const result = load?.(fromPath);
|
|
40
|
+
expect(result).toBe('export const mutated = true\n');
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
it('ignores modules that do not match the redirect', async () => {
|
|
47
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'mutineer-pool-plugin-'));
|
|
48
|
+
const fromPath = path.join(tmpDir, 'source.ts');
|
|
49
|
+
const mutatedPath = path.join(tmpDir, 'mutated.ts');
|
|
50
|
+
await fs.writeFile(mutatedPath, 'export const mutated = true\n', 'utf8');
|
|
51
|
+
try {
|
|
52
|
+
;
|
|
53
|
+
globalThis.__mutineer_redirect__ = {
|
|
54
|
+
from: fromPath,
|
|
55
|
+
to: mutatedPath,
|
|
56
|
+
};
|
|
57
|
+
const load = getLoadFn();
|
|
58
|
+
const result = load?.(path.join(tmpDir, 'other.ts?import'));
|
|
59
|
+
expect(result).toBeNull();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|