Skip to content

Commit

Permalink
support multi-level nested classes pytest (microsoft#22681)
Browse files Browse the repository at this point in the history
  • Loading branch information
eleanorjboyd authored Dec 19, 2023
1 parent 81c17e5 commit f2b0b7f
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 11 deletions.
19 changes: 19 additions & 0 deletions pythonFiles/tests/pytestadapter/.data/test_multi_class_nest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.


class TestFirstClass:
class TestSecondClass:
def test_second(self): # test_marker--test_second
assert 1 == 2

def test_first(self): # test_marker--test_first
assert 1 == 2

class TestSecondClass2:
def test_second2(self): # test_marker--test_second2
assert 1 == 1


def test_independent(): # test_marker--test_independent
assert 1 == 1
108 changes: 108 additions & 0 deletions pythonFiles/tests/pytestadapter/expected_discovery_test_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -886,3 +886,111 @@
],
"id_": os.fspath(tests_path),
}
TEST_MULTI_CLASS_NEST_PATH = TEST_DATA_PATH / "test_multi_class_nest.py"

nested_classes_expected_test_output = {
"name": ".data",
"path": TEST_DATA_PATH_STR,
"type_": "folder",
"children": [
{
"name": "test_multi_class_nest.py",
"path": str(TEST_MULTI_CLASS_NEST_PATH),
"type_": "file",
"id_": str(TEST_MULTI_CLASS_NEST_PATH),
"children": [
{
"name": "TestFirstClass",
"path": str(TEST_MULTI_CLASS_NEST_PATH),
"type_": "class",
"id_": "test_multi_class_nest.py::TestFirstClass",
"children": [
{
"name": "TestSecondClass",
"path": str(TEST_MULTI_CLASS_NEST_PATH),
"type_": "class",
"id_": "test_multi_class_nest.py::TestFirstClass::TestSecondClass",
"children": [
{
"name": "test_second",
"path": str(TEST_MULTI_CLASS_NEST_PATH),
"lineno": find_test_line_number(
"test_second",
str(TEST_MULTI_CLASS_NEST_PATH),
),
"type_": "test",
"id_": get_absolute_test_id(
"test_multi_class_nest.py::TestFirstClass::TestSecondClass::test_second",
TEST_MULTI_CLASS_NEST_PATH,
),
"runID": get_absolute_test_id(
"test_multi_class_nest.py::TestFirstClass::TestSecondClass::test_second",
TEST_MULTI_CLASS_NEST_PATH,
),
}
],
},
{
"name": "test_first",
"path": str(TEST_MULTI_CLASS_NEST_PATH),
"lineno": find_test_line_number(
"test_first", str(TEST_MULTI_CLASS_NEST_PATH)
),
"type_": "test",
"id_": get_absolute_test_id(
"test_multi_class_nest.py::TestFirstClass::test_first",
TEST_MULTI_CLASS_NEST_PATH,
),
"runID": get_absolute_test_id(
"test_multi_class_nest.py::TestFirstClass::test_first",
TEST_MULTI_CLASS_NEST_PATH,
),
},
{
"name": "TestSecondClass2",
"path": str(TEST_MULTI_CLASS_NEST_PATH),
"type_": "class",
"id_": "test_multi_class_nest.py::TestFirstClass::TestSecondClass2",
"children": [
{
"name": "test_second2",
"path": str(TEST_MULTI_CLASS_NEST_PATH),
"lineno": find_test_line_number(
"test_second2",
str(TEST_MULTI_CLASS_NEST_PATH),
),
"type_": "test",
"id_": get_absolute_test_id(
"test_multi_class_nest.py::TestFirstClass::TestSecondClass2::test_second2",
TEST_MULTI_CLASS_NEST_PATH,
),
"runID": get_absolute_test_id(
"test_multi_class_nest.py::TestFirstClass::TestSecondClass2::test_second2",
TEST_MULTI_CLASS_NEST_PATH,
),
}
],
},
],
},
{
"name": "test_independent",
"path": str(TEST_MULTI_CLASS_NEST_PATH),
"lineno": find_test_line_number(
"test_independent", str(TEST_MULTI_CLASS_NEST_PATH)
),
"type_": "test",
"id_": get_absolute_test_id(
"test_multi_class_nest.py::test_independent",
TEST_MULTI_CLASS_NEST_PATH,
),
"runID": get_absolute_test_id(
"test_multi_class_nest.py::test_independent",
TEST_MULTI_CLASS_NEST_PATH,
),
},
],
}
],
"id_": str(TEST_DATA_PATH),
}
4 changes: 4 additions & 0 deletions pythonFiles/tests/pytestadapter/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@ def test_parameterized_error_collect():
@pytest.mark.parametrize(
"file, expected_const",
[
(
"test_multi_class_nest.py",
expected_discovery_test_output.nested_classes_expected_test_output,
),
(
"unittest_skiptest_file_level.py",
expected_discovery_test_output.unittest_skip_file_level_expected_output,
Expand Down
34 changes: 23 additions & 11 deletions pythonFiles/vscode_pytest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,25 +391,37 @@ def build_test_tree(session: pytest.Session) -> TestNode:
for test_case in session.items:
test_node = create_test_node(test_case)
if isinstance(test_case.parent, pytest.Class):
try:
test_class_node = class_nodes_dict[test_case.parent.nodeid]
except KeyError:
test_class_node = create_class_node(test_case.parent)
class_nodes_dict[test_case.parent.nodeid] = test_class_node
test_class_node["children"].append(test_node)
if test_case.parent.parent:
parent_module = test_case.parent.parent
case_iter = test_case.parent
node_child_iter = test_node
test_class_node: Union[TestNode, None] = None
while isinstance(case_iter, pytest.Class):
# While the given node is a class, create a class and nest the previous node as a child.
try:
test_class_node = class_nodes_dict[case_iter.nodeid]
except KeyError:
test_class_node = create_class_node(case_iter)
class_nodes_dict[case_iter.nodeid] = test_class_node
test_class_node["children"].append(node_child_iter)
# Iterate up.
node_child_iter = test_class_node
case_iter = case_iter.parent
# Now the parent node is not a class node, it is a file node.
if case_iter:
parent_module = case_iter
else:
ERRORS.append(f"Test class {test_case.parent} has no parent")
ERRORS.append(f"Test class {case_iter} has no parent")
break
# Create a file node that has the class as a child.
# Create a file node that has the last class as a child.
try:
test_file_node: TestNode = file_nodes_dict[parent_module]
except KeyError:
test_file_node = create_file_node(parent_module)
file_nodes_dict[parent_module] = test_file_node
# Check if the class is already a child of the file node.
if test_class_node not in test_file_node["children"]:
if (
test_class_node is not None
and test_class_node not in test_file_node["children"]
):
test_file_node["children"].append(test_class_node)
elif hasattr(test_case, "callspec"): # This means it is a parameterized test.
function_name: str = ""
Expand Down

0 comments on commit f2b0b7f

Please sign in to comment.