Skip to content

Commit

Permalink
fix!: remove covered line adjustment in deploy coverage reports
Browse files Browse the repository at this point in the history
  • Loading branch information
mcarvin8 committed Dec 14, 2024
1 parent cba9a8c commit 9cc164f
Show file tree
Hide file tree
Showing 9 changed files with 70 additions and 233 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ sf plugins install apex-code-coverage-transformer@x.y.z

## Who is the Plugin For?

This plugin is intended for users who deploy their Apex codebase (Apex classes and triggers) from any Salesforce DX repository (`sfdx-project.json` file), not just git-based ones. You should be running this plugin somewhere inside your Salesforce DX repository (root folder preferred). This plugin searches for your repository's `sfdx-project.json` file to know which package directories to search into. Since SonarQube relies on file-paths to map code coverage to the files in their explorer interface, the Apex files must be found in one of your package directories.
This plugin is intended for users who deploy their Apex codebase (Apex classes and triggers) from any Salesforce DX repository (`sfdx-project.json` file), not just git-based ones. You should be running this plugin somewhere inside your Salesforce DX repository (root folder preferred). This plugin searches for your repository's `sfdx-project.json` file to know which package directories to search into. Since SonarQube relies on file-paths to map code coverage to the files in their explorer interface, the Apex files must be found in one of your package directories.

This plugin will work if you run local tests or run all tests in an org, including tests that originate from installed managed and unlocked packages. Since files from managed and unlocked packages aren't retrieved into Salesforce DX repositories, these files cannot be included in your SonarQube scans.
This plugin will work if you run local tests or run all tests in an org, including tests that originate from installed managed and unlocked packages. Since files from managed and unlocked packages aren't retrieved into Salesforce DX repositories, these files cannot be included in your SonarQube scans.

When the plugin is unable to find the Apex file from the coverage report in your repository, it will print a warning and not add that file's coverage data to the coverage XML created by this plugin. A warning will be printed for each file not found in a package directory in your repository. See [Errors and Warnings](https://github.com/mcarvin8/apex-code-coverage-transformer?tab=readme-ov-file#errors-and-warnings) for more information.

Expand All @@ -51,8 +51,6 @@ sf apex get test --test-run-id <test run id> --code-coverage --result-format jso

The code coverage JSONs created by the Salesforce CLI aren't accepted by SonarQube automatically for Salesforce DX repositories and needs to be converted using this plugin.

**Disclaimer**: Due to existing bugs with how the Salesforce CLI reports covered lines during deployments (see [5511](https://github.com/forcedotcom/salesforcedx-vscode/issues/5511) and [1568](https://github.com/forcedotcom/cli/issues/1568)), to add support for covered lines in this plugin for deployment coverage files, I had to add a function to re-number out-of-range covered lines the CLI may report (ex: line 100 in a 98-line Apex Class is reported back as covered by the Salesforce CLI deploy command). Salesforce's coverage result may also include extra lines as covered (ex: 120 lines are included in the coverage report for a 100 line file), so the coverage percentage may vary based on how many lines the API returns in the coverage report. Once Salesforce fixes the API to correctly return covered lines in the deploy command, this function will be removed.

## Command

The `apex-code-coverage-transformer` has 1 command:
Expand Down
8 changes: 0 additions & 8 deletions src/helpers/getTotalLines.ts

This file was deleted.

40 changes: 0 additions & 40 deletions src/helpers/setCoveredLines.ts

This file was deleted.

23 changes: 10 additions & 13 deletions src/helpers/transformDeployCoverageReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { create } from 'xmlbuilder2';
import { DeployCoverageData, CoverageObject, FileObject } from './types.js';
import { getPackageDirectories } from './getPackageDirectories.js';
import { findFilePath } from './findFilePath.js';
import { setCoveredLines } from './setCoveredLines.js';
import { normalizePathToUnix } from './normalizePathToUnix.js';

export async function transformDeployCoverageReport(
Expand All @@ -26,23 +25,21 @@ export async function transformDeployCoverageReport(
warnings.push(`The file name ${formattedFileName} was not found in any package directory.`);
continue;
}
const uncoveredLines = Object.keys(fileInfo.s)
.filter((lineNumber) => fileInfo.s[lineNumber] === 0)
.map(Number);
const coveredLines = Object.keys(fileInfo.s)
.filter((lineNumber) => fileInfo.s[lineNumber] === 1)
.map(Number);

const fileObj: FileObject = {
'@path': normalizePathToUnix(relativeFilePath),
lineToCover: uncoveredLines.map((lineNumber: number) => ({
'@lineNumber': lineNumber,
'@covered': 'false',
})),
lineToCover: [],
};

// this function is only needed until Salesforce fixes the API to correctly return covered lines
await setCoveredLines(coveredLines, uncoveredLines, repoRoot, relativeFilePath, fileObj);
for (const lineNumberString in fileInfo.s) {
if (!Object.hasOwn(fileInfo.s, lineNumberString)) continue;
const covered = fileInfo.s[lineNumberString] === 1 ? 'true' : 'false';
fileObj.lineToCover.push({
'@lineNumber': Number(lineNumberString),
'@covered': covered,
});
}

filesProcessed++;
coverageObj.coverage.file.push(fileObj);
}
Expand Down
72 changes: 0 additions & 72 deletions test/baselines/classes/AccountProfile.cls

This file was deleted.

40 changes: 0 additions & 40 deletions test/baselines/triggers/AccountTrigger.trigger

This file was deleted.

17 changes: 9 additions & 8 deletions test/commands/acc-transformer/transform.nut.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import { copyFile, writeFile, readFile, rm, mkdir } from 'node:fs/promises';
import { writeFile, readFile, rm, mkdir } from 'node:fs/promises';
import { strictEqual } from 'node:assert';
import { resolve } from 'node:path';

Expand All @@ -9,8 +9,10 @@ import { expect } from 'chai';

describe('acc-transformer transform NUTs', () => {
let session: TestSession;
const baselineClassPath = resolve('test/baselines/classes/AccountProfile.cls');
const baselineTriggerPath = resolve('test/baselines/triggers/AccountTrigger.trigger');
const mockClassContent = '// Test Apex Class';
const mockTriggerContent = '// Test Apex Trigger';
const baselineClassPath = resolve('force-app/main/default/classes/AccountProfile.cls');
const baselineTriggerPath = resolve('packaged/triggers/AccountTrigger.trigger');
const deployCoverageNoExts = resolve('test/deploy_coverage_no_file_exts.json');
const deployCoverageWithExts = resolve('test/deploy_coverage_with_file_exts.json');
const testCoverage = resolve('test/test_coverage.json');
Expand All @@ -26,7 +28,6 @@ describe('acc-transformer transform NUTs', () => {
packageDirectories: [{ path: 'force-app', default: true }, { path: 'packaged' }],
namespace: '',
sfdcLoginUrl: 'https://login.salesforce.com',
sourceApiVersion: '58.0',
};
const configJsonString = JSON.stringify(configFile, null, 2);

Expand All @@ -35,15 +36,15 @@ describe('acc-transformer transform NUTs', () => {
await writeFile(sfdxConfigFile, configJsonString);
await mkdir('force-app/main/default/classes', { recursive: true });
await mkdir('packaged/triggers', { recursive: true });
await copyFile(baselineClassPath, 'force-app/main/default/classes/AccountProfile.cls');
await copyFile(baselineTriggerPath, 'packaged/triggers/AccountTrigger.trigger');
await writeFile(baselineClassPath, mockClassContent);
await writeFile(baselineTriggerPath, mockTriggerContent);
});

after(async () => {
await session?.clean();
await rm(sfdxConfigFile);
await rm('force-app/main/default/classes/AccountProfile.cls');
await rm('packaged/triggers/AccountTrigger.trigger');
await rm(baselineClassPath);
await rm(baselineTriggerPath);
await rm('force-app', { recursive: true });
await rm('packaged', { recursive: true });
await rm(coverageXmlPath1);
Expand Down
19 changes: 10 additions & 9 deletions test/commands/acc-transformer/transform.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

import { copyFile, readFile, writeFile, rm, mkdir } from 'node:fs/promises';
import { readFile, writeFile, rm, mkdir } from 'node:fs/promises';
import { strictEqual } from 'node:assert';
import { resolve } from 'node:path';

Expand All @@ -12,8 +12,10 @@ import TransformerTransform from '../../../src/commands/acc-transformer/transfor
describe('main', () => {
const $$ = new TestContext();
let sfCommandStubs: ReturnType<typeof stubSfCommandUx>;
const baselineClassPath = resolve('test/baselines/classes/AccountProfile.cls');
const baselineTriggerPath = resolve('test/baselines/triggers/AccountTrigger.trigger');
const mockClassContent = '// Test Apex Class';
const mockTriggerContent = '// Test Apex Trigger';
const baselineClassPath = resolve('force-app/main/default/classes/AccountProfile.cls');
const baselineTriggerPath = resolve('packaged/triggers/AccountTrigger.trigger');
const deployCoverageNoExts = resolve('test/deploy_coverage_no_file_exts.json');
const deployCoverageWithExts = resolve('test/deploy_coverage_with_file_exts.json');
const testCoverage = resolve('test/test_coverage.json');
Expand All @@ -29,16 +31,15 @@ describe('main', () => {
packageDirectories: [{ path: 'force-app', default: true }, { path: 'packaged' }],
namespace: '',
sfdcLoginUrl: 'https://login.salesforce.com',
sourceApiVersion: '58.0',
};
const configJsonString = JSON.stringify(configFile, null, 2);

before(async () => {
await writeFile(sfdxConfigFile, configJsonString);
await mkdir('force-app/main/default/classes', { recursive: true });
await mkdir('packaged/triggers', { recursive: true });
await copyFile(baselineClassPath, 'force-app/main/default/classes/AccountProfile.cls');
await copyFile(baselineTriggerPath, 'packaged/triggers/AccountTrigger.trigger');
await writeFile(sfdxConfigFile, configJsonString);
await writeFile(baselineClassPath, mockClassContent);
await writeFile(baselineTriggerPath, mockTriggerContent);
});

beforeEach(() => {
Expand All @@ -51,8 +52,8 @@ describe('main', () => {

after(async () => {
await rm(sfdxConfigFile);
await rm('force-app/main/default/classes/AccountProfile.cls');
await rm('packaged/triggers/AccountTrigger.trigger');
await rm(baselineClassPath);
await rm(baselineTriggerPath);
await rm('force-app', { recursive: true });
await rm('packaged', { recursive: true });
await rm(coverageXmlPath1);
Expand Down
Loading

0 comments on commit 9cc164f

Please sign in to comment.