Skip to content

Commit 7c46caf

Browse files
authored
fix: update parent node state when test completes (#1799)
Signed-off-by: Fred Bricon <[email protected]>
1 parent 44d0408 commit 7c46caf

File tree

3 files changed

+109
-2
lines changed

3 files changed

+109
-2
lines changed

src/runners/baseRunner/RunnerResultAnalyzer.ts

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
// Licensed under the MIT license.
33

44
import { Location, MarkdownString, TestItem } from 'vscode';
5-
import { IRunTestContext } from '../../java-test-runner.api';
5+
import { dataCache, ITestItemData } from '../../controller/testItemDataCache';
6+
import { IRunTestContext, TestLevel, TestResultState } from '../../java-test-runner.api';
67
import { processStackTraceLine } from '../utils';
78

89
export abstract class RunnerResultAnalyzer {
10+
// Track parent test item states to update them when all children complete
11+
protected parentStates: Map<TestItem, ParentItemState> = new Map();
12+
913
constructor(protected testContext: IRunTestContext) { }
1014

1115
public abstract analyzeData(data: string): void;
@@ -36,4 +40,95 @@ export abstract class RunnerResultAnalyzer {
3640
return stacktrace.includes(s);
3741
});
3842
}
43+
44+
/**
45+
* Initialize parent state tracking for a test item.
46+
* Counts how many method-level children are being tested.
47+
*/
48+
protected initializeParentState(item: TestItem, triggeredTestsMapping: Map<string, TestItem>): void {
49+
const parent: TestItem | undefined = item.parent;
50+
if (!parent) {
51+
return;
52+
}
53+
54+
const parentData: ITestItemData | undefined = dataCache.get(parent);
55+
if (!parentData || parentData.testLevel !== TestLevel.Class) {
56+
return;
57+
}
58+
59+
if (!this.parentStates.has(parent)) {
60+
// Count how many method-level children are being tested (only count triggered tests)
61+
let childCount: number = 0;
62+
parent.children.forEach((child: TestItem) => {
63+
const childData: ITestItemData | undefined = dataCache.get(child);
64+
if (childData?.testLevel === TestLevel.Method && triggeredTestsMapping.has(child.id)) {
65+
childCount++;
66+
}
67+
});
68+
69+
this.parentStates.set(parent, {
70+
started: false,
71+
childrenTotal: childCount,
72+
childrenCompleted: 0,
73+
hasFailure: false,
74+
});
75+
}
76+
}
77+
78+
/**
79+
* Update parent test item when a child test starts.
80+
* Marks the parent as "started" when the first child starts.
81+
*/
82+
protected updateParentOnChildStart(item: TestItem): void {
83+
const parent: TestItem | undefined = item.parent;
84+
if (!parent) {
85+
return;
86+
}
87+
88+
const parentState: ParentItemState | undefined = this.parentStates.get(parent);
89+
if (parentState && !parentState.started) {
90+
parentState.started = true;
91+
this.testContext.testRun.started(parent);
92+
}
93+
}
94+
95+
/**
96+
* Update parent test item when a child test completes.
97+
* Marks the parent as "passed" or "failed" when all children complete.
98+
*/
99+
protected updateParentOnChildComplete(item: TestItem, childState: TestResultState): void {
100+
const parent: TestItem | undefined = item.parent;
101+
if (!parent) {
102+
return;
103+
}
104+
105+
const parentState: ParentItemState | undefined = this.parentStates.get(parent);
106+
if (!parentState) {
107+
return;
108+
}
109+
110+
// Consider failed or errored tests as failures for the parent
111+
if (childState === TestResultState.Failed ||
112+
childState === TestResultState.Errored) {
113+
parentState.hasFailure = true;
114+
}
115+
116+
parentState.childrenCompleted++;
117+
118+
// Check if all children have completed
119+
if (parentState.childrenCompleted >= parentState.childrenTotal && parentState.childrenTotal > 0) {
120+
if (parentState.hasFailure) {
121+
this.testContext.testRun.failed(parent, []);
122+
} else {
123+
this.testContext.testRun.passed(parent);
124+
}
125+
}
126+
}
127+
}
128+
129+
interface ParentItemState {
130+
started: boolean;
131+
childrenTotal: number;
132+
childrenCompleted: number;
133+
hasFailure: boolean;
39134
}

src/runners/junitRunner/JUnitRunnerResultAnalyzer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,11 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {
6565
if (!item) {
6666
return;
6767
}
68+
this.initializeParentState(item, this.triggeredTestsMapping);
6869
this.setCurrentState(item, TestResultState.Running, 0);
6970
this.setDurationAtStart(this.getCurrentState(item));
7071
setTestState(this.testContext.testRun, item, this.getCurrentState(item).resultState);
72+
this.updateParentOnChildStart(item);
7173
} else if (data.startsWith(MessageId.TestEnd)) {
7274
const item: TestItem | undefined = this.getTestItem(data.substr(MessageId.TestEnd.length));
7375
if (!item) {
@@ -77,6 +79,10 @@ export class JUnitRunnerResultAnalyzer extends RunnerResultAnalyzer {
7779
this.calcDurationAtEnd(currentState);
7880
this.determineResultStateAtEnd(data, currentState);
7981
setTestState(this.testContext.testRun, item, currentState.resultState, undefined, currentState.duration);
82+
const itemData: ITestItemData | undefined = dataCache.get(item);
83+
if (itemData?.testLevel === TestLevel.Method) {
84+
this.updateParentOnChildComplete(item, currentState.resultState);
85+
}
8086
} else if (data.startsWith(MessageId.TestFailed)) {
8187
const item: TestItem | undefined = this.getTestItem(data.substr(MessageId.TestFailed.length));
8288
if (!item) {

src/runners/testngRunner/TestNGRunnerResultAnalyzer.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// Licensed under the MIT license.
33

44
import { Location, MarkdownString, TestItem, TestMessage } from 'vscode';
5-
import { dataCache } from '../../controller/testItemDataCache';
5+
import { dataCache, ITestItemData } from '../../controller/testItemDataCache';
66
import { RunnerResultAnalyzer } from '../baseRunner/RunnerResultAnalyzer';
77
import { setTestState } from '../utils';
88
import { IRunTestContext, TestLevel, TestResultState } from '../../java-test-runner.api';
@@ -64,8 +64,10 @@ export class TestNGRunnerResultAnalyzer extends RunnerResultAnalyzer {
6464
if (!item) {
6565
return;
6666
}
67+
this.initializeParentState(item, this.triggeredTestsMapping);
6768
this.currentTestState = TestResultState.Running;
6869
this.testContext.testRun.started(item);
70+
this.updateParentOnChildStart(item);
6971
} else if (outputData.name === TEST_FAIL) {
7072
const item: TestItem | undefined = this.getTestItem(id);
7173
if (!item) {
@@ -103,6 +105,10 @@ export class TestNGRunnerResultAnalyzer extends RunnerResultAnalyzer {
103105
}
104106
const duration: number = Number.parseInt(outputData.attributes.duration, 10);
105107
setTestState(this.testContext.testRun, item, this.currentTestState, undefined, duration);
108+
const itemData: ITestItemData | undefined = dataCache.get(item);
109+
if (itemData?.testLevel === TestLevel.Method) {
110+
this.updateParentOnChildComplete(item, this.currentTestState);
111+
}
106112
}
107113
}
108114

0 commit comments

Comments
 (0)