Skip to content

Commit d23afe8

Browse files
eleanorjboydKartik Raj
andauthored
Add unittest subtest (#21081)
closes #21038 also updates the subtests names --------- Co-authored-by: Kartik Raj <karraj@microsoft.com>
1 parent 7ddfd9f commit d23afe8

4 files changed

Lines changed: 124 additions & 5 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import unittest
5+
6+
# Test class for the test_subtest_run test.
7+
# The test_failed_tests function should return a dictionary that has a "success" status
8+
# and the "result" value is a dict with 6 entries, one for each subtest.
9+
10+
11+
class NumbersTest(unittest.TestCase):
12+
def test_even(self):
13+
"""
14+
Test that numbers between 0 and 5 are all even.
15+
"""
16+
for i in range(0, 6):
17+
with self.subTest(i=i):
18+
self.assertEqual(i % 2, 0)

pythonFiles/tests/unittestadapter/test_execution.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,35 @@ def test_single_ids_run() -> None:
9494
assert id_result["outcome"] == "success"
9595

9696

97+
def test_subtest_run() -> None:
98+
"""This test runs on a the test_subtest which has a single method, test_even,
99+
that uses unittest subtest.
100+
101+
The actual result of run should return a dict payload with 6 entry for the 6 subtests.
102+
"""
103+
id = "test_subtest.NumbersTest.test_even"
104+
actual = run_tests(
105+
os.fspath(TEST_DATA_PATH), [id], "test_subtest.py", None, "fake-uuid"
106+
)
107+
subtests_ids = [
108+
"test_subtest.NumbersTest.test_even (i=0)",
109+
"test_subtest.NumbersTest.test_even (i=1)",
110+
"test_subtest.NumbersTest.test_even (i=2)",
111+
"test_subtest.NumbersTest.test_even (i=3)",
112+
"test_subtest.NumbersTest.test_even (i=4)",
113+
"test_subtest.NumbersTest.test_even (i=5)",
114+
]
115+
assert actual
116+
assert all(item in actual for item in ("cwd", "status"))
117+
assert actual["status"] == "success"
118+
assert actual["cwd"] == os.fspath(TEST_DATA_PATH)
119+
assert "result" in actual
120+
result = actual["result"]
121+
assert len(result) == 6
122+
for id in subtests_ids:
123+
assert id in result
124+
125+
97126
@pytest.mark.parametrize(
98127
"test_ids, pattern, cwd, expected_outcome",
99128
[

pythonFiles/unittestadapter/execution.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,10 @@ def formatResult(
130130
formatted = formatted[1:]
131131
tb = "".join(formatted)
132132

133-
test_id = test.id()
133+
if subtest:
134+
test_id = subtest.id()
135+
else:
136+
test_id = test.id()
134137

135138
result = {
136139
"test": test.id(),

src/client/testing/testController/workspaceTestAdapter.ts

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { sendTelemetryEvent } from '../../telemetry';
2222
import { EventName } from '../../telemetry/constants';
2323
import { TestProvider } from '../types';
2424
import {
25+
clearAllChildren,
2526
createErrorTestItem,
2627
DebugTestTag,
2728
ErrorTestItemOptions,
@@ -135,8 +136,11 @@ export class WorkspaceTestAdapter {
135136
}
136137

137138
if (rawTestExecData !== undefined && rawTestExecData.result !== undefined) {
139+
// Map which holds the subtest information for each test item.
140+
const subTestStats: Map<string, { passed: number; failed: number }> = new Map();
141+
142+
// iterate through payload and update the UI accordingly.
138143
for (const keyTemp of Object.keys(rawTestExecData.result)) {
139-
// check for result and update the UI accordingly.
140144
const testCases: TestItem[] = [];
141145

142146
// grab leaf level test items
@@ -147,7 +151,6 @@ export class WorkspaceTestAdapter {
147151

148152
if (
149153
rawTestExecData.result[keyTemp].outcome === 'failure' ||
150-
rawTestExecData.result[keyTemp].outcome === 'subtest-failure' ||
151154
rawTestExecData.result[keyTemp].outcome === 'passed-unexpected'
152155
) {
153156
const rawTraceback = rawTestExecData.result[keyTemp].traceback ?? '';
@@ -175,8 +178,7 @@ export class WorkspaceTestAdapter {
175178
});
176179
} else if (
177180
rawTestExecData.result[keyTemp].outcome === 'success' ||
178-
rawTestExecData.result[keyTemp].outcome === 'expected-failure' ||
179-
rawTestExecData.result[keyTemp].outcome === 'subtest-passed'
181+
rawTestExecData.result[keyTemp].outcome === 'expected-failure'
180182
) {
181183
const grabTestItem = this.runIdToTestItem.get(keyTemp);
182184
const grabVSid = this.runIdToVSid.get(keyTemp);
@@ -203,6 +205,73 @@ export class WorkspaceTestAdapter {
203205
}
204206
});
205207
}
208+
} else if (rawTestExecData.result[keyTemp].outcome === 'subtest-failure') {
209+
// split on " " since the subtest ID has the parent test ID in the first part of the ID.
210+
const parentTestCaseId = keyTemp.split(' ')[0];
211+
const parentTestItem = this.runIdToTestItem.get(parentTestCaseId);
212+
const data = rawTestExecData.result[keyTemp];
213+
// find the subtest's parent test item
214+
if (parentTestItem) {
215+
const subtestStats = subTestStats.get(parentTestCaseId);
216+
if (subtestStats) {
217+
subtestStats.failed += 1;
218+
} else {
219+
subTestStats.set(parentTestCaseId, { failed: 1, passed: 0 });
220+
runInstance.appendOutput(fixLogLines(`${parentTestCaseId} [subtests]:\r\n`));
221+
// clear since subtest items don't persist between runs
222+
clearAllChildren(parentTestItem);
223+
}
224+
const subtestId = keyTemp;
225+
const subTestItem = testController?.createTestItem(subtestId, subtestId);
226+
runInstance.appendOutput(fixLogLines(`${subtestId} Failed\r\n`));
227+
// create a new test item for the subtest
228+
if (subTestItem) {
229+
const traceback = data.traceback ?? '';
230+
const text = `${data.subtest} Failed: ${data.message ?? data.outcome}\r\n${traceback}\r\n`;
231+
runInstance.appendOutput(fixLogLines(text));
232+
parentTestItem.children.add(subTestItem);
233+
runInstance.started(subTestItem);
234+
const message = new TestMessage(rawTestExecData?.result[keyTemp].message ?? '');
235+
if (parentTestItem.uri && parentTestItem.range) {
236+
message.location = new Location(parentTestItem.uri, parentTestItem.range);
237+
}
238+
runInstance.failed(subTestItem, message);
239+
} else {
240+
throw new Error('Unable to create new child node for subtest');
241+
}
242+
} else {
243+
throw new Error('Parent test item not found');
244+
}
245+
} else if (rawTestExecData.result[keyTemp].outcome === 'subtest-success') {
246+
// split on " " since the subtest ID has the parent test ID in the first part of the ID.
247+
const parentTestCaseId = keyTemp.split(' ')[0];
248+
const parentTestItem = this.runIdToTestItem.get(parentTestCaseId);
249+
250+
// find the subtest's parent test item
251+
if (parentTestItem) {
252+
const subtestStats = subTestStats.get(parentTestCaseId);
253+
if (subtestStats) {
254+
subtestStats.passed += 1;
255+
} else {
256+
subTestStats.set(parentTestCaseId, { failed: 0, passed: 1 });
257+
runInstance.appendOutput(fixLogLines(`${parentTestCaseId} [subtests]:\r\n`));
258+
// clear since subtest items don't persist between runs
259+
clearAllChildren(parentTestItem);
260+
}
261+
const subtestId = keyTemp;
262+
const subTestItem = testController?.createTestItem(subtestId, subtestId);
263+
// create a new test item for the subtest
264+
if (subTestItem) {
265+
parentTestItem.children.add(subTestItem);
266+
runInstance.started(subTestItem);
267+
runInstance.passed(subTestItem);
268+
runInstance.appendOutput(fixLogLines(`${subtestId} Passed\r\n`));
269+
} else {
270+
throw new Error('Unable to create new child node for subtest');
271+
}
272+
} else {
273+
throw new Error('Parent test item not found');
274+
}
206275
}
207276
}
208277
}

0 commit comments

Comments
 (0)