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.
Files changed (45) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +143 -0
  3. package/lib/adapter/apexClassRepository.d.ts +10 -0
  4. package/lib/adapter/apexClassRepository.js +34 -0
  5. package/lib/adapter/apexClassRepository.js.map +1 -0
  6. package/lib/adapter/apexTestRunner.d.ts +7 -0
  7. package/lib/adapter/apexTestRunner.js +16 -0
  8. package/lib/adapter/apexTestRunner.js.map +1 -0
  9. package/lib/commands/apex/mutation/test/run.d.ts +17 -0
  10. package/lib/commands/apex/mutation/test/run.js +55 -0
  11. package/lib/commands/apex/mutation/test/run.js.map +1 -0
  12. package/lib/mutator/baseListener.d.ts +5 -0
  13. package/lib/mutator/baseListener.js +5 -0
  14. package/lib/mutator/baseListener.js.map +1 -0
  15. package/lib/mutator/boundaryConditionMutator.d.ts +6 -0
  16. package/lib/mutator/boundaryConditionMutator.js +33 -0
  17. package/lib/mutator/boundaryConditionMutator.js.map +1 -0
  18. package/lib/mutator/incrementMutator.d.ts +8 -0
  19. package/lib/mutator/incrementMutator.js +37 -0
  20. package/lib/mutator/incrementMutator.js.map +1 -0
  21. package/lib/mutator/mutationListener.d.ts +9 -0
  22. package/lib/mutator/mutationListener.js +36 -0
  23. package/lib/mutator/mutationListener.js.map +1 -0
  24. package/lib/reporter/HTMLReporter.d.ts +5 -0
  25. package/lib/reporter/HTMLReporter.js +75 -0
  26. package/lib/reporter/HTMLReporter.js.map +1 -0
  27. package/lib/service/mutantGenerator.d.ts +6 -0
  28. package/lib/service/mutantGenerator.js +28 -0
  29. package/lib/service/mutantGenerator.js.map +1 -0
  30. package/lib/service/mutationTestingService.d.ts +17 -0
  31. package/lib/service/mutationTestingService.js +113 -0
  32. package/lib/service/mutationTestingService.js.map +1 -0
  33. package/lib/type/ApexClass.d.ts +4 -0
  34. package/lib/type/ApexClass.js +2 -0
  35. package/lib/type/ApexClass.js.map +1 -0
  36. package/lib/type/ApexMutation.d.ts +6 -0
  37. package/lib/type/ApexMutation.js +2 -0
  38. package/lib/type/ApexMutation.js.map +1 -0
  39. package/lib/type/ApexMutationTestResult.d.ts +22 -0
  40. package/lib/type/ApexMutationTestResult.js +2 -0
  41. package/lib/type/ApexMutationTestResult.js.map +1 -0
  42. package/messages/apex.mutation.test.run.md +49 -0
  43. package/npm-shrinkwrap.json +13119 -0
  44. package/oclif.manifest.json +119 -0
  45. 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
+ [![NPM](https://img.shields.io/npm/v/apex-mutation-testing.svg?label=apex-mutation-testing)](https://www.npmjs.com/package/apex-mutation-testing) [![Downloads/week](https://img.shields.io/npm/dw/apex-mutation-testing.svg)](https://npmjs.org/package/apex-mutation-testing) [![License](https://img.shields.io/badge/License-BSD%203--Clause-brightgreen.svg)](https://raw.githubusercontent.com/salesforcecli/apex-mutation-testing/main/LICENSE.txt)
4
+ ![GitHub Sponsors](https://img.shields.io/github/sponsors/scolladon)
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,5 @@
1
+ import { ApexParserListener } from 'apex-parser';
2
+ import { ApexMutation } from '../type/ApexMutation.js';
3
+ export declare class BaseListener implements ApexParserListener {
4
+ _mutations: ApexMutation[];
5
+ }
@@ -0,0 +1,5 @@
1
+ // @ts-ignore: Base type with just a common _mutations property
2
+ export class BaseListener {
3
+ _mutations = [];
4
+ }
5
+ //# sourceMappingURL=baseListener.js.map
@@ -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,6 @@
1
+ import { BaseListener } from './baseListener.js';
2
+ import { ParserRuleContext } from 'antlr4ts';
3
+ export declare class BoundaryConditionMutator extends BaseListener {
4
+ private REPLACEMENT_MAP;
5
+ enterParExpression(ctx: ParserRuleContext): void;
6
+ }
@@ -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,5 @@
1
+ import { ApexMutationTestResult } from '../type/ApexMutationTestResult.js';
2
+ export declare class ApexMutationHTMLReporter {
3
+ generateReport(apexMutationTestResult: ApexMutationTestResult, outputDir?: string): Promise<void>;
4
+ private transformApexResults;
5
+ }
@@ -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,6 @@
1
+ import { ApexMutation } from '../type/ApexMutation.js';
2
+ export declare class MutantGenerator {
3
+ private tokenStream?;
4
+ compute(classContent: string): ApexMutation[];
5
+ mutate(mutation: ApexMutation): string;
6
+ }
@@ -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
+ }