Skip to content

Commit 25dbb7b

Browse files
Copiloteleanorjboyd
andcommitted
Add lineno to class nodes for unittest discovery
- Modified build_test_tree() in pvsc_utils.py to add line numbers to class nodes - Added get_class_line() function to extract class line numbers using inspect - Made lineno an optional field in TestNode TypedDict - Added find_class_line_number() helper for test expectations - Updated expected test outputs to include lineno for class nodes This implements the same functionality for unittest as was done for pytest, enabling TestClass items to show the green arrow and be runnable in VS Code's Test Explorer. Co-authored-by: eleanorjboyd <[email protected]>
1 parent 85e6ca8 commit 25dbb7b

File tree

2 files changed

+48
-0
lines changed

2 files changed

+48
-0
lines changed

python_files/tests/unittestadapter/expected_discovery_test_output.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,25 @@
99
TEST_DATA_PATH = pathlib.Path(__file__).parent / ".data"
1010

1111

12+
def find_class_line_number(class_name: str, test_file_path) -> str:
13+
"""Function which finds the correct line number for a class definition.
14+
15+
Args:
16+
class_name: The name of the class to find the line number for.
17+
test_file_path: The path to the test file where the class is located.
18+
"""
19+
# Look for the class definition line
20+
with pathlib.Path(test_file_path).open() as f:
21+
for i, line in enumerate(f):
22+
# Match "class ClassName" or "class ClassName(" or "class ClassName:"
23+
if line.strip().startswith(f"class {class_name}") or line.strip().startswith(
24+
f"class {class_name}("
25+
):
26+
return str(i + 1)
27+
error_str: str = f"Class {class_name!r} not found on any line in {test_file_path}"
28+
raise ValueError(error_str)
29+
30+
1231
skip_unittest_folder_discovery_output = {
1332
"path": os.fspath(TEST_DATA_PATH / "unittest_skip"),
1433
"name": "unittest_skip",
@@ -49,6 +68,10 @@
4968
],
5069
"id_": os.fspath(TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py")
5170
+ "\\SimpleTest",
71+
"lineno": find_class_line_number(
72+
"SimpleTest",
73+
TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py",
74+
),
5275
}
5376
],
5477
"id_": os.fspath(TEST_DATA_PATH / "unittest_skip" / "unittest_skip_function.py"),
@@ -114,6 +137,16 @@
114137
},
115138
],
116139
"id_": complex_tree_file_path + "\\" + "TreeOne",
140+
"lineno": find_class_line_number(
141+
"TreeOne",
142+
pathlib.PurePath(
143+
TEST_DATA_PATH,
144+
"utils_complex_tree",
145+
"test_outer_folder",
146+
"test_inner_folder",
147+
"test_utils_complex_tree.py",
148+
),
149+
),
117150
}
118151
],
119152
"id_": complex_tree_file_path,

python_files/unittestadapter/pvsc_utils.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class TestItem(TestData):
4444

4545
class TestNode(TestData):
4646
children: "List[TestNode | TestItem]"
47+
lineno: NotRequired[str] # Optional field for class nodes
4748

4849

4950
class TestExecutionStatus(str, enum.Enum):
@@ -101,6 +102,16 @@ def get_test_case(suite):
101102
yield from get_test_case(test)
102103

103104

105+
def get_class_line(test_case: unittest.TestCase) -> str:
106+
"""Get the line number where a test class is defined."""
107+
try:
108+
test_class = test_case.__class__
109+
_sourcelines, lineno = inspect.getsourcelines(test_class)
110+
return str(lineno)
111+
except Exception:
112+
return "*"
113+
114+
104115
def get_source_line(obj) -> str:
105116
"""Get the line number of a test case start line."""
106117
try:
@@ -249,6 +260,10 @@ def build_test_tree(
249260
class_name, file_path, TestNodeTypeEnum.class_, current_node
250261
)
251262

263+
# Add line number to class node if not already present.
264+
if "lineno" not in current_node:
265+
current_node["lineno"] = get_class_line(test_case)
266+
252267
# Get test line number.
253268
test_method = getattr(test_case, test_case._testMethodName) # noqa: SLF001
254269
lineno = get_source_line(test_method)

0 commit comments

Comments
 (0)