apex-mutation-testing 1.0.0-dev-6.13527894120-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/LICENSE.md +9 -0
- package/README.md +143 -0
- package/lib/adapter/apexClassRepository.d.ts +10 -0
- package/lib/adapter/apexClassRepository.js +34 -0
- package/lib/adapter/apexClassRepository.js.map +1 -0
- package/lib/adapter/apexTestRunner.d.ts +7 -0
- package/lib/adapter/apexTestRunner.js +16 -0
- package/lib/adapter/apexTestRunner.js.map +1 -0
- package/lib/commands/apex/mutation/test/run.d.ts +17 -0
- package/lib/commands/apex/mutation/test/run.js +55 -0
- package/lib/commands/apex/mutation/test/run.js.map +1 -0
- package/lib/mutator/baseListener.d.ts +5 -0
- package/lib/mutator/baseListener.js +5 -0
- package/lib/mutator/baseListener.js.map +1 -0
- package/lib/mutator/boundaryConditionMutator.d.ts +6 -0
- package/lib/mutator/boundaryConditionMutator.js +33 -0
- package/lib/mutator/boundaryConditionMutator.js.map +1 -0
- package/lib/mutator/incrementMutator.d.ts +8 -0
- package/lib/mutator/incrementMutator.js +37 -0
- package/lib/mutator/incrementMutator.js.map +1 -0
- package/lib/mutator/mutationListener.d.ts +9 -0
- package/lib/mutator/mutationListener.js +36 -0
- package/lib/mutator/mutationListener.js.map +1 -0
- package/lib/reporter/HTMLReporter.d.ts +5 -0
- package/lib/reporter/HTMLReporter.js +75 -0
- package/lib/reporter/HTMLReporter.js.map +1 -0
- package/lib/service/mutantGenerator.d.ts +6 -0
- package/lib/service/mutantGenerator.js +28 -0
- package/lib/service/mutantGenerator.js.map +1 -0
- package/lib/service/mutationTestingService.d.ts +17 -0
- package/lib/service/mutationTestingService.js +113 -0
- package/lib/service/mutationTestingService.js.map +1 -0
- package/lib/type/ApexClass.d.ts +4 -0
- package/lib/type/ApexClass.js +2 -0
- package/lib/type/ApexClass.js.map +1 -0
- package/lib/type/ApexMutation.d.ts +6 -0
- package/lib/type/ApexMutation.js +2 -0
- package/lib/type/ApexMutation.js.map +1 -0
- package/lib/type/ApexMutationTestResult.d.ts +22 -0
- package/lib/type/ApexMutationTestResult.js +2 -0
- package/lib/type/ApexMutationTestResult.js.map +1 -0
- package/messages/apex.mutation.test.run.md +49 -0
- package/npm-shrinkwrap.json +13119 -0
- package/oclif.manifest.json +119 -0
- package/package.json +250 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sebastien Colladon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Apex Mutation Testing
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/apex-mutation-testing) [](https://npmjs.org/package/apex-mutation-testing) [](https://raw.githubusercontent.com/salesforcecli/apex-mutation-testing/main/LICENSE.txt)
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
## Disclaimer
|
|
7
|
+
|
|
8
|
+
This project is in its early stages and requires further development and testing.
|
|
9
|
+
It provides a solid foundation for implementing additional features and improvements.
|
|
10
|
+
You are welcome to contribute by logging issue, proposing enhancements or pull requests.
|
|
11
|
+
|
|
12
|
+
## TL;DR
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
sf plugins install apex-mutation-testing
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
sf apex mutation test run --class-file MyClass --test-file MyClassTest
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## What is it mutation testing ?
|
|
23
|
+
|
|
24
|
+
Mutation testing is a software testing technique that evaluates the quality of your test suite by introducing small changes (mutations) to your code and checking if your tests can detect these changes. It helps identify weaknesses in your test coverage by measuring how effectively your tests can catch intentional bugs. cf [wikipedia](https://en.wikipedia.org/wiki/Mutation_testing)
|
|
25
|
+
|
|
26
|
+
The apex-mutation-testing plugin implements this technique for Salesforce Apex code by:
|
|
27
|
+
|
|
28
|
+
1. Parsing your Apex class to identify potential mutation points
|
|
29
|
+
2. Generating mutated versions of your code with specific changes
|
|
30
|
+
3. Deploying each mutated version to a Salesforce org
|
|
31
|
+
4. Running your test class against each mutation
|
|
32
|
+
5. Analyzing the results to determine if your tests:
|
|
33
|
+
- Detected the mutation (killed the mutant)
|
|
34
|
+
- Failed to detect the mutation (created a zombie)
|
|
35
|
+
- Caused a test failure unrelated to the mutation
|
|
36
|
+
6. Generating a detailed report showing mutation coverage and test effectiveness
|
|
37
|
+
|
|
38
|
+
This process helps you identify areas where your tests may be insufficient and provides insights into improving your test quality.
|
|
39
|
+
|
|
40
|
+
cf this [idea](https://ideas.salesforce.com/s/idea/a0B8W00000GdmxmUAB/use-mutation-testing-to-stop-developers-from-cheating-on-apex-tests) for more information about the community appetit
|
|
41
|
+
|
|
42
|
+
## How to use it?
|
|
43
|
+
|
|
44
|
+
Fast unit tests are crucial for mutation testing as each detected mutation is deployed and tested individually. The plugin generates numerous mutations, and having quick-running tests allows for:
|
|
45
|
+
1. Efficient execution of the mutation testing process
|
|
46
|
+
2. Faster feedback on test coverage quality
|
|
47
|
+
3. Ability to test more mutations within time constraints
|
|
48
|
+
4. Reduced resource consumption during testing
|
|
49
|
+
5. More iterations and improvements in test quality
|
|
50
|
+
|
|
51
|
+
The more the test interacts with the database (dml or soql) the more times the test will take
|
|
52
|
+
|
|
53
|
+
### Test Coverage Requirements
|
|
54
|
+
|
|
55
|
+
To maximize the benefits of mutation testing, your test class should have very high code coverage (ideally 100%). Here's why:
|
|
56
|
+
|
|
57
|
+
1. **Mutation Detection**: Mutations can only be detected in code that is executed by your tests. Uncovered code means undetectable mutations.
|
|
58
|
+
|
|
59
|
+
2. **Accurate Metrics**: High coverage ensures the mutation score accurately reflects your test suite's effectiveness.
|
|
60
|
+
|
|
61
|
+
3. **Meaningful Results**: With high coverage, the mutation test results provide actionable insights about your test quality.
|
|
62
|
+
|
|
63
|
+
Before running mutation testing:
|
|
64
|
+
- Ensure your test class achieves maximum coverage
|
|
65
|
+
- Verify all critical paths are tested
|
|
66
|
+
- Include edge case scenarios
|
|
67
|
+
- Validate test assertions are comprehensive
|
|
68
|
+
|
|
69
|
+
Remember, mutation testing complements but doesn't replace good test coverage. It helps identify weaknesses in your existing tests, but only for the code they already cover.
|
|
70
|
+
|
|
71
|
+
<!-- commands -->
|
|
72
|
+
* [`sf apex mutation test run`](#sf-apex-mutation-test-run)
|
|
73
|
+
|
|
74
|
+
## `sf apex mutation test run`
|
|
75
|
+
|
|
76
|
+
Evaluate test coverage quality by injecting mutations and measuring test detection rates
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
USAGE
|
|
80
|
+
$ sf apex mutation test run -c <value> -t <value> -o <value> [--json] [--flags-dir <value>] [-r <value>] [--api-version
|
|
81
|
+
<value>]
|
|
82
|
+
|
|
83
|
+
FLAGS
|
|
84
|
+
-c, --apex-class=<value> (required) Apex class name to mutate
|
|
85
|
+
-o, --target-org=<value> (required) Username or alias of the target org. Not required if the `target-org`
|
|
86
|
+
configuration variable is already set.
|
|
87
|
+
-r, --report-dir=<value> [default: mutations] Path to the directory where mutation test reports will be generated
|
|
88
|
+
-t, --test-class=<value> (required) Apex test class name to validate mutations
|
|
89
|
+
--api-version=<value> Override the api version used for api requests made by this command
|
|
90
|
+
|
|
91
|
+
GLOBAL FLAGS
|
|
92
|
+
--flags-dir=<value> Import flag values from a directory.
|
|
93
|
+
--json Format output as json.
|
|
94
|
+
|
|
95
|
+
DESCRIPTION
|
|
96
|
+
Evaluate test coverage quality by injecting mutations and measuring test detection rates
|
|
97
|
+
|
|
98
|
+
The Apex Mutation Testing plugin helps evaluate the effectiveness of your Apex test classes by introducing mutations
|
|
99
|
+
into your code and checking if your tests can detect these changes:
|
|
100
|
+
|
|
101
|
+
The plugin provides insights into how trustworthy your test suite is by measuring its ability to catch intentional
|
|
102
|
+
code changes.
|
|
103
|
+
|
|
104
|
+
EXAMPLES
|
|
105
|
+
Run mutation testing on a class with its test file:
|
|
106
|
+
|
|
107
|
+
$ sf apex mutation test run --class-file MyClass --test-file MyClassTest
|
|
108
|
+
```
|
|
109
|
+
<!-- commandsstop -->
|
|
110
|
+
|
|
111
|
+
## Backlog
|
|
112
|
+
|
|
113
|
+
- **Expand Mutation Types**: Add more mutation operators to test different code patterns
|
|
114
|
+
- **Smart Mutation Detection**: Implement logic to identify relevant mutations for specific code contexts
|
|
115
|
+
- **Coverage Analysis**: Detect untested code paths that mutations won't affect
|
|
116
|
+
- **Performance Optimization**: Add CPU time monitoring to fail fast on non ending mutation
|
|
117
|
+
- **Better Configurability**: Pass threashold and use more information from test class
|
|
118
|
+
- **Additional Features**: Explore other mutation testing enhancements and quality metrics
|
|
119
|
+
|
|
120
|
+
## Changelog
|
|
121
|
+
|
|
122
|
+
[changelog.md](CHANGELOG.md) is available for consultation.
|
|
123
|
+
|
|
124
|
+
## Versioning
|
|
125
|
+
|
|
126
|
+
Versioning follows [SemVer](http://semver.org/) specification.
|
|
127
|
+
|
|
128
|
+
## Authors
|
|
129
|
+
|
|
130
|
+
- **Sebastien Colladon** - Developer - [scolladon](https://github.com/scolladon)
|
|
131
|
+
|
|
132
|
+
Special thanks to **Sara Sali** for her [presentation at Dreamforce](https://www.youtube.com/watch?v=8PjzrTaNNns) about apex mutation testing
|
|
133
|
+
This repository is basically a port of her idea / repo to a sf plugin.
|
|
134
|
+
|
|
135
|
+
## Contributing
|
|
136
|
+
|
|
137
|
+
Contributions are what make the trailblazer community such an amazing place. I regard this component as a way to inspire and learn from others. Any contributions you make are **appreciated**.
|
|
138
|
+
|
|
139
|
+
See [contributing.md](CONTRIBUTING.md) for sgd contribution principles.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
This project license is MIT - see the [LICENSE.md](LICENSE.md) file for details
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Connection } from '@salesforce/core';
|
|
2
|
+
import { ApexClass } from '../type/ApexClass.js';
|
|
3
|
+
export declare class ApexClassRepository {
|
|
4
|
+
protected readonly connection: Connection;
|
|
5
|
+
constructor(connection: Connection);
|
|
6
|
+
read(name: string): Promise<{
|
|
7
|
+
[name: string]: import("@jsforce/jsforce-node").SObjectFieldType | null;
|
|
8
|
+
} & import("@jsforce/jsforce-node").Record>;
|
|
9
|
+
update(apexClass: ApexClass): Promise<import("@jsforce/jsforce-node").SaveResult>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export class ApexClassRepository {
|
|
2
|
+
connection;
|
|
3
|
+
constructor(connection) {
|
|
4
|
+
this.connection = connection;
|
|
5
|
+
}
|
|
6
|
+
async read(name) {
|
|
7
|
+
return (await this.connection.tooling
|
|
8
|
+
.sobject('ApexClass')
|
|
9
|
+
.find({ Name: name })
|
|
10
|
+
.execute())[0];
|
|
11
|
+
}
|
|
12
|
+
async update(apexClass) {
|
|
13
|
+
const container = await this.connection.tooling
|
|
14
|
+
.sobject('MetadataContainer')
|
|
15
|
+
.create({
|
|
16
|
+
Name: `MutationTest_${Date.now()}`,
|
|
17
|
+
});
|
|
18
|
+
// Create ApexClassMember for the mutated version
|
|
19
|
+
await this.connection.tooling.sobject('ApexClassMember').create({
|
|
20
|
+
MetadataContainerId: container.id,
|
|
21
|
+
ContentEntityId: apexClass.Id,
|
|
22
|
+
Body: apexClass.Body,
|
|
23
|
+
});
|
|
24
|
+
// Create ContainerAsyncRequest to deploy
|
|
25
|
+
return await this.connection.tooling
|
|
26
|
+
.sobject('ContainerAsyncRequest')
|
|
27
|
+
.create({
|
|
28
|
+
IsCheckOnly: false,
|
|
29
|
+
MetadataContainerId: container.id,
|
|
30
|
+
IsRunTests: true,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=apexClassRepository.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apexClassRepository.js","sourceRoot":"","sources":["../../src/adapter/apexClassRepository.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,mBAAmB;IACC;IAA/B,YAA+B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAElD,KAAK,CAAC,IAAI,CAAC,IAAY;QAC5B,OAAO,CACL,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO;aAC1B,OAAO,CAAC,WAAW,CAAC;aACpB,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;aACpB,OAAO,EAAE,CACb,CAAC,CAAC,CAAC,CAAA;IACN,CAAC;IAEM,KAAK,CAAC,MAAM,CAAC,SAAoB;QACtC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO;aAC5C,OAAO,CAAC,mBAAmB,CAAC;aAC5B,MAAM,CAAC;YACN,IAAI,EAAE,gBAAgB,IAAI,CAAC,GAAG,EAAE,EAAE;SACnC,CAAC,CAAA;QAEJ,iDAAiD;QACjD,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,MAAM,CAAC;YAC9D,mBAAmB,EAAE,SAAS,CAAC,EAAE;YACjC,eAAe,EAAE,SAAS,CAAC,EAAE;YAC7B,IAAI,EAAE,SAAS,CAAC,IAAI;SACrB,CAAC,CAAA;QAEF,yCAAyC;QACzC,OAAO,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO;aACjC,OAAO,CAAC,uBAAuB,CAAC;aAChC,MAAM,CAAC;YACN,WAAW,EAAE,KAAK;YAClB,mBAAmB,EAAE,SAAS,CAAC,EAAE;YACjC,UAAU,EAAE,IAAI;SACjB,CAAC,CAAA;IACN,CAAC;CACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TestResult, TestService } from '@salesforce/apex-node';
|
|
2
|
+
import { Connection } from '@salesforce/core';
|
|
3
|
+
export declare class ApexTestRunner {
|
|
4
|
+
protected readonly testService: TestService;
|
|
5
|
+
constructor(connection: Connection);
|
|
6
|
+
run(className: string): Promise<TestResult>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { TestService } from '@salesforce/apex-node';
|
|
2
|
+
export class ApexTestRunner {
|
|
3
|
+
testService;
|
|
4
|
+
constructor(connection) {
|
|
5
|
+
this.testService = new TestService(connection);
|
|
6
|
+
}
|
|
7
|
+
async run(className) {
|
|
8
|
+
return (await this.testService.runTestAsynchronous({
|
|
9
|
+
tests: [{ className }],
|
|
10
|
+
testLevel: "RunSpecifiedTests" /* TestLevel.RunSpecifiedTests */,
|
|
11
|
+
skipCodeCoverage: true,
|
|
12
|
+
maxFailedTests: 0,
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=apexTestRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apexTestRunner.js","sourceRoot":"","sources":["../../src/adapter/apexTestRunner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAG1E,MAAM,OAAO,cAAc;IACN,WAAW,CAAa;IAC3C,YAAY,UAAsB;QAChC,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,UAAU,CAAC,CAAA;IAChD,CAAC;IAEM,KAAK,CAAC,GAAG,CAAC,SAAiB;QAChC,OAAO,CAAC,MAAM,IAAI,CAAC,WAAW,CAAC,mBAAmB,CAAC;YACjD,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;YACtB,SAAS,uDAA6B;YACtC,gBAAgB,EAAE,IAAI;YACtB,cAAc,EAAE,CAAC;SAClB,CAAC,CAAe,CAAA;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SfCommand } from '@salesforce/sf-plugins-core';
|
|
2
|
+
export type ApexMutationTestResult = {
|
|
3
|
+
score: number;
|
|
4
|
+
};
|
|
5
|
+
export default class ApexMutationTest extends SfCommand<ApexMutationTestResult> {
|
|
6
|
+
static readonly summary: string;
|
|
7
|
+
static readonly description: string;
|
|
8
|
+
static readonly examples: string[];
|
|
9
|
+
static readonly flags: {
|
|
10
|
+
'apex-class': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'test-class': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'report-dir': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'target-org': import("@oclif/core/interfaces").OptionFlag<import("@salesforce/core").Org, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
'api-version': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<ApexMutationTestResult>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Messages } from '@salesforce/core';
|
|
2
|
+
import { Flags, SfCommand } from '@salesforce/sf-plugins-core';
|
|
3
|
+
import { ApexMutationHTMLReporter } from '../../../../reporter/HTMLReporter.js';
|
|
4
|
+
import { MutationTestingService } from '../../../../service/mutationTestingService.js';
|
|
5
|
+
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
|
|
6
|
+
const messages = Messages.loadMessages('apex-mutation-testing', 'apex.mutation.test.run');
|
|
7
|
+
export default class ApexMutationTest extends SfCommand {
|
|
8
|
+
static summary = messages.getMessage('summary');
|
|
9
|
+
static description = messages.getMessage('description');
|
|
10
|
+
static examples = messages.getMessages('examples');
|
|
11
|
+
static flags = {
|
|
12
|
+
'apex-class': Flags.string({
|
|
13
|
+
char: 'c',
|
|
14
|
+
summary: messages.getMessage('flags.apex-class.summary'),
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
'test-class': Flags.string({
|
|
18
|
+
char: 't',
|
|
19
|
+
summary: messages.getMessage('flags.test-class.summary'),
|
|
20
|
+
required: true,
|
|
21
|
+
}),
|
|
22
|
+
'report-dir': Flags.directory({
|
|
23
|
+
char: 'r',
|
|
24
|
+
summary: messages.getMessage('flags.report-dir.summary'),
|
|
25
|
+
exists: true,
|
|
26
|
+
default: 'mutations',
|
|
27
|
+
}),
|
|
28
|
+
'target-org': Flags.requiredOrg(),
|
|
29
|
+
'api-version': Flags.orgApiVersion(),
|
|
30
|
+
};
|
|
31
|
+
async run() {
|
|
32
|
+
// parse the provided flags
|
|
33
|
+
const { flags } = await this.parse(ApexMutationTest);
|
|
34
|
+
const connection = flags['target-org'].getConnection(flags['api-version']);
|
|
35
|
+
this.log(messages.getMessage('info.CommandIsRunning', [
|
|
36
|
+
flags['apex-class'],
|
|
37
|
+
flags['test-class'],
|
|
38
|
+
]));
|
|
39
|
+
const mutationTestingService = new MutationTestingService(this.progress, this.spinner, connection, {
|
|
40
|
+
apexClassName: flags['apex-class'],
|
|
41
|
+
apexClassTestName: flags['test-class'],
|
|
42
|
+
});
|
|
43
|
+
const mutationResult = await mutationTestingService.process();
|
|
44
|
+
const htmlReporter = new ApexMutationHTMLReporter();
|
|
45
|
+
await htmlReporter.generateReport(mutationResult, flags['report-dir']);
|
|
46
|
+
this.log(messages.getMessage('info.reportGenerated', [flags['report-dir']]));
|
|
47
|
+
const score = mutationTestingService.calculateScore(mutationResult);
|
|
48
|
+
this.log(messages.getMessage('info.CommandSuccess', [score]));
|
|
49
|
+
this.info(messages.getMessage('info.EncourageSponsorship'));
|
|
50
|
+
return {
|
|
51
|
+
score,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../../../../src/commands/apex/mutation/test/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseListener.js","sourceRoot":"","sources":["../../src/mutator/baseListener.ts"],"names":[],"mappings":"AAGA,+DAA+D;AAC/D,MAAM,OAAO,YAAY;IACvB,UAAU,GAAmB,EAAE,CAAA;CAChC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { BaseListener } from './baseListener.js';
|
|
2
|
+
import { TerminalNode } from 'antlr4ts/tree/index.js';
|
|
3
|
+
export class BoundaryConditionMutator extends BaseListener {
|
|
4
|
+
REPLACEMENT_MAP = {
|
|
5
|
+
'!=': '==',
|
|
6
|
+
'==': '!=',
|
|
7
|
+
'<': '<=',
|
|
8
|
+
'<=': '<',
|
|
9
|
+
'>': '>=',
|
|
10
|
+
'>=': '>',
|
|
11
|
+
'===': '!==',
|
|
12
|
+
'!==': '===',
|
|
13
|
+
};
|
|
14
|
+
// Target rule
|
|
15
|
+
// expression: expression ('<=' | '>=' | '>' | '<') expression
|
|
16
|
+
enterParExpression(ctx) {
|
|
17
|
+
if (ctx.childCount === 3) {
|
|
18
|
+
const symbol = ctx.getChild(1).getChild(1);
|
|
19
|
+
if (symbol instanceof TerminalNode) {
|
|
20
|
+
const symbolText = symbol.text;
|
|
21
|
+
const replacement = this.REPLACEMENT_MAP[symbolText];
|
|
22
|
+
if (replacement) {
|
|
23
|
+
this._mutations.push({
|
|
24
|
+
mutationName: this.constructor.name,
|
|
25
|
+
token: symbol,
|
|
26
|
+
replacement,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=boundaryConditionMutator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundaryConditionMutator.js","sourceRoot":"","sources":["../../src/mutator/boundaryConditionMutator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAErD,MAAM,OAAO,wBAAyB,SAAQ,YAAY;IAChD,eAAe,GAA2B;QAChD,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,IAAI;QACV,GAAG,EAAE,IAAI;QACT,IAAI,EAAE,GAAG;QACT,GAAG,EAAE,IAAI;QACT,IAAI,EAAE,GAAG;QACT,KAAK,EAAE,KAAK;QACZ,KAAK,EAAE,KAAK;KACb,CAAA;IAED,cAAc;IACd,8DAA8D;IAC9D,kBAAkB,CAAC,GAAsB;QACvC,IAAI,GAAG,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;YAC1C,IAAI,MAAM,YAAY,YAAY,EAAE,CAAC;gBACnC,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAA;gBAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,eAAe,CAAC,UAAU,CAAC,CAAA;gBACpD,IAAI,WAAW,EAAE,CAAC;oBAChB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;wBACnB,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI;wBACnC,KAAK,EAAE,MAAM;wBACb,WAAW;qBACZ,CAAC,CAAA;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { BaseListener } from './baseListener.js';
|
|
2
|
+
import { ParserRuleContext } from 'antlr4ts';
|
|
3
|
+
export declare class IncrementMutator extends BaseListener {
|
|
4
|
+
private REPLACEMENT_MAP;
|
|
5
|
+
enterPostOpExpression(ctx: ParserRuleContext): void;
|
|
6
|
+
enterPreOpExpression(ctx: ParserRuleContext): void;
|
|
7
|
+
private processOperation;
|
|
8
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BaseListener } from './baseListener.js';
|
|
2
|
+
import { TerminalNode } from 'antlr4ts/tree/index.js';
|
|
3
|
+
export class IncrementMutator extends BaseListener {
|
|
4
|
+
REPLACEMENT_MAP = {
|
|
5
|
+
'++': '--',
|
|
6
|
+
'--': '++',
|
|
7
|
+
};
|
|
8
|
+
// Target rule
|
|
9
|
+
// expression :
|
|
10
|
+
// | expression ('++' | '--')
|
|
11
|
+
// | ('+' | '-' | '++' | '--') expression
|
|
12
|
+
enterPostOpExpression(ctx) {
|
|
13
|
+
this.processOperation(ctx);
|
|
14
|
+
}
|
|
15
|
+
enterPreOpExpression(ctx) {
|
|
16
|
+
this.processOperation(ctx);
|
|
17
|
+
}
|
|
18
|
+
processOperation(ctx) {
|
|
19
|
+
if (ctx.childCount === 2) {
|
|
20
|
+
let symbol = null;
|
|
21
|
+
if (ctx.getChild(0) instanceof TerminalNode) {
|
|
22
|
+
symbol = ctx.getChild(0);
|
|
23
|
+
}
|
|
24
|
+
else if (ctx.getChild(1) instanceof TerminalNode) {
|
|
25
|
+
symbol = ctx.getChild(1);
|
|
26
|
+
}
|
|
27
|
+
if (symbol !== null && symbol.text in this.REPLACEMENT_MAP) {
|
|
28
|
+
this._mutations.push({
|
|
29
|
+
mutationName: this.constructor.name,
|
|
30
|
+
token: symbol,
|
|
31
|
+
replacement: this.REPLACEMENT_MAP[symbol.text],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=incrementMutator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incrementMutator.js","sourceRoot":"","sources":["../../src/mutator/incrementMutator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAErD,MAAM,OAAO,gBAAiB,SAAQ,YAAY;IACxC,eAAe,GAA2B;QAChD,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,IAAI;KACX,CAAA;IAED,cAAc;IACd,eAAe;IACf,8BAA8B;IAC9B,0CAA0C;IAC1C,qBAAqB,CAAC,GAAsB;QAC1C,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAA;IAC5B,CAAC;IAED,oBAAoB,CAAC,GAAsB;QACzC,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAA;IAC5B,CAAC;IAEO,gBAAgB,CAAC,GAAsB;QAC7C,IAAI,GAAG,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;YACzB,IAAI,MAAM,GAAwB,IAAI,CAAA;YACtC,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,YAAY,EAAE,CAAC;gBAC5C,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAiB,CAAA;YAC1C,CAAC;iBAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,YAAY,YAAY,EAAE,CAAC;gBACnD,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAiB,CAAA;YAC1C,CAAC;YAED,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBAC3D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;oBACnB,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI;oBACnC,KAAK,EAAE,MAAM;oBACb,WAAW,EAAE,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,IAAI,CAAC;iBAC/C,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ApexMutation } from '../type/ApexMutation.js';
|
|
2
|
+
import { BaseListener } from './baseListener.js';
|
|
3
|
+
import { ApexParserListener } from 'apex-parser';
|
|
4
|
+
export declare class MutationListener implements ApexParserListener {
|
|
5
|
+
private listeners;
|
|
6
|
+
_mutations: ApexMutation[];
|
|
7
|
+
getMutations(): ApexMutation[];
|
|
8
|
+
constructor(listeners: BaseListener[]);
|
|
9
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// @ts-ignore: Just a proxy doing accumulation of mutations
|
|
2
|
+
export class MutationListener {
|
|
3
|
+
listeners;
|
|
4
|
+
_mutations = [];
|
|
5
|
+
getMutations() {
|
|
6
|
+
return this._mutations;
|
|
7
|
+
}
|
|
8
|
+
constructor(listeners) {
|
|
9
|
+
this.listeners = listeners;
|
|
10
|
+
// Share mutations array across all listeners
|
|
11
|
+
this.listeners
|
|
12
|
+
.filter(listener => '_mutations' in listener)
|
|
13
|
+
.forEach(listener => {
|
|
14
|
+
;
|
|
15
|
+
listener._mutations = this._mutations;
|
|
16
|
+
});
|
|
17
|
+
// Create a proxy that automatically forwards all method calls to listeners
|
|
18
|
+
return new Proxy(this, {
|
|
19
|
+
get: (target, prop) => {
|
|
20
|
+
if (prop in target) {
|
|
21
|
+
return target[prop];
|
|
22
|
+
}
|
|
23
|
+
// Return a function that calls the method on all listeners that have it
|
|
24
|
+
return (...args) => {
|
|
25
|
+
this.listeners.forEach(listener => {
|
|
26
|
+
if (prop in listener && typeof listener[prop] === 'function') {
|
|
27
|
+
;
|
|
28
|
+
listener[prop].apply(listener, args);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=mutationListener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutationListener.js","sourceRoot":"","sources":["../../src/mutator/mutationListener.ts"],"names":[],"mappings":"AAKA,2DAA2D;AAC3D,MAAM,OAAO,gBAAgB;IACnB,SAAS,CAAgB;IACjC,UAAU,GAAmB,EAAE,CAAA;IAExB,YAAY;QACjB,OAAO,IAAI,CAAC,UAAU,CAAA;IACxB,CAAC;IAED,YAAY,SAAyB;QACnC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAA;QAC1B,6CAA6C;QAC7C,IAAI,CAAC,SAAS;aACX,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,YAAY,IAAI,QAAQ,CAAC;aAC5C,OAAO,CAAC,QAAQ,CAAC,EAAE;YAClB,CAAC;YAAC,QAAyB,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QAC1D,CAAC,CAAC,CAAA;QAEJ,2EAA2E;QAC3E,OAAO,IAAI,KAAK,CAAC,IAAI,EAAE;YACrB,GAAG,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;gBACpB,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;oBACnB,OAAO,MAAM,CAAC,IAAI,CAAC,CAAA;gBACrB,CAAC;gBAED,wEAAwE;gBACxE,OAAO,CAAC,GAAG,IAAQ,EAAE,EAAE;oBACrB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE;wBAChC,IAAI,IAAI,IAAI,QAAQ,IAAI,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,UAAU,EAAE,CAAC;4BAC7D,CAAC;4BAAC,QAAQ,CAAC,IAAI,CAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;wBACrD,CAAC;oBACH,CAAC,CAAC,CAAA;gBACJ,CAAC,CAAA;YACH,CAAC;SACF,CAAC,CAAA;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
export class ApexMutationHTMLReporter {
|
|
4
|
+
async generateReport(apexMutationTestResult, outputDir = 'reports') {
|
|
5
|
+
const reportData = this.transformApexResults(apexMutationTestResult);
|
|
6
|
+
// Generate and write the HTML file with the report data embedded
|
|
7
|
+
const htmlContent = createReportHtml(reportData);
|
|
8
|
+
await writeFile(path.join(outputDir, 'index.html'), htmlContent);
|
|
9
|
+
}
|
|
10
|
+
transformApexResults(apexMutationTestResult) {
|
|
11
|
+
const mutationTestResult = {
|
|
12
|
+
schemaVersion: '2.0.0',
|
|
13
|
+
config: {}, // You can add your configuration here
|
|
14
|
+
thresholds: {
|
|
15
|
+
high: 80,
|
|
16
|
+
low: 60,
|
|
17
|
+
},
|
|
18
|
+
files: {},
|
|
19
|
+
};
|
|
20
|
+
const fileResult = {
|
|
21
|
+
language: 'java',
|
|
22
|
+
source: apexMutationTestResult.sourceFileContent,
|
|
23
|
+
mutants: apexMutationTestResult.mutants.map(mutant => ({
|
|
24
|
+
id: mutant.id,
|
|
25
|
+
mutatorName: mutant.mutatorName,
|
|
26
|
+
replacement: mutant.replacement,
|
|
27
|
+
status: mutant.status,
|
|
28
|
+
static: false,
|
|
29
|
+
coveredBy: ['0'],
|
|
30
|
+
killedBy: mutant.status === 'Killed' ? ['0'] : undefined,
|
|
31
|
+
testsCompleted: 1,
|
|
32
|
+
location: {
|
|
33
|
+
start: {
|
|
34
|
+
line: mutant.location.start.line,
|
|
35
|
+
column: mutant.location.start.column,
|
|
36
|
+
},
|
|
37
|
+
end: {
|
|
38
|
+
line: mutant.location.end.line,
|
|
39
|
+
column: mutant.location.end.column,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
})),
|
|
43
|
+
};
|
|
44
|
+
mutationTestResult.files[`${apexMutationTestResult.sourceFile}.java`] =
|
|
45
|
+
fileResult;
|
|
46
|
+
return mutationTestResult;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const createReportHtml = report => {
|
|
50
|
+
return `<!DOCTYPE html>
|
|
51
|
+
<html>
|
|
52
|
+
<head>
|
|
53
|
+
<meta charset="utf-8">
|
|
54
|
+
<script src="https://cdn.jsdelivr.net/npm/mutation-testing-elements@3.5.1/dist/mutation-test-elements.min.js"></script>
|
|
55
|
+
</head>
|
|
56
|
+
<body>
|
|
57
|
+
<mutation-test-report-app titlePostfix="apex-mutation-testing">
|
|
58
|
+
Your browser doesn't support <a href="https://caniuse.com/#search=custom%20elements">custom elements</a>.
|
|
59
|
+
Please use a latest version of an evergreen browser (Firefox, Chrome, Safari, Opera, Edge, etc).
|
|
60
|
+
</mutation-test-report-app>
|
|
61
|
+
<script>
|
|
62
|
+
const app = document.querySelector('mutation-test-report-app');
|
|
63
|
+
app.report = ${escapeHtmlTags(JSON.stringify(report))};
|
|
64
|
+
</script>
|
|
65
|
+
</body>
|
|
66
|
+
</html>`;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Escapes the HTML tags inside strings in a JSON input by breaking them apart.
|
|
70
|
+
*/
|
|
71
|
+
function escapeHtmlTags(json) {
|
|
72
|
+
const j = json.replace(/</g, '<"+"');
|
|
73
|
+
return j;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=HTMLReporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HTMLReporter.js","sourceRoot":"","sources":["../../src/reporter/HTMLReporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAG5B,MAAM,OAAO,wBAAwB;IACnC,KAAK,CAAC,cAAc,CAClB,sBAA8C,EAC9C,YAAoB,SAAS;QAE7B,MAAM,UAAU,GAAG,IAAI,CAAC,oBAAoB,CAAC,sBAAsB,CAAC,CAAA;QACpE,iEAAiE;QACjE,MAAM,WAAW,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAA;QAChD,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,WAAW,CAAC,CAAA;IAClE,CAAC;IAEO,oBAAoB,CAAC,sBAA8C;QACzE,MAAM,kBAAkB,GAAG;YACzB,aAAa,EAAE,OAAO;YACtB,MAAM,EAAE,EAAE,EAAE,sCAAsC;YAClD,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE;gBACR,GAAG,EAAE,EAAE;aACR;YACD,KAAK,EAAE,EAAE;SACV,CAAA;QAED,MAAM,UAAU,GAAG;YACjB,QAAQ,EAAE,MAAM;YAChB,MAAM,EAAE,sBAAsB,CAAC,iBAAiB;YAChD,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;gBACrD,EAAE,EAAE,MAAM,CAAC,EAAE;gBACb,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,WAAW,EAAE,MAAM,CAAC,WAAW;gBAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,MAAM,EAAE,KAAK;gBACb,SAAS,EAAE,CAAC,GAAG,CAAC;gBAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;gBACxD,cAAc,EAAE,CAAC;gBACjB,QAAQ,EAAE;oBACR,KAAK,EAAE;wBACL,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI;wBAChC,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM;qBACrC;oBACD,GAAG,EAAE;wBACH,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI;wBAC9B,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM;qBACnC;iBACF;aACF,CAAC,CAAC;SACJ,CAAA;QAED,kBAAkB,CAAC,KAAK,CAAC,GAAG,sBAAsB,CAAC,UAAU,OAAO,CAAC;YACnE,UAAU,CAAA;QAEZ,OAAO,kBAAkB,CAAA;IAC3B,CAAC;CACF;AAED,MAAM,gBAAgB,GAAG,MAAM,CAAC,EAAE;IAChC,OAAO;;;;;;;;;;;;;qBAaY,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;;;UAGjD,CAAA;AACV,CAAC,CAAA;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,IAAY;IAClC,MAAM,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;IACpC,OAAO,CAAC,CAAA;AACV,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { BoundaryConditionMutator } from '../mutator/boundaryConditionMutator.js';
|
|
2
|
+
import { IncrementMutator } from '../mutator/incrementMutator.js';
|
|
3
|
+
import { MutationListener } from '../mutator/mutationListener.js';
|
|
4
|
+
import { ApexLexer, ApexParser, CaseInsensitiveInputStream, CommonTokenStream, ParseTreeWalker, } from 'apex-parser';
|
|
5
|
+
import { TokenStreamRewriter } from 'antlr4ts';
|
|
6
|
+
export class MutantGenerator {
|
|
7
|
+
tokenStream;
|
|
8
|
+
compute(classContent) {
|
|
9
|
+
const lexer = new ApexLexer(new CaseInsensitiveInputStream('other', classContent));
|
|
10
|
+
this.tokenStream = new CommonTokenStream(lexer);
|
|
11
|
+
const parser = new ApexParser(this.tokenStream);
|
|
12
|
+
const tree = parser.compilationUnit();
|
|
13
|
+
const incrementListener = new IncrementMutator();
|
|
14
|
+
const boundaryListener = new BoundaryConditionMutator();
|
|
15
|
+
const listener = new MutationListener([incrementListener, boundaryListener]);
|
|
16
|
+
ParseTreeWalker.DEFAULT.walk(listener, tree);
|
|
17
|
+
return listener.getMutations();
|
|
18
|
+
}
|
|
19
|
+
mutate(mutation) {
|
|
20
|
+
// Create a new token stream rewriter
|
|
21
|
+
const rewriter = new TokenStreamRewriter(this.tokenStream);
|
|
22
|
+
// Apply the mutation by replacing the original token with the replacement text
|
|
23
|
+
rewriter.replace(mutation.token.symbol.tokenIndex, mutation.token.symbol.tokenIndex, mutation.replacement);
|
|
24
|
+
// Get the mutated code
|
|
25
|
+
return rewriter.getText();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=mutantGenerator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mutantGenerator.js","sourceRoot":"","sources":["../../src/service/mutantGenerator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AACjF,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AAEjE,OAAO,EACL,SAAS,EACT,UAAU,EAEV,0BAA0B,EAC1B,iBAAiB,EACjB,eAAe,GAChB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AAG9C,MAAM,OAAO,eAAe;IAClB,WAAW,CAAoB;IAChC,OAAO,CAAC,YAAoB;QACjC,MAAM,KAAK,GAAG,IAAI,SAAS,CACzB,IAAI,0BAA0B,CAAC,OAAO,EAAE,YAAY,CAAC,CACtD,CAAA;QACD,IAAI,CAAC,WAAW,GAAG,IAAI,iBAAiB,CAAC,KAAK,CAAC,CAAA;QAC/C,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAC/C,MAAM,IAAI,GAAG,MAAM,CAAC,eAAe,EAAE,CAAA;QAErC,MAAM,iBAAiB,GAAG,IAAI,gBAAgB,EAAE,CAAA;QAChD,MAAM,gBAAgB,GAAG,IAAI,wBAAwB,EAAE,CAAA;QAEvD,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,CAAC,CAAA;QAE5E,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,QAA8B,EAAE,IAAI,CAAC,CAAA;QAElE,OAAO,QAAQ,CAAC,YAAY,EAAE,CAAA;IAChC,CAAC;IAEM,MAAM,CAAC,QAAsB;QAClC,qCAAqC;QACrC,MAAM,QAAQ,GAAG,IAAI,mBAAmB,CAAC,IAAI,CAAC,WAAY,CAAC,CAAA;QAC3D,+EAA+E;QAC/E,QAAQ,CAAC,OAAO,CACd,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAChC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,UAAU,EAChC,QAAQ,CAAC,WAAW,CACrB,CAAA;QAED,uBAAuB;QACvB,OAAO,QAAQ,CAAC,OAAO,EAAE,CAAA;IAC3B,CAAC;CACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Connection } from '@salesforce/core';
|
|
2
|
+
import { Progress, Spinner } from '@salesforce/sf-plugins-core';
|
|
3
|
+
import { ApexMutationTestResult } from '../type/ApexMutationTestResult.js';
|
|
4
|
+
export declare class MutationTestingService {
|
|
5
|
+
protected readonly progress: Progress;
|
|
6
|
+
protected readonly spinner: Spinner;
|
|
7
|
+
protected readonly connection: Connection;
|
|
8
|
+
protected readonly apexClassName: string;
|
|
9
|
+
protected readonly apexClassTestName: string;
|
|
10
|
+
constructor(progress: Progress, spinner: Spinner, connection: Connection, { apexClassName, apexClassTestName, }: {
|
|
11
|
+
apexClassName: string;
|
|
12
|
+
apexClassTestName: string;
|
|
13
|
+
});
|
|
14
|
+
process(): Promise<ApexMutationTestResult>;
|
|
15
|
+
calculateScore(mutationResult: ApexMutationTestResult): number;
|
|
16
|
+
private buildMutantResult;
|
|
17
|
+
}
|