Skip to content

Commit

Permalink
support Junit5 parallel execution microsoft#1472
Browse files Browse the repository at this point in the history
  • Loading branch information
fladdimir committed Jan 11, 2023
1 parent a1f020c commit 56ee558
Show file tree
Hide file tree
Showing 6 changed files with 357 additions and 47 deletions.
116 changes: 72 additions & 44 deletions src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,26 @@ import { dataCache, ITestItemData } from '../../controller/testItemDataCache';
import { createTestItem, updateOrCreateTestItem } from '../../controller/utils';
import { IJavaTestItem, IRunTestContext, TestKind, TestLevel } from '../../types';
import { RunnerResultAnalyzer } from '../baseRunner/RunnerResultAnalyzer';
import { findTestLocation, setTestState, TestResultState } from '../utils';
import { CurrentItemState, findTestLocation, setTestState, TestResultState } from '../utils';


export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {

private testOutputMapping: Map<string, ITestInfo> = new Map();
private triggeredTestsMapping: Map<string, TestItem> = new Map();
private currentTestState: TestResultState;
private projectName: string;
private incompleteTestSuite: ITestInfo[] = [];

// tests may be run concurrently, so each item's current state needs to be remembered
private currentStates: Map<TestItem, CurrentItemState> = new Map();

// failure info for a test is received consecutively:
private currentItem: TestItem | undefined;
private currentDuration: number = 0;
private traces: MarkdownString;
private assertionFailure: TestMessage | undefined;
private recordingType: RecordingType;
private expectString: string;
private actualString: string;
private projectName: string;
private incompleteTestSuite: ITestInfo[] = [];

constructor(protected testContext: IRunTestContext) {
super(testContext);
Expand Down Expand Up @@ -60,40 +64,26 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {
if (!item) {
return;
}
if (item.id !== this.currentItem?.id) {
this.initializeCache(item);
}
this.testContext.testRun.started(item);

const start: number = Date.now();
if (this.currentDuration === 0) {
this.currentDuration = -start;
} else if (this.currentDuration > 0) {
// Some test cases may executed multiple times (@RepeatedTest), we need to calculate the time for each execution
this.currentDuration -= start;
}
this.setCurrentState(item, TestResultState.Running, 0);
this.setDurationAtStart(this.getCurrentState(item));
setTestState(this.testContext.testRun, item, this.getCurrentState(item).resultState);
} else if (data.startsWith(MessageId.TestEnd)) {
if (!this.currentItem) {
const item: TestItem | undefined = this.getTestItem(data.substr(MessageId.TestEnd.length));
if (!item) {
return;
}

if (this.currentDuration < 0) {
const end: number = Date.now();
this.currentDuration += end;
}

if (data.indexOf(MessageId.IGNORE_TEST_PREFIX) > -1) {
this.currentTestState = TestResultState.Skipped;
} else if (this.currentTestState === TestResultState.Running) {
this.currentTestState = TestResultState.Passed;
}
setTestState(this.testContext.testRun, this.currentItem, this.currentTestState, undefined, this.currentDuration);
const currentState: CurrentItemState = this.getCurrentState(item);
this.calcDurationAtEnd(currentState);
this.determineResultStateAtEnd(data, currentState);
setTestState(this.testContext.testRun, item, currentState.resultState, undefined, currentState.duration);
} else if (data.startsWith(MessageId.TestFailed)) {
if (data.indexOf(MessageId.ASSUMPTION_FAILED_TEST_PREFIX) > -1) {
this.currentTestState = TestResultState.Skipped;
} else {
this.currentTestState = TestResultState.Failed;
const item: TestItem | undefined = this.getTestItem(data.substr(MessageId.TestFailed.length));
if (!item) {
return;
}
const currentState: CurrentItemState = this.getCurrentState(item);
this.determineResultStateOnFailure(data, currentState);
this.initializeSingleItemProcessingCache(item); // traces or comparison failure info might follow immediately
} else if (data.startsWith(MessageId.TestError)) {
let item: TestItem | undefined = this.getTestItem(data.substr(MessageId.TestError.length));
if (!item) {
Expand All @@ -104,10 +94,10 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {
return;
}
}
this.getCurrentState(item).resultState = TestResultState.Errored;
if (item.id !== this.currentItem?.id) {
this.initializeCache(item);
this.initializeSingleItemProcessingCache(item);
}
this.currentTestState = TestResultState.Errored;
} else if (data.startsWith(MessageId.TraceStart)) {
this.traces = new MarkdownString();
this.traces.isTrusted = true;
Expand All @@ -117,12 +107,12 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {
if (!this.currentItem) {
return;
}

const testMessage: TestMessage = new TestMessage(this.traces);
this.tryAppendMessage(this.currentItem, testMessage, this.currentTestState);
const currentResultState: TestResultState = this.getCurrentState(this.currentItem).resultState;
this.tryAppendMessage(this.currentItem, testMessage, currentResultState);
this.recordingType = RecordingType.None;
if (this.currentTestState === TestResultState.Errored) {
setTestState(this.testContext.testRun, this.currentItem, this.currentTestState);
if (currentResultState === TestResultState.Errored) {
setTestState(this.testContext.testRun, this.currentItem, currentResultState);
}
} else if (data.startsWith(MessageId.ExpectStart)) {
this.recordingType = RecordingType.ExpectMessage;
Expand Down Expand Up @@ -154,6 +144,37 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {
}
}

private determineResultStateOnFailure(data: string, currentState: CurrentItemState): void {
const isSkip: boolean = data.indexOf(MessageId.ASSUMPTION_FAILED_TEST_PREFIX) > -1;
currentState.resultState = isSkip ? TestResultState.Skipped : TestResultState.Failed;
}

private determineResultStateAtEnd(data: string, currentState: CurrentItemState): void {
const isIgnore: boolean = data.indexOf(MessageId.IGNORE_TEST_PREFIX) > -1;
if (isIgnore) {
currentState.resultState = TestResultState.Skipped;
} else if (currentState.resultState === TestResultState.Running) {
currentState.resultState = TestResultState.Passed;
}
}

private setDurationAtStart(itemState: CurrentItemState): void {
const start: number = Date.now();
if (itemState.duration === 0) {
itemState.duration = -start;
} else if (itemState.duration > 0) {
// Some test cases may executed multiple times (@RepeatedTest), we need to calculate the time for each execution
itemState.duration -= start;
}
}

private calcDurationAtEnd(currentState: CurrentItemState): void {
if (currentState.duration < 0) {
const end: number = Date.now();
currentState.duration += end;
}
}

protected getTestItem(message: string): TestItem | undefined {
const index: string = message.substring(0, message.indexOf(',')).trim();
return this.testOutputMapping.get(index)?.testItem;
Expand Down Expand Up @@ -182,10 +203,17 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {
return `${this.projectName}@${message}`;
}

protected initializeCache(item: TestItem): void {
this.currentTestState = TestResultState.Running;
protected setCurrentState(testItem: TestItem, resultState: TestResultState, duration: number): void {
this.currentStates.set(testItem, { resultState, duration });
}

protected getCurrentState(testItem: TestItem): CurrentItemState {
if (!this.currentStates.has(testItem)) this.setCurrentState(testItem, TestResultState.Running, 0);
return this.currentStates.get(testItem)!;
}

protected initializeSingleItemProcessingCache(item: TestItem): void {
this.currentItem = item;
this.currentDuration = 0;
this.assertionFailure = undefined;
this.expectString = '';
this.actualString = '';
Expand Down Expand Up @@ -288,7 +316,7 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {

if (testItem) {
if (dataCache.get(testItem)?.testKind === TestKind.JUnit5 &&
this.getLabelWithoutCodicon(testItem.label) !== displayName) {
this.getLabelWithoutCodicon(testItem.label) !== displayName) {
testItem.description = displayName;
} else {
testItem.description = '';
Expand Down
5 changes: 5 additions & 0 deletions src/runners/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,8 @@ export enum TestResultState {
// Test run failed for some other reason (compilation error, timeout, etc)
Errored = 6,
}

export class CurrentItemState {
public resultState: TestResultState;
public duration: number;
}
Loading

0 comments on commit 56ee558

Please sign in to comment.