apex-mutation-testing 1.5.2 → 1.6.1
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/README.md +2 -1
- package/lib/adapter/apexClassRepository.d.ts +14 -1
- package/lib/adapter/apexClassRepository.js +93 -31
- package/lib/adapter/apexClassRepository.js.map +1 -1
- package/lib/mutator/experimentalSwitchMutator.d.ts +7 -0
- package/lib/mutator/experimentalSwitchMutator.js +18 -1
- package/lib/mutator/experimentalSwitchMutator.js.map +1 -1
- package/lib/mutator/mutationListener.d.ts +2 -0
- package/lib/mutator/mutationListener.js +20 -8
- package/lib/mutator/mutationListener.js.map +1 -1
- package/lib/reporter/HTMLReporter.js +68 -12
- package/lib/reporter/HTMLReporter.js.map +1 -1
- package/lib/service/configReader.js +8 -1
- package/lib/service/configReader.js.map +1 -1
- package/lib/service/mutantGenerator.d.ts +12 -4
- package/lib/service/mutantGenerator.js +16 -12
- package/lib/service/mutantGenerator.js.map +1 -1
- package/lib/service/mutationTestingService.d.ts +0 -1
- package/lib/service/mutationTestingService.js +61 -30
- package/lib/service/mutationTestingService.js.map +1 -1
- package/lib/service/typeDiscoverer.d.ts +12 -0
- package/lib/service/typeDiscoverer.js +46 -2
- package/lib/service/typeDiscoverer.js.map +1 -1
- package/npm-shrinkwrap.json +1210 -1207
- package/oclif.manifest.json +1 -1
- package/package.json +34 -15
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Apex Mutation Testing
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/apex-mutation-testing) [](https://npmjs.org/package/apex-mutation-testing) [](https://raw.githubusercontent.com/scolladon/apex-mutation-testing/main/LICENSE.md)
|
|
4
|
+
[](https://scolladon.github.io/apex-mutation-testing/dev/bench/runtime/)
|
|
4
5
|

|
|
5
6
|
|
|
6
7
|
## Disclaimer
|
|
@@ -380,7 +381,7 @@ EXAMPLES
|
|
|
380
381
|
$ sf apex mutation test run --apex-class MyClass --test-class MyClassTest --dry-run
|
|
381
382
|
```
|
|
382
383
|
|
|
383
|
-
_See code: [src/commands/apex/mutation/test/run.ts](https://github.com/scolladon/apex-mutation-testing/blob/v1.
|
|
384
|
+
_See code: [src/commands/apex/mutation/test/run.ts](https://github.com/scolladon/apex-mutation-testing/blob/v1.6.1/src/commands/apex/mutation/test/run.ts)_
|
|
384
385
|
<!-- commandsstop -->
|
|
385
386
|
<!-- markdownlint-enable MD040 -->
|
|
386
387
|
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
import { Connection } from '@salesforce/core';
|
|
2
2
|
import { ApexClass } from '../type/ApexClass.js';
|
|
3
3
|
import { MetadataComponentDependency } from '../type/MetadataComponentDependency.js';
|
|
4
|
+
interface PollOptions {
|
|
5
|
+
initialIntervalMs?: number;
|
|
6
|
+
maxIntervalMs?: number;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare class PollTimeoutError extends Error {
|
|
10
|
+
readonly requestId: string;
|
|
11
|
+
readonly lastState: string;
|
|
12
|
+
constructor(requestId: string, lastState: string);
|
|
13
|
+
}
|
|
4
14
|
export declare class ApexClassRepository {
|
|
5
15
|
protected readonly connection: Connection;
|
|
6
|
-
|
|
16
|
+
private readonly pollOptions;
|
|
17
|
+
constructor(connection: Connection, pollOptions?: PollOptions);
|
|
7
18
|
read(name: string): Promise<{
|
|
8
19
|
[name: string]: import("@jsforce/jsforce-node").SObjectFieldType | null;
|
|
9
20
|
} & import("@jsforce/jsforce-node").Record>;
|
|
@@ -11,6 +22,8 @@ export declare class ApexClassRepository {
|
|
|
11
22
|
update(apexClass: ApexClass): Promise<{
|
|
12
23
|
[name: string]: import("@jsforce/jsforce-node").SObjectFieldType | null;
|
|
13
24
|
} & import("@jsforce/jsforce-node").Record>;
|
|
25
|
+
private deleteContainer;
|
|
14
26
|
private pollForCompletion;
|
|
15
27
|
private delay;
|
|
16
28
|
}
|
|
29
|
+
export {};
|
|
@@ -1,7 +1,43 @@
|
|
|
1
|
+
const DEFAULT_POLL_INITIAL_INTERVAL_MS = 100;
|
|
2
|
+
const DEFAULT_POLL_MAX_INTERVAL_MS = 2000;
|
|
3
|
+
const DEFAULT_POLL_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
4
|
+
const POLL_BACKOFF_FACTOR = 1.5;
|
|
5
|
+
const TERMINAL_STATES = new Set([
|
|
6
|
+
'Completed',
|
|
7
|
+
'Failed',
|
|
8
|
+
'Error',
|
|
9
|
+
'Aborted',
|
|
10
|
+
]);
|
|
11
|
+
export class PollTimeoutError extends Error {
|
|
12
|
+
requestId;
|
|
13
|
+
lastState;
|
|
14
|
+
constructor(requestId, lastState) {
|
|
15
|
+
super(`Tooling API ContainerAsyncRequest ${requestId} did not reach a terminal state within the poll timeout (last state: ${lastState})`);
|
|
16
|
+
this.requestId = requestId;
|
|
17
|
+
this.lastState = lastState;
|
|
18
|
+
this.name = 'PollTimeoutError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
1
21
|
export class ApexClassRepository {
|
|
2
22
|
connection;
|
|
3
|
-
|
|
23
|
+
pollOptions;
|
|
24
|
+
constructor(connection, pollOptions = {}) {
|
|
4
25
|
this.connection = connection;
|
|
26
|
+
this.pollOptions = pollOptions;
|
|
27
|
+
// Validate poll configuration eagerly so misconfiguration fails fast
|
|
28
|
+
// at construction rather than mid-deploy with non-deterministic behaviour.
|
|
29
|
+
const { initialIntervalMs, maxIntervalMs, timeoutMs } = pollOptions;
|
|
30
|
+
if (initialIntervalMs !== undefined && initialIntervalMs < 0) {
|
|
31
|
+
throw new Error(`pollOptions.initialIntervalMs must be >= 0 (got ${initialIntervalMs})`);
|
|
32
|
+
}
|
|
33
|
+
if (maxIntervalMs !== undefined && maxIntervalMs < 0) {
|
|
34
|
+
throw new Error(`pollOptions.maxIntervalMs must be >= 0 (got ${maxIntervalMs})`);
|
|
35
|
+
}
|
|
36
|
+
// timeoutMs <= 0 allowed only for test harnesses that want an instant
|
|
37
|
+
// timeout; reject 0 which is the racy value (deadline == now).
|
|
38
|
+
if (timeoutMs !== undefined && timeoutMs === 0) {
|
|
39
|
+
throw new Error(`pollOptions.timeoutMs must be non-zero (0 is racy); use a negative value for immediate timeout or a positive value for a real budget`);
|
|
40
|
+
}
|
|
5
41
|
}
|
|
6
42
|
async read(name) {
|
|
7
43
|
return (await this.connection.tooling
|
|
@@ -21,43 +57,69 @@ export class ApexClassRepository {
|
|
|
21
57
|
.create({
|
|
22
58
|
Name: `MutationTest_${Date.now()}`,
|
|
23
59
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
60
|
+
if (!container.id) {
|
|
61
|
+
throw new Error('MetadataContainer did not return an ID');
|
|
62
|
+
}
|
|
63
|
+
const containerId = container.id;
|
|
64
|
+
try {
|
|
65
|
+
await this.connection.tooling.sobject('ApexClassMember').create({
|
|
66
|
+
MetadataContainerId: containerId,
|
|
67
|
+
ContentEntityId: apexClass.Id,
|
|
68
|
+
Body: apexClass.Body,
|
|
69
|
+
});
|
|
70
|
+
const asyncRequest = await this.connection.tooling
|
|
71
|
+
.sobject('ContainerAsyncRequest')
|
|
72
|
+
.create({
|
|
73
|
+
IsCheckOnly: false,
|
|
74
|
+
MetadataContainerId: containerId,
|
|
75
|
+
IsRunTests: true,
|
|
76
|
+
});
|
|
77
|
+
if (!asyncRequest.id) {
|
|
78
|
+
throw new Error('ContainerAsyncRequest did not return an ID');
|
|
79
|
+
}
|
|
80
|
+
const result = await this.pollForCompletion(asyncRequest.id);
|
|
81
|
+
if (result['State'] === 'Failed') {
|
|
82
|
+
const messages = result['DeployDetails']?.['allComponentMessages'];
|
|
83
|
+
const formattedErrors = Array.isArray(messages)
|
|
84
|
+
? messages
|
|
85
|
+
.map(m => `[${m.fileName}:${m.lineNumber}:${m.columnNumber}] ${m.problem}`)
|
|
86
|
+
.join('\n')
|
|
87
|
+
: result['ErrorMsg'] || 'Unknown error';
|
|
88
|
+
throw new Error(`Deployment failed:\n${formattedErrors}`);
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
40
91
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
? messages
|
|
47
|
-
.map(m => `[${m.fileName}:${m.lineNumber}:${m.columnNumber}] ${m.problem}`)
|
|
48
|
-
.join('\n')
|
|
49
|
-
: result['ErrorMsg'] || 'Unknown error';
|
|
50
|
-
throw new Error(`Deployment failed:\n${formattedErrors}`);
|
|
92
|
+
finally {
|
|
93
|
+
// Fire-and-forget cleanup: awaiting this would add a full Tooling API
|
|
94
|
+
// round-trip to every deploy (500 extra calls on a 500-mutant run).
|
|
95
|
+
// If the delete fails, Salesforce reaps the MetadataContainer after 24h.
|
|
96
|
+
this.deleteContainer(containerId);
|
|
51
97
|
}
|
|
52
|
-
|
|
98
|
+
}
|
|
99
|
+
deleteContainer(containerId) {
|
|
100
|
+
this.connection.tooling
|
|
101
|
+
.sobject('MetadataContainer')
|
|
102
|
+
.delete(containerId)
|
|
103
|
+
.catch(() => {
|
|
104
|
+
// Non-fatal: swallow so the unhandled rejection does not surface
|
|
105
|
+
// and the container gets reaped by Salesforce after 24h.
|
|
106
|
+
});
|
|
53
107
|
}
|
|
54
108
|
async pollForCompletion(requestId) {
|
|
55
|
-
const
|
|
109
|
+
const initialIntervalMs = this.pollOptions.initialIntervalMs ?? DEFAULT_POLL_INITIAL_INTERVAL_MS;
|
|
110
|
+
const maxIntervalMs = this.pollOptions.maxIntervalMs ?? DEFAULT_POLL_MAX_INTERVAL_MS;
|
|
111
|
+
const timeoutMs = this.pollOptions.timeoutMs ?? DEFAULT_POLL_TIMEOUT_MS;
|
|
112
|
+
const deadline = Date.now() + timeoutMs;
|
|
113
|
+
let intervalMs = initialIntervalMs;
|
|
56
114
|
let result = await this.connection.tooling
|
|
57
115
|
.sobject('ContainerAsyncRequest')
|
|
58
116
|
.retrieve(requestId);
|
|
59
|
-
while (!
|
|
60
|
-
|
|
117
|
+
while (!TERMINAL_STATES.has(result['State'])) {
|
|
118
|
+
if (Date.now() > deadline) {
|
|
119
|
+
throw new PollTimeoutError(requestId, String(result['State']));
|
|
120
|
+
}
|
|
121
|
+
await this.delay(intervalMs);
|
|
122
|
+
intervalMs = Math.min(Math.floor(intervalMs * POLL_BACKOFF_FACTOR), maxIntervalMs);
|
|
61
123
|
result = await this.connection.tooling
|
|
62
124
|
.sobject('ContainerAsyncRequest')
|
|
63
125
|
.retrieve(requestId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apexClassRepository.js","sourceRoot":"","sources":["../../src/adapter/apexClassRepository.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"apexClassRepository.js","sourceRoot":"","sources":["../../src/adapter/apexClassRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,4 +4,11 @@ export declare class ExperimentalSwitchMutator extends BaseListener {
|
|
|
4
4
|
enterSwitchStatement(ctx: ParserRuleContext): void;
|
|
5
5
|
private isElseCase;
|
|
6
6
|
private createSwapAdjacentValuesMutations;
|
|
7
|
+
/**
|
|
8
|
+
* Swap-span mutation spans two when-clauses. The MutationListener only
|
|
9
|
+
* verifies the switch-statement start line is covered; this method asserts
|
|
10
|
+
* coverage for at least one line inside each when-clause so we never emit
|
|
11
|
+
* a mutation whose replacement body is wholly uncovered.
|
|
12
|
+
*/
|
|
13
|
+
private isSpanCovered;
|
|
7
14
|
}
|
|
@@ -47,11 +47,28 @@ export class ExperimentalSwitchMutator extends BaseListener {
|
|
|
47
47
|
// with swapped values (keeping blocks in original positions)
|
|
48
48
|
const originalText = currentCase.text + nextCase.text;
|
|
49
49
|
const swappedText = `when ${nextValue.text} ${currentBlock.text}when ${currentValue.text} ${nextBlock.text}`;
|
|
50
|
-
if (currentCase.start &&
|
|
50
|
+
if (currentCase.start &&
|
|
51
|
+
nextCase.stop &&
|
|
52
|
+
this.isSpanCovered(currentCase.start.line, nextCase.stop.line)) {
|
|
51
53
|
this.createMutation(currentCase.start, nextCase.stop, originalText, swappedText);
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Swap-span mutation spans two when-clauses. The MutationListener only
|
|
60
|
+
* verifies the switch-statement start line is covered; this method asserts
|
|
61
|
+
* coverage for at least one line inside each when-clause so we never emit
|
|
62
|
+
* a mutation whose replacement body is wholly uncovered.
|
|
63
|
+
*/
|
|
64
|
+
isSpanCovered(startLine, endLine) {
|
|
65
|
+
if (!this._coveredLines)
|
|
66
|
+
return true;
|
|
67
|
+
for (let line = startLine; line <= endLine; line++) {
|
|
68
|
+
if (this._coveredLines.has(line))
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
56
73
|
}
|
|
57
74
|
//# sourceMappingURL=experimentalSwitchMutator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"experimentalSwitchMutator.js","sourceRoot":"","sources":["../../src/mutator/experimentalSwitchMutator.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"experimentalSwitchMutator.js","sourceRoot":"","sources":["../../src/mutator/experimentalSwitchMutator.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,8 +8,10 @@ export declare class MutationListener implements ApexParserListener {
|
|
|
8
8
|
protected readonly allowedLines: Set<number> | undefined;
|
|
9
9
|
protected readonly sourceLines: string[];
|
|
10
10
|
private listeners;
|
|
11
|
+
private readonly dispatchCache;
|
|
11
12
|
_mutations: ApexMutation[];
|
|
12
13
|
getMutations(): ApexMutation[];
|
|
13
14
|
constructor(listeners: BaseListener[], coveredLines: Set<number>, skipPatterns?: RE2Instance[], allowedLines?: Set<number> | undefined, sourceLines?: string[]);
|
|
15
|
+
private resolveSubscribers;
|
|
14
16
|
private isLineEligible;
|
|
15
17
|
}
|
|
@@ -5,6 +5,10 @@ export class MutationListener {
|
|
|
5
5
|
allowedLines;
|
|
6
6
|
sourceLines;
|
|
7
7
|
listeners;
|
|
8
|
+
// Memoised dispatch table: method name → listeners that implement it.
|
|
9
|
+
// Populated lazily per method on first invocation so Proxy traps for
|
|
10
|
+
// unknown ANTLR hooks do not re-scan every listener on every AST node.
|
|
11
|
+
dispatchCache = new Map();
|
|
8
12
|
_mutations = [];
|
|
9
13
|
getMutations() {
|
|
10
14
|
return this._mutations;
|
|
@@ -25,29 +29,37 @@ export class MutationListener {
|
|
|
25
29
|
;
|
|
26
30
|
listener._mutations = this._mutations;
|
|
27
31
|
});
|
|
28
|
-
// Create a proxy that automatically forwards all method calls to listeners
|
|
32
|
+
// Create a proxy that automatically forwards all method calls to listeners.
|
|
33
|
+
// Per-trap cost is now O(K) where K = listeners implementing the hook
|
|
34
|
+
// (typically 1-2), not O(N) over all 25 mutators.
|
|
29
35
|
return new Proxy(this, {
|
|
30
36
|
get: (target, prop) => {
|
|
31
37
|
if (prop in target) {
|
|
32
38
|
return target[prop];
|
|
33
39
|
}
|
|
34
|
-
// Return a function that calls the method on all listeners that have it
|
|
35
40
|
return (...args) => {
|
|
36
41
|
if (Array.isArray(args) && args.length > 0) {
|
|
37
42
|
const ctx = args[0];
|
|
38
43
|
if (this.isLineEligible(ctx?.start?.line)) {
|
|
39
|
-
this.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
});
|
|
44
|
+
const subscribers = this.resolveSubscribers(prop);
|
|
45
|
+
for (const listener of subscribers) {
|
|
46
|
+
;
|
|
47
|
+
listener[prop].apply(listener, args);
|
|
48
|
+
}
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
};
|
|
48
52
|
},
|
|
49
53
|
});
|
|
50
54
|
}
|
|
55
|
+
resolveSubscribers(prop) {
|
|
56
|
+
const cached = this.dispatchCache.get(prop);
|
|
57
|
+
if (cached !== undefined)
|
|
58
|
+
return cached;
|
|
59
|
+
const subs = this.listeners.filter(listener => prop in listener && typeof listener[prop] === 'function');
|
|
60
|
+
this.dispatchCache.set(prop, subs);
|
|
61
|
+
return subs;
|
|
62
|
+
}
|
|
51
63
|
isLineEligible(line) {
|
|
52
64
|
if (!line) {
|
|
53
65
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mutationListener.js","sourceRoot":"","sources":["../../src/mutator/mutationListener.ts"],"names":[],"mappings":"AAMA,2DAA2D;AAC3D,MAAM,OAAO,gBAAgB;
|
|
1
|
+
{"version":3,"file":"mutationListener.js","sourceRoot":"","sources":["../../src/mutator/mutationListener.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,16 +1,35 @@
|
|
|
1
|
-
import { writeFile } from 'node:fs/promises';
|
|
1
|
+
import { readFile, realpath, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
2
3
|
import * as path from 'path';
|
|
4
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
5
|
+
const MUTATION_TEST_ELEMENTS_PATH = requireFromHere.resolve('mutation-testing-elements/dist/mutation-test-elements.js');
|
|
6
|
+
async function loadMutationTestElements() {
|
|
7
|
+
const content = await readFile(MUTATION_TEST_ELEMENTS_PATH, 'utf8');
|
|
8
|
+
return content;
|
|
9
|
+
}
|
|
3
10
|
export class ApexMutationHTMLReporter {
|
|
4
11
|
async generateReport(apexMutationTestResult, outputDir = 'reports') {
|
|
12
|
+
// The caller (run.ts) validates that `outputDir` exists via the oclif
|
|
13
|
+
// `Flags.directory({ exists: true })` guard. We deliberately do NOT
|
|
14
|
+
// create the directory here: the plugin may be installed under a more
|
|
15
|
+
// privileged user than the one running the command, and auto-creating
|
|
16
|
+
// paths under that higher privilege would let a crafted `-r` flag write
|
|
17
|
+
// into locations the invoking user should not be able to touch.
|
|
18
|
+
//
|
|
19
|
+
// Two-stage path check:
|
|
20
|
+
// 1. string-level resolve rejects `../` traversal;
|
|
21
|
+
// 2. realpath rejects an existing symlink whose target is outside cwd.
|
|
22
|
+
const resolvedDir = resolveSafeOutputDir(outputDir);
|
|
23
|
+
await assertRealPathWithinCwd(resolvedDir, outputDir);
|
|
5
24
|
const reportData = this.transformApexResults(apexMutationTestResult);
|
|
6
|
-
|
|
7
|
-
const htmlContent = createReportHtml(reportData);
|
|
8
|
-
await writeFile(path.join(
|
|
25
|
+
const bundle = await loadMutationTestElements();
|
|
26
|
+
const htmlContent = createReportHtml(reportData, bundle);
|
|
27
|
+
await writeFile(path.join(resolvedDir, 'index.html'), htmlContent);
|
|
9
28
|
}
|
|
10
29
|
transformApexResults(apexMutationTestResult) {
|
|
11
30
|
const mutationTestResult = {
|
|
12
31
|
schemaVersion: '2.0.0',
|
|
13
|
-
config: {},
|
|
32
|
+
config: {},
|
|
14
33
|
thresholds: {
|
|
15
34
|
high: 80,
|
|
16
35
|
low: 60,
|
|
@@ -52,31 +71,68 @@ export class ApexMutationHTMLReporter {
|
|
|
52
71
|
return mutationTestResult;
|
|
53
72
|
}
|
|
54
73
|
}
|
|
55
|
-
|
|
74
|
+
function resolveSafeOutputDir(outputDir) {
|
|
75
|
+
const resolved = path.resolve(outputDir);
|
|
76
|
+
const cwd = path.resolve(process.cwd());
|
|
77
|
+
if (resolved !== cwd && !resolved.startsWith(cwd + path.sep)) {
|
|
78
|
+
throw new Error(`Report directory '${outputDir}' resolves outside the current working directory (${cwd}). Refusing to write reports outside the project root.`);
|
|
79
|
+
}
|
|
80
|
+
return resolved;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve symbolic links in the target directory and verify the dereferenced
|
|
84
|
+
* path still lives inside cwd. Defeats attacks where a symlink `reports` → `/etc`
|
|
85
|
+
* is present at cwd and the string-level check in resolveSafeOutputDir is satisfied.
|
|
86
|
+
*/
|
|
87
|
+
async function assertRealPathWithinCwd(resolvedDir, originalInput) {
|
|
88
|
+
const realDir = await realpath(resolvedDir);
|
|
89
|
+
const realCwd = await realpath(process.cwd());
|
|
90
|
+
if (realDir !== realCwd && !realDir.startsWith(realCwd + path.sep)) {
|
|
91
|
+
throw new Error(`Report directory '${originalInput}' dereferences to '${realDir}', outside the current working directory (${realCwd}). Refusing to follow symlinks out of the project root.`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const createReportHtml = (report, elementsBundle) => {
|
|
95
|
+
const safeJson = serializeReportForScript(report);
|
|
96
|
+
const safeBundle = neutraliseScriptContent(elementsBundle);
|
|
56
97
|
return `<!DOCTYPE html>
|
|
57
98
|
<html>
|
|
58
99
|
<head>
|
|
59
100
|
<meta charset="utf-8">
|
|
60
101
|
<title>Mutation Testing Report</title>
|
|
61
|
-
<script
|
|
102
|
+
<script>${safeBundle}</script>
|
|
62
103
|
</head>
|
|
63
104
|
<body>
|
|
64
105
|
<mutation-test-report-app titlePostfix="apex-mutation-testing">
|
|
65
106
|
Your browser doesn't support <a href="https://caniuse.com/#search=custom%20elements">custom elements</a>.
|
|
66
107
|
Please use a latest version of an evergreen browser (Firefox, Chrome, Safari, Opera, Edge, etc).
|
|
67
108
|
</mutation-test-report-app>
|
|
109
|
+
<script id="mutation-report-data" type="application/json">${safeJson}</script>
|
|
68
110
|
<script>
|
|
69
111
|
const app = document.querySelector('mutation-test-report-app');
|
|
70
|
-
app.report =
|
|
112
|
+
app.report = JSON.parse(document.getElementById('mutation-report-data').textContent);
|
|
71
113
|
</script>
|
|
72
114
|
</body>
|
|
73
115
|
</html>`;
|
|
74
116
|
};
|
|
75
117
|
/**
|
|
76
|
-
*
|
|
118
|
+
* Neutralise `</script` so a vendored bundle cannot prematurely close the host <script> tag.
|
|
119
|
+
* The bundle is trusted (our own node_modules), but defensive escaping costs nothing.
|
|
120
|
+
*/
|
|
121
|
+
function neutraliseScriptContent(content) {
|
|
122
|
+
return content.replace(/<\/script/gi, '<\\/script');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Serialise report data for safe embedding inside a <script type="application/json"> block.
|
|
126
|
+
* Neutralises every character sequence a browser parser treats specially inside script content:
|
|
127
|
+
* `</` (script-end sentinel), `<!--` (HTML comment open), `-->` (close), `<script`, and U+2028/2029.
|
|
77
128
|
*/
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
129
|
+
function serializeReportForScript(report) {
|
|
130
|
+
return JSON.stringify(report)
|
|
131
|
+
.replace(/<\//g, '<\\/')
|
|
132
|
+
.replace(/<!--/g, '<\\!--')
|
|
133
|
+
.replace(/-->/g, '--\\>')
|
|
134
|
+
.replace(/<script/gi, '<\\script')
|
|
135
|
+
.replace(/\u2028/g, '\\u2028')
|
|
136
|
+
.replace(/\u2029/g, '\\u2029');
|
|
81
137
|
}
|
|
82
138
|
//# sourceMappingURL=HTMLReporter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HTMLReporter.js","sourceRoot":"","sources":["../../src/reporter/HTMLReporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"HTMLReporter.js","sourceRoot":"","sources":["../../src/reporter/HTMLReporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,12 +41,19 @@ export class ConfigReader {
|
|
|
41
41
|
for (const range of lines) {
|
|
42
42
|
if (range.includes('-')) {
|
|
43
43
|
const [start, end] = range.split('-').map(Number);
|
|
44
|
+
if (!Number.isFinite(start) || !Number.isFinite(end) || start > end) {
|
|
45
|
+
throw new Error(`Invalid line range '${range}': must be a number or range (e.g., '10' or '1-10')`);
|
|
46
|
+
}
|
|
44
47
|
for (let i = start; i <= end; i++) {
|
|
45
48
|
result.add(i);
|
|
46
49
|
}
|
|
47
50
|
}
|
|
48
51
|
else {
|
|
49
|
-
|
|
52
|
+
const value = Number(range);
|
|
53
|
+
if (!Number.isFinite(value)) {
|
|
54
|
+
throw new Error(`Invalid line range '${range}': must be a number or range (e.g., '10' or '1-10')`);
|
|
55
|
+
}
|
|
56
|
+
result.add(value);
|
|
50
57
|
}
|
|
51
58
|
}
|
|
52
59
|
return result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configReader.js","sourceRoot":"","sources":["../../src/service/configReader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAE3C,OAAO,GAAG,MAAM,KAAK,CAAA;AAMrB,MAAM,mBAAmB,GAAG,wBAAwB,CAAA;AAgBpD,MAAM,OAAO,YAAY;IAChB,KAAK,CAAC,OAAO,CAClB,SAAgC;QAEhC,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,IAAI,mBAAmB,CAAA;QAC9D,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,CAAA;QAExD,MAAM,QAAQ,GAA0B;YACtC,GAAG,SAAS;YACZ,eAAe,EACb,SAAS,CAAC,eAAe,IAAI,UAAU,EAAE,QAAQ,EAAE,OAAO;YAC5D,eAAe,EACb,SAAS,CAAC,eAAe,IAAI,UAAU,EAAE,QAAQ,EAAE,OAAO;YAC5D,kBAAkB,EAChB,SAAS,CAAC,kBAAkB,IAAI,UAAU,EAAE,WAAW,EAAE,OAAO;YAClE,kBAAkB,EAChB,SAAS,CAAC,kBAAkB,IAAI,UAAU,EAAE,WAAW,EAAE,OAAO;YAClE,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,UAAU,EAAE,SAAS;YACvD,YAAY,EAAE,SAAS,CAAC,YAAY,IAAI,UAAU,EAAE,YAAY;YAChE,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,UAAU,EAAE,KAAK;SAC5C,CAAA;QAED,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAEvB,OAAO,QAAQ,CAAA;IACjB,CAAC;IAEO,KAAK,CAAC,cAAc,CAC1B,UAAkB;QAElB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;YACnD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAA0B,CAAA;QACrD,CAAC;QAAC,OAAO,KAAc,EAAE,CAAC;YACxB,IACE,KAAK;gBACL,OAAO,KAAK,KAAK,QAAQ;gBACzB,MAAM,IAAI,KAAK;gBACf,KAAK,CAAC,IAAI,KAAK,QAAQ,EACvB,CAAC;gBACD,OAAO,SAAS,CAAA;YAClB,CAAC;YACD,MAAM,IAAI,KAAK,CACb,gCAAgC,UAAU,MAAM,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACzG,CAAA;QACH,CAAC;IACH,CAAC;IAEM,MAAM,CAAC,eAAe,CAC3B,KAA2B;QAE3B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACjC,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAA;QAChC,KAAK,MAAM,KAAK,IAAI,KAAK,EAAE,CAAC;YAC1B,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACxB,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;gBACjD,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,IAAI,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;oBAClC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;gBACf,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,MAAM,
|
|
1
|
+
{"version":3,"file":"configReader.js","sourceRoot":"","sources":["../../src/service/configReader.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,15 +1,23 @@
|
|
|
1
|
+
import { ParserRuleContext } from 'antlr4ts';
|
|
1
2
|
import { CommonTokenStream } from 'apex-parser';
|
|
2
3
|
import { ApexMutation } from '../type/ApexMutation.js';
|
|
3
4
|
import { TypeRegistry } from '../type/TypeRegistry.js';
|
|
4
5
|
import type { RE2Instance } from './configReader.js';
|
|
6
|
+
interface MutantGeneratorComputeResult {
|
|
7
|
+
mutations: ApexMutation[];
|
|
8
|
+
tokenStream: CommonTokenStream;
|
|
9
|
+
}
|
|
10
|
+
interface PreParsedInput {
|
|
11
|
+
tree: ParserRuleContext;
|
|
12
|
+
tokenStream: CommonTokenStream;
|
|
13
|
+
}
|
|
5
14
|
export declare class MutantGenerator {
|
|
6
|
-
private tokenStream?;
|
|
7
|
-
getTokenStream(): CommonTokenStream | undefined;
|
|
8
15
|
compute(classContent: string, coveredLines: Set<number>, typeRegistry?: TypeRegistry, mutatorFilter?: {
|
|
9
16
|
include?: string[];
|
|
10
17
|
exclude?: string[];
|
|
11
|
-
}, skipPatterns?: RE2Instance[], allowedLines?: Set<number
|
|
12
|
-
mutate(mutation: ApexMutation): string;
|
|
18
|
+
}, skipPatterns?: RE2Instance[], allowedLines?: Set<number>, preParsed?: PreParsedInput): MutantGeneratorComputeResult;
|
|
19
|
+
mutate(mutation: ApexMutation, tokenStream: CommonTokenStream): string;
|
|
13
20
|
private filterRegistry;
|
|
14
21
|
private warnUnknownMutators;
|
|
15
22
|
}
|
|
23
|
+
export {};
|
|
@@ -162,24 +162,28 @@ const MUTATOR_REGISTRY = [
|
|
|
162
162
|
},
|
|
163
163
|
];
|
|
164
164
|
export class MutantGenerator {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
165
|
+
compute(classContent, coveredLines, typeRegistry, mutatorFilter, skipPatterns = [], allowedLines, preParsed) {
|
|
166
|
+
let tree;
|
|
167
|
+
let tokenStream;
|
|
168
|
+
if (preParsed) {
|
|
169
|
+
tree = preParsed.tree;
|
|
170
|
+
tokenStream = preParsed.tokenStream;
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const lexer = new ApexLexer(new CaseInsensitiveInputStream('other', classContent));
|
|
174
|
+
tokenStream = new CommonTokenStream(lexer);
|
|
175
|
+
const parser = new ApexParser(tokenStream);
|
|
176
|
+
tree = parser.compilationUnit();
|
|
177
|
+
}
|
|
174
178
|
const filteredRegistry = this.filterRegistry(mutatorFilter);
|
|
175
179
|
const mutators = filteredRegistry.map(entry => entry.create(typeRegistry));
|
|
176
180
|
const sourceLines = classContent.split('\n');
|
|
177
181
|
const listener = new MutationListener(mutators, coveredLines, skipPatterns, allowedLines, sourceLines);
|
|
178
182
|
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
179
|
-
return listener.getMutations();
|
|
183
|
+
return { mutations: listener.getMutations(), tokenStream };
|
|
180
184
|
}
|
|
181
|
-
mutate(mutation) {
|
|
182
|
-
const rewriter = new TokenStreamRewriter(
|
|
185
|
+
mutate(mutation, tokenStream) {
|
|
186
|
+
const rewriter = new TokenStreamRewriter(tokenStream);
|
|
183
187
|
rewriter.replace(mutation.target.startToken.tokenIndex, mutation.target.endToken.tokenIndex, mutation.replacement);
|
|
184
188
|
return rewriter.getText();
|
|
185
189
|
}
|