Skip to content

Commit

Permalink
feat: Added mappaths command
Browse files Browse the repository at this point in the history
  • Loading branch information
jdkgabri committed Aug 9, 2022
1 parent 512fb79 commit fecb086
Show file tree
Hide file tree
Showing 6 changed files with 6,233 additions and 6,027 deletions.
56 changes: 43 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- [Prerequisites](#prerequisites)
- [Installation](#installation)
- [How to use it?](#how-to-use-it)
- [`sfdx nps:coverage:formatters:mappaths -p <filepath> -t cobertura [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-npscoverageformattersmappaths--p-filepath--t-cobertura---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal)
- [`sfdx nps:coverage:verify -p <filepath> [-r <number>] -c <string> [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-npscoverageverify--p-filepath--r-number--c-string---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal)
- [Walkthrough](#walkthrough)
- [Get a folder with all the files](#get-a-folder-with-all-the-files)
Expand Down Expand Up @@ -68,23 +69,23 @@ If you run your CI/CD jobs inside a Docker image, you can add the plugin to your
## How to use it?

<!-- commands -->
* [`sfdx nps:coverage:formatters:mappaths -p <filepath> [-t cobertura] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-npscoverageformattersmappaths--p-filepath--t-cobertura---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal)
* [`sfdx nps:coverage:formatters:mappaths -p <filepath> -t cobertura [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-npscoverageformattersmappaths--p-filepath--t-cobertura---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal)
* [`sfdx nps:coverage:verify -p <filepath> -c <array> [-r <integer>] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`](#sfdx-npscoverageverify--p-filepath--c-array--r-integer---json---loglevel-tracedebuginfowarnerrorfataltracedebuginfowarnerrorfatal)

## `sfdx nps:coverage:formatters:mappaths -p <filepath> [-t cobertura] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`
## `sfdx nps:coverage:formatters:mappaths -p <filepath> -t cobertura [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`

Map the paths in a given file to replace them with the actual project relative location for classes and triggers

```
USAGE
$ sfdx nps:coverage:formatters:mappaths -p <filepath> [-t cobertura] [--json] [--loglevel
$ sfdx nps:coverage:formatters:mappaths -p <filepath> -t cobertura [--json] [--loglevel
trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]
FLAGS
-p, --path=<value> (required) project relative path to
the file containing the test
execution results
-t, --type=(cobertura) format type of the file
-t, --type=(cobertura) (required) format type of the file
--json format output as json
--loglevel=(trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL) [default: warn] logging level for
this command invocation
Expand All @@ -96,7 +97,7 @@ EXAMPLES
$ sfdx nps:coverage:formatters:mappaths --path test-results/coverage/cobertura.xml --type cobertura
```

_See code: [src/commands/nps/coverage/formatters/mappaths.ts](https://github.com/Nakama-Partnering-Services/nakama-plugin-sfdx/blob/v1.0.3/src/commands/nps/coverage/formatters/mappaths.ts)_
_See code: [src/commands/nps/coverage/formatters/mappaths.ts](https://github.com/Nakama-Partnering-Services/nakama-plugin-sfdx/blob/v1.0.4/src/commands/nps/coverage/formatters/mappaths.ts)_

## `sfdx nps:coverage:verify -p <filepath> -c <array> [-r <integer>] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]`

Expand Down Expand Up @@ -127,7 +128,7 @@ EXAMPLES
$ sfdx nps:coverage:verify --path test-results/results.json --required-coverage 90 --classes AccountTriggerHandler,ContactTriggerHandler
```

_See code: [src/commands/nps/coverage/verify.ts](https://github.com/Nakama-Partnering-Services/nakama-plugin-sfdx/blob/v1.0.3/src/commands/nps/coverage/verify.ts)_
_See code: [src/commands/nps/coverage/verify.ts](https://github.com/Nakama-Partnering-Services/nakama-plugin-sfdx/blob/v1.0.4/src/commands/nps/coverage/verify.ts)_
<!-- commandsstop -->

## Walkthrough
Expand All @@ -138,16 +139,17 @@ Let’s take a look at the following scenario:
In our example, we have the following files:

- _Custom Field added:_ Account.NumberOfContacts\_\_c
- _Apex Class added:_ ContactTriggerHandler
- _Apex Class added:_ ContactTriggerHandlerTest
- _Apex Class modified:_ AccountTriggerHandler
- _Apex Class modified:_ AccountTriggerHandlerTest
- _Custom Field added:_ `Account.NumberOfContacts__c`
- _Apex Class added:_ `ContactTriggerHandler`
- _Apex Class added:_ `ContactTriggerHandlerTest`
- _Apex Class modified:_ `AccountTriggerHandler`
- _Apex Class modified:_ `AccountTriggerHandlerTest`

In this situation, we would expect the CI pipeline to:

1. **Detect the relevant apex classes in the PR to verify**: `ContactTriggerHandler`, `AccountTriggerHandler`
2. **Report an error for those classes without enough test coverage**: `AccountTriggerHandler`
3. **Optional: if using Gitlab CI, highglight coverage in MR diff changes for**: `ContactTriggerHandler`, `AccountTriggerHandler`

So let’s do it!

Expand All @@ -170,7 +172,29 @@ which means:
The simplest option to deploy the incremental changes is to use `force:source:deploy` command with `-x` parameter:

```sh
sfdx force:source:deploy --wait 60 --checkonly --manifest deltas/package/package.xml --postdestructivechanges deltas/destructiveChanges/destructiveChanges.xml --verbose --testlevel RunLocalTests --json > test-results/results.json
sfdx force:source:deploy --wait 60 --checkonly --manifest deltas/package/package.xml --postdestructivechanges deltas/destructiveChanges/destructiveChanges.xml --verbose --testlevel RunLocalTests --coverageformatters cobertura --resultsdir test-results --json > test-results/results.json
```

### Optional, if using Gitlab CI: remap coverage formatter file with actual project locations paths for apex files

Due to the apex files actually belonging to an org, the coverage formatters uses apex files paths with a `no-map/` default path. We can remap them properly to the real paths for the actual files location in our project with:


```sh
sfdx nps:coverage:formatters:mappaths -p test-results/cobertura.xml -t cobertura
```

> :warning: Currently there is a limitation where an issue will likely happen if there are `.cls` and `.trigger` files with the same name
(Bonus) Make sure that you specify the following in your deployment job:

```yml
artifacts:
when: always
reports:
coverage_report:
coverage_format: cobertura
path: test-results/coverage/cobertura.xml
```
### Recommended: Print deployment result
Expand All @@ -195,14 +219,20 @@ Imagine that you want all of our apex classes to have at least a 90% of test cov
```sh
sfdx nps:coverage:verify -p test-results/results.json -r 90 -c $NON_TEST_CLASSES
```
And voilà! 🥳
We should get and output like:
```sh
List of analyzed apex classes with coverage:
ContactTriggerHandler: 92%
AccountTriggerHandler: 68%
ERROR running nps:coverage:verify: Included apex classes should met at least the required coverage of 90%. Classes without enough coverage: AccountTriggerHandler
```
And voilà! 🥳
Besides, if using Gitlab CI and followe the optional steps, in our MR diff changes we should be able to spot the [Test Coverage Visualization](https://docs.gitlab.com/ee/ci/testing/test_coverage_visualization.html).
## Versioning
Expand Down
1 change: 1 addition & 0 deletions messages/mappaths.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
"commandDescription": "Map the paths in a given file to replace them with the actual project relative location for classes and triggers",
"pathToFileFlagDescription": "project relative path to the file containing the test execution results",
"typeFlagDescription": "format type of the file",
"errorNoApexFiles": "No apex files have being found in the project directory. Make sure this command runs at a sfdx project level and it contains the relevant apex files affected by the test execution",
"examples": ["sfdx nps:coverage:formatters:mappaths --path test-results/coverage/cobertura.xml --type cobertura"]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "nakama-plugin-sfdx",
"description": "This plugin provides commands to work with the coverage files generated by other commands such as sfdx force:source:deploy or sfdx force:apex:test:run",
"version": "1.0.3",
"version": "1.0.4",
"author": "Gabriel Serrano @jdkgabri",
"bugs": "https://github.com/Nakama-Partnering-Services/nakama-plugin-sfdx/issues",
"dependencies": {
Expand Down
41 changes: 24 additions & 17 deletions src/commands/nps/coverage/formatters/mappaths.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as os from 'os';
import { flags, SfdxCommand } from '@salesforce/command';
import { Messages } from '@salesforce/core'; // SfError
import { Messages, SfError } from '@salesforce/core';
import { AnyJson } from '@salesforce/ts-types';

import { promisify } from 'util';
Expand All @@ -15,32 +15,35 @@ Messages.importMessagesDirectory(__dirname);

const messages = Messages.loadMessages('nakama-plugin-sfdx', 'mappaths');

const parseXml = (xmlFile) => {
const parseXml = (xmlContent) => {
return new XMLParser({
ignoreDeclaration: true,
attributeNamePrefix: '@_',
ignoreAttributes: false
}).parse(xmlFile);
}).parse(xmlContent);
};

const buildXml = (obj) => {
const buildXml = (jsonObject) => {
return new XMLBuilder({
format: true,
attributeNamePrefix: '@_',
ignoreAttributes: false
}).build(obj);
}).build(jsonObject);
};

const remap = async (obj) => {
const allClasses = await globPromise('sfdx-source/**/*.{cls,trigger}');
const remap = async (coverageContent) => {
const allApexFiles = await globPromise('sfdx-source/**/*.{cls,trigger}');

for (const file of obj.coverage.packages.package.classes.class) {
if (!allApexFiles.length) {
throw new SfError(messages.getMessage('errorNoApexFiles'), 'No apex files');
}

for (const file of coverageContent.coverage.packages.package.classes.class) {
const fileName = file['@_filename'];
console.log(fileName);
const fullPath = allClasses.filter(
// Warning: an issue may happen if an apex class and an apex trigger have the same name.
const fullPath = allApexFiles.filter(
(item) => item.endsWith(fileName + '.cls') || item.endsWith(fileName + '.trigger')
)[0];
console.log(fullPath);

file['@_filename'] = fullPath;
}
Expand All @@ -62,20 +65,24 @@ export default class Mappaths extends SfdxCommand {
type: flags.enum({
char: 't',
description: messages.getMessage('typeFlagDescription'),
required: true,
options: ['cobertura'] // clover, html-spa, html, json, json-summary, lcovonly, none, teamcity, text, text-summary
})
};

public async run(): Promise<AnyJson> {
const cobertura = readFileSync(this.flags.path, { encoding: 'utf-8' });
const regex = /filename="no-map[\\|/]/g
const coberturaReplaced = cobertura.replaceAll(regex, 'filename="');
const fileContent = readFileSync(this.flags.path, { encoding: 'utf-8' });

// if (this.flags.type !== 'cobertura') return; // Currenly only cobertura is suported, enforced by enum

const regex = /filename="no-map[\\|/]/g;
const replacedContent = fileContent.replaceAll(regex, 'filename="');

const output = parseXml(coberturaReplaced);
const jsonContent = parseXml(replacedContent);

await remap(output);
await remap(jsonContent);

const xmlContent = buildXml(output);
const xmlContent = buildXml(jsonContent);

writeFileSync(this.flags.path, xmlContent);

Expand Down
6 changes: 4 additions & 2 deletions src/commands/nps/coverage/verify.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Note: currently it only supports verification of coverage for apex classes, but not apex triggers nor flows.

import * as os from 'os';
import { flags, SfdxCommand } from '@salesforce/command';
import { Messages, SfError } from '@salesforce/core';
Expand Down Expand Up @@ -46,8 +48,8 @@ export default class Verify extends SfdxCommand {
.filter((item) => item.percentage < coverage)
.map((item) => item.class);

if (classesNotCovered.length > 0) {
throw new SfError(messages.getMessage('errorClassesNotCovered', [coverage, classesNotCovered.join(', ')]));
if (classesNotCovered.length) {
throw new SfError(messages.getMessage('errorClassesNotCovered', [coverage, classesNotCovered.join(', ')]), 'ClaasesNotCovered');
}

this.ux.log(messages.getMessage('noClassesWithInsufficientCoverage'));
Expand Down
Loading

0 comments on commit fecb086

Please sign in to comment.