Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add JSON result to test:run command #101

Merged
merged 10 commits into from
Dec 2, 2020
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)';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CLI json result expects Coverage info for each ApexCodeCoverage record

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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: capitalizing the Or to be consistent

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