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: Check for query limits before getting apex test results @W-8379096@ #92

Merged
merged 9 commits into from
Nov 19, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 86 additions & 53 deletions packages/apex-node/src/tests/testService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import * as util from 'util';
import { nls } from '../i18n';
import { StreamingClient } from '../streaming';

// Tooling API query char limit is 100,000 after v48; REST API limit for uri + headers is 16,348 bytes
// local testing shows query char limit to be closer to ~12,400
const QUERY_CHAR_LIMIT = 12400;

export class TestService {
public readonly connection: Connection;

Expand Down Expand Up @@ -195,6 +199,47 @@ export class TestService {
return percentage;
}

private addIdToQuery(formattedIds: string, id: string): string {
return formattedIds.length === 0 ? id : `${formattedIds}','${id}`;
}

public async getApexTestResults(
testQueueResult: ApexTestQueueItem
): Promise<ApexTestResult[]> {
let apexTestResultQuery = 'SELECT Id, QueueItemId, StackTrace, Message, ';
apexTestResultQuery +=
'RunTime, TestTimestamp, AsyncApexJobId, MethodName, Outcome, ApexLogId, ';
apexTestResultQuery +=
'ApexClass.Id, ApexClass.Name, ApexClass.NamespacePrefix ';
apexTestResultQuery += 'FROM ApexTestResult WHERE QueueItemId IN (%s)';

const apexResultIds = testQueueResult.records.map(record => record.Id);
let formattedIds = '';
const queries = [];

// iterate thru ids, create query with id, & compare query length to char limit
for (const id of apexResultIds) {
const newIds = this.addIdToQuery(formattedIds, id);
const query = util.format(apexTestResultQuery, `'${newIds}'`);

if (query.length > QUERY_CHAR_LIMIT) {
queries.push(util.format(apexTestResultQuery, `'${formattedIds}'`));
formattedIds = '';
}
formattedIds = this.addIdToQuery(formattedIds, id);
}

if (formattedIds.length > 0) {
queries.push(util.format(apexTestResultQuery, `'${formattedIds}'`));
}

const queryPromises = queries.map(query => {
return this.connection.tooling.query(query) as Promise<ApexTestResult>;
});
const apexTestResults = await Promise.all(queryPromises);
return apexTestResults;
}

public async getTestResultData(
testQueueResult: ApexTestQueueItem,
testRunId: string,
Expand All @@ -214,21 +259,7 @@ export class TestService {
}

const summaryRecord = testRunSummaryResults.records[0];

let apexTestResultQuery = 'SELECT Id, QueueItemId, StackTrace, Message, ';
apexTestResultQuery +=
'RunTime, TestTimestamp, AsyncApexJobId, MethodName, Outcome, ApexLogId, ';
apexTestResultQuery +=
'ApexClass.Id, ApexClass.Name, ApexClass.NamespacePrefix ';
apexTestResultQuery += 'FROM ApexTestResult WHERE QueueItemId IN (%s)';

// TODO: this needs to check for query length
const apexResultIds = testQueueResult.records
.map(record => record.Id)
.join("','");
const apexTestResults = (await this.connection.tooling.query(
util.format(apexTestResultQuery, `'${apexResultIds}'`)
)) as ApexTestResult;
const apexTestResults = await this.getApexTestResults(testQueueResult);

let globalTestPassed = 0;
let globalTestFailed = 0;
Expand All @@ -237,46 +268,48 @@ export class TestService {
const coveredApexClassIdSet = new Set<string>();
// Iterate over test results, format and add them as results.tests
const testResults: ApexTestResultData[] = [];
apexTestResults.records.forEach(item => {
switch (item.Outcome) {
case ApexTestResultOutcome.Pass:
globalTestPassed++;
break;
case ApexTestResultOutcome.Fail:
case ApexTestResultOutcome.CompileFail:
globalTestFailed++;
break;
case ApexTestResultOutcome.Skip:
globalTestSkipped++;
break;
}

apexTestClassIdSet.add(item.ApexClass.Id);
// Can only query the FullName field if a single record is returned, so manually build the field
item.ApexClass.FullName = item.ApexClass.NamespacePrefix
? `${item.ApexClass.NamespacePrefix}__${item.ApexClass.Name}`
: item.ApexClass.Name;
for (const result of apexTestResults) {
result.records.forEach(item => {
switch (item.Outcome) {
case ApexTestResultOutcome.Pass:
globalTestPassed++;
break;
case ApexTestResultOutcome.Fail:
case ApexTestResultOutcome.CompileFail:
globalTestFailed++;
break;
case ApexTestResultOutcome.Skip:
globalTestSkipped++;
break;
}

testResults.push({
id: item.Id,
queueItemId: item.QueueItemId,
stackTrace: item.StackTrace,
message: item.Message,
asyncApexJobId: item.AsyncApexJobId,
methodName: item.MethodName,
outcome: item.Outcome,
apexLogId: item.ApexLogId,
apexClass: {
id: item.ApexClass.Id,
name: item.ApexClass.Name,
namespacePrefix: item.ApexClass.NamespacePrefix,
fullName: item.ApexClass.FullName
},
runTime: item.RunTime,
testTimestamp: item.TestTimestamp, // TODO: convert timestamp
fullName: `${item.ApexClass.FullName}.${item.MethodName}`
apexTestClassIdSet.add(item.ApexClass.Id);
// Can only query the FullName field if a single record is returned, so manually build the field
item.ApexClass.FullName = item.ApexClass.NamespacePrefix
? `${item.ApexClass.NamespacePrefix}__${item.ApexClass.Name}`
: item.ApexClass.Name;

testResults.push({
id: item.Id,
queueItemId: item.QueueItemId,
stackTrace: item.StackTrace,
message: item.Message,
asyncApexJobId: item.AsyncApexJobId,
methodName: item.MethodName,
outcome: item.Outcome,
apexLogId: item.ApexLogId,
apexClass: {
id: item.ApexClass.Id,
name: item.ApexClass.Name,
namespacePrefix: item.ApexClass.NamespacePrefix,
fullName: item.ApexClass.FullName
},
runTime: item.RunTime,
testTimestamp: item.TestTimestamp, // TODO: convert timestamp
fullName: `${item.ApexClass.FullName}.${item.MethodName}`
});
});
});
}

if (codeCoverage) {
const perClassCoverageMap = await this.getPerClassCodeCoverage(
Expand Down
Loading