Skip to content

Commit

Permalink
feat: add JSON result to test:run command (#101)
Browse files Browse the repository at this point in the history
@W-8320712@
  • Loading branch information
AnanyaJha committed Dec 2, 2020
1 parent 7c77be3 commit c35c36f
Show file tree
Hide file tree
Showing 9 changed files with 748 additions and 59 deletions.
19 changes: 10 additions & 9 deletions packages/apex-node/src/reporters/junitReporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ import { msToSecond } from '../utils';
// cli currently has spaces in multiples of four for junit format
const tab = ' ';

const timeProperties = [
'testExecutionTimeInMs',
'testTotalTimeInMs',
'commandTimeInMs'
];

// properties not in cli junit spec
const skippedProperties = ['skipRate', 'totalLines', 'linesCovered'];
export class JUnitReporter {
public format(testResult: TestResult): string {
const { summary, tests } = testResult;
Expand All @@ -40,18 +48,11 @@ export class JUnitReporter {
let junitProperties = `${tab}${tab}<properties>\n`;

Object.entries(testResult.summary).forEach(([key, value]) => {
// skipRate not in cli spec
if (this.isEmpty(value) || key === 'skipRate') {
if (this.isEmpty(value) || skippedProperties.includes(key)) {
return;
}

if (
[
'testExecutionTimeInMs',
'testTotalTimeInMs',
'commandTimeInMs'
].includes(key)
) {
if (timeProperties.includes(key)) {
value = `${msToSecond(value)} s`;
key = key.replace('InMs', '');
}
Expand Down
56 changes: 33 additions & 23 deletions packages/apex-node/src/tests/testService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,19 +111,22 @@ export class TestService {
result.tests.forEach(item => {
const keyCodeCov = `${item.apexClass.id}-${item.methodName}`;
const perTestCov = perTestCovMap.get(keyCodeCov);
coveredApexClassIdSet.add(perTestCov.apexClassorTriggerId);
item.perTestCoverage = {
apexClassOrTriggerName: perTestCov.apexClassOrTriggerName,
percentage: perTestCov.percentage
};
coveredApexClassIdSet.add(perTestCov.apexClassOrTriggerId);
item.perTestCoverage = perTestCov;
});

const {
codeCoverageResults,
testRunCoverage
totalLines,
coveredLines
} = await this.getAggregateCodeCoverage(coveredApexClassIdSet);
result.codecoverage = codeCoverageResults;
result.summary.testRunCoverage = testRunCoverage;
result.summary.totalLines = totalLines;
result.summary.coveredLines = coveredLines;
result.summary.testRunCoverage = this.calculatePercentage(
coveredLines,
totalLines
);
result.summary.orgWideCoverage = await this.getOrgWideCoverage();
}
return result;
Expand Down Expand Up @@ -285,20 +288,23 @@ export class TestService {
const perTestCov = perTestCovMap.get(keyCodeCov);
// Skipped test is not in coverage map, check to see if perTestCov exists first
if (perTestCov) {
coveredApexClassIdSet.add(perTestCov.apexClassorTriggerId);
item.perTestCoverage = {
apexClassOrTriggerName: perTestCov.apexClassOrTriggerName,
percentage: perTestCov.percentage
};
coveredApexClassIdSet.add(perTestCov.apexClassOrTriggerId);
item.perTestCoverage = perTestCov;
}
});

const {
codeCoverageResults,
testRunCoverage
totalLines,
coveredLines
} = await this.getAggregateCodeCoverage(coveredApexClassIdSet);
result.codecoverage = codeCoverageResults;
result.summary.testRunCoverage = testRunCoverage;
result.summary.totalLines = totalLines;
result.summary.coveredLines = coveredLines;
result.summary.testRunCoverage = this.calculatePercentage(
coveredLines,
totalLines
);
result.summary.orgWideCoverage = await this.getOrgWideCoverage();
}

Expand Down Expand Up @@ -431,7 +437,7 @@ export class TestService {
str = str.slice(0, -1);

const perTestCodeCovQuery =
'SELECT ApexTestClassId, ApexClassOrTrigger.Id, ApexClassOrTrigger.Name, TestMethodName, NumLinesCovered, NumLinesUncovered FROM ApexCodeCoverage WHERE ApexTestClassId IN (%s)';
'SELECT ApexTestClassId, ApexClassOrTrigger.Id, ApexClassOrTrigger.Name, TestMethodName, NumLinesCovered, NumLinesUncovered, Coverage FROM ApexCodeCoverage WHERE ApexTestClassId IN (%s)';
const perTestCodeCovResuls = (await this.connection.tooling.query(
util.format(perTestCodeCovQuery, `${str}`)
)) as ApexCodeCoverage;
Expand All @@ -447,10 +453,13 @@ export class TestService {
//NOTE: a test could cover more than one class, we should change this in order to handle that
perTestCoverageMap.set(`${item.ApexTestClassId}-${item.TestMethodName}`, {
apexClassOrTriggerName: item.ApexClassOrTrigger.Name,
apexClassorTriggerId: item.ApexClassOrTrigger.Id,
apexClassOrTriggerId: item.ApexClassOrTrigger.Id,
apexTestClassId: item.ApexTestClassId,
apexTestMethodName: item.TestMethodName,
percentage
numLinesCovered: item.NumLinesCovered,
numLinesUncovered: item.NumLinesUncovered,
percentage,
...(item.Coverage ? { coverage: item.Coverage } : {})
});
});

Expand All @@ -461,7 +470,8 @@ export class TestService {
apexClassIdSet: Set<string>
): Promise<{
codeCoverageResults: CodeCoverageResult[];
testRunCoverage: string;
totalLines: number;
coveredLines: number;
}> {
let str = '';
apexClassIdSet.forEach(elem => {
Expand Down Expand Up @@ -502,11 +512,11 @@ export class TestService {
}
);

const testRunCoverage = this.calculatePercentage(
totalLinesCovered,
totalLinesCovered + totalLinesUncovered
);
return { codeCoverageResults, testRunCoverage };
return {
codeCoverageResults,
totalLines: totalLinesCovered + totalLinesUncovered,
coveredLines: totalLinesCovered
};
}

private calculatePercentage(dividend: number, divisor: number): string {
Expand Down
19 changes: 12 additions & 7 deletions packages/apex-node/src/tests/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,10 @@ export type ApexTestResultData = {
* The full name of the associated ApexClass method
*/
fullName: string;
perTestCoverage?: {
apexClassOrTriggerName: string;
percentage: string;
};
/**
* The associated ApexCodeCoverage object
*/
perTestCoverage?: PerTestCoverage;
};

export type CodeCoverageResult = {
Expand All @@ -353,8 +353,6 @@ export type TestResult = {
failRate: string;
testsRan: number;
orgId: string;
testRunCoverage?: string;
orgWideCoverage?: string;
outcome: string;
passing: number;
failing: number;
Expand All @@ -369,6 +367,10 @@ export type TestResult = {
username: string;
testRunId: string;
userId: string;
testRunCoverage?: string;
orgWideCoverage?: string;
totalLines?: number;
coveredLines?: number;
};
tests: ApexTestResultData[];
codecoverage?: CodeCoverageResult[];
Expand Down Expand Up @@ -397,10 +399,13 @@ export type ApexCodeCoverage = {

export type PerTestCoverage = {
apexClassOrTriggerName: string;
apexClassorTriggerId: string;
apexClassOrTriggerId: string;
apexTestClassId: string;
apexTestMethodName: string;
numLinesCovered: number;
numLinesUncovered: number;
percentage: string;
coverage?: { coveredLines: number[]; uncoveredLines: number[] };
};

export type ApexCodeCoverageAggregateRecord = {
Expand Down
51 changes: 35 additions & 16 deletions packages/apex-node/test/tests/codeCoverage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ describe('Get code coverage results', () => {
);
});

it('should return test code coverage result', async () => {
it('should return aggregate code coverage result and testRunCoverage', async () => {
const codeCoverageQueryResult = [
{
ApexClassOrTrigger: { Id: '0001x05958', Name: 'ApexTrigger1' },
Expand Down Expand Up @@ -136,16 +136,18 @@ describe('Get code coverage results', () => {
const testSrv = new TestService(mockConnection);
const {
codeCoverageResults,
testRunCoverage
totalLines,
coveredLines
} = await testSrv.getAggregateCodeCoverage(
new Set<string>(['0001x05958', '0001x05959', '0001x05951'])
);

expect(testRunCoverage).to.equal('75%');
expect(totalLines).to.equal(24);
expect(coveredLines).to.equal(18);
expect(codeCoverageResults).to.eql(expectedResult);
});

it('should return test code coverage result with 0 records', async () => {
it('should return aggregate code coverage result with 0 records', async () => {
toolingQueryStub.resolves({
done: true,
totalSize: 0,
Expand All @@ -155,14 +157,16 @@ describe('Get code coverage results', () => {
const testSrv = new TestService(mockConnection);
const {
codeCoverageResults,
testRunCoverage
totalLines,
coveredLines
} = await testSrv.getAggregateCodeCoverage(new Set([]));
expect(codeCoverageResults.length).to.equal(0);
expect(testRunCoverage).to.equal('0%');
expect(totalLines).to.equal(0);
expect(coveredLines).to.equal(0);
});

it('should return per class code coverage and test run coverage', async () => {
const perClassCodeCovResult = [
it('should return per test code coverage', async () => {
const perTestCodeCovResult = [
{
ApexClassOrTrigger: { Id: '0001x05958', Name: 'ApexTrigger1' },
TestMethodName: 'MethodOne',
Expand Down Expand Up @@ -194,7 +198,7 @@ describe('Get code coverage results', () => {
toolingQueryStub.resolves({
done: true,
totalSize: 3,
records: perClassCodeCovResult
records: perTestCodeCovResult
} as ApexCodeCoverage);

const testSrv = new TestService(mockConnection);
Expand All @@ -204,28 +208,43 @@ describe('Get code coverage results', () => {
expect(perTestCoverageMap.size).to.eql(3);
expect(perTestCoverageMap.get('0001x05958-MethodOne')).to.deep.equal({
apexClassOrTriggerName: 'ApexTrigger1',
apexClassorTriggerId: '0001x05958',
apexClassOrTriggerId: '0001x05958',
apexTestClassId: '0001x05958',
apexTestMethodName: 'MethodOne',
percentage: '83%'
percentage: '83%',
coverage: { coveredLines: [1, 2, 3, 4, 5], uncoveredLines: [6] },
numLinesCovered: 5,
numLinesUncovered: 1
});
expect(perTestCoverageMap.get('0001x05959-MethodTwo')).to.deep.equal({
apexClassOrTriggerName: 'ApexTrigger2',
apexClassorTriggerId: '0001x05959',
apexClassOrTriggerId: '0001x05959',
apexTestClassId: '0001x05959',
apexTestMethodName: 'MethodTwo',
percentage: '75%'
percentage: '75%',
coverage: {
coveredLines: [1, 2, 3, 4, 5, 6],
uncoveredLines: [7, 8]
},
numLinesCovered: 6,
numLinesUncovered: 2
});
expect(perTestCoverageMap.get('0001x05951-MethodThree')).to.deep.equal({
apexClassOrTriggerName: 'ApexTrigger3',
apexClassorTriggerId: '0001x05951',
apexClassOrTriggerId: '0001x05951',
apexTestClassId: '0001x05951',
apexTestMethodName: 'MethodThree',
percentage: '70%'
percentage: '70%',
coverage: {
coveredLines: [1, 2, 3, 4, 5, 6, 7],
uncoveredLines: [8, 9, 10]
},
numLinesCovered: 7,
numLinesUncovered: 3
});
});

it('should return per class coverage and test run coverage with 0 records', async () => {
it('should return per test coverage', async () => {
toolingQueryStub.resolves({
done: true,
totalSize: 0,
Expand Down
15 changes: 14 additions & 1 deletion packages/plugin-apex/src/commands/force/apex/test/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Row, Table } from '@salesforce/apex-node/lib/src/utils';
import { flags, SfdxCommand } from '@salesforce/command';
import { Messages, Org } from '@salesforce/core';
import { AnyJson } from '@salesforce/ts-types';
import { JsonReporter } from '../../../../jsonReporter';
import { buildDescription, logLevels } from '../../../../utils';

Messages.importMessagesDirectory(__dirname);
Expand Down Expand Up @@ -210,7 +211,7 @@ export default class Run extends SfdxCommand {
);
}

return result;
return this.logJson(result);
} catch (e) {
return Promise.reject(e);
}
Expand Down Expand Up @@ -483,6 +484,18 @@ export default class Run extends SfdxCommand {
}
}

private logJson(result: TestResult): AnyJson {
try {
const reporter = new JsonReporter();
return reporter.format(result);
} catch (e) {
this.ux.logJson(result);
const msg = messages.getMessage('testResultProcessErr', [e]);
this.ux.error(msg);
}
return result;
}

private formatReportHint(result: TestResult): string {
let reportArgs = `-i ${result.summary.testRunId}`;
if (this.flags.targetusername) {
Expand Down
Loading

0 comments on commit c35c36f

Please sign in to comment.