Skip to content

Commit a91346b

Browse files
authored
Resolve bug with session not being a parent of tests while building test tree with pytest (#23420)
closes #23411
1 parent d99e74e commit a91346b

File tree

4 files changed

+91
-7
lines changed

4 files changed

+91
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
[pytest]
5+
python_files =
6+
test_*.py
7+
testpaths =
8+
tests
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
5+
def test_hello(): # test_marker--test_hello
6+
assert True

python_files/tests/pytestadapter/test_discovery.py

+34-1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ def test_parameterized_error_collect():
109109
@pytest.mark.parametrize(
110110
"file, expected_const",
111111
[
112+
(
113+
"test_multi_class_nest.py",
114+
expected_discovery_test_output.nested_classes_expected_test_output,
115+
),
112116
(
113117
"test_multi_class_nest.py",
114118
expected_discovery_test_output.nested_classes_expected_test_output,
@@ -178,7 +182,9 @@ def test_pytest_collect(file, expected_const):
178182
if actual_list is not None:
179183
actual_item = actual_list.pop(0)
180184
assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
181-
assert actual_item.get("status") == "success"
185+
assert (
186+
actual_item.get("status") == "success"
187+
), f"Status is not 'success', error is: {actual_item.get('error')}"
182188
assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH)
183189
assert is_same_tree(
184190
actual_item.get("tests"), expected_const
@@ -276,3 +282,30 @@ def test_pytest_config_file():
276282
actual_item.get("tests"),
277283
expected_discovery_test_output.root_with_config_expected_output,
278284
), f"Tests tree does not match expected value. \n Expected: {json.dumps(expected_discovery_test_output.root_with_config_expected_output, indent=4)}. \n Actual: {json.dumps(actual_item.get('tests'), indent=4)}"
285+
286+
287+
def test_config_sub_folder():
288+
"""Here the session node will be a subfolder of the workspace root and the test are in another subfolder.
289+
This tests checks to see if test node path are under the session node and if so the session node is correctly updated to the common path."""
290+
folder_path = helpers.TEST_DATA_PATH / "config_sub_folder"
291+
actual = helpers.runner_with_cwd(
292+
[
293+
"--collect-only",
294+
"-c=config/pytest.ini",
295+
"--rootdir=config/",
296+
"-vv",
297+
],
298+
folder_path,
299+
)
300+
301+
assert actual
302+
actual_list: List[Dict[str, Any]] = actual
303+
if actual_list is not None:
304+
actual_item = actual_list.pop(0)
305+
assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
306+
assert actual_item.get("status") == "success"
307+
assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH / "config_sub_folder")
308+
assert actual_item.get("tests") is not None
309+
if actual_item.get("tests") is not None:
310+
tests: Any = actual_item.get("tests")
311+
assert tests.get("name") == "config_sub_folder"

python_files/vscode_pytest/__init__.py

+43-6
Original file line numberDiff line numberDiff line change
@@ -509,9 +509,25 @@ def build_test_tree(session: pytest.Session) -> TestNode:
509509
created_files_folders_dict: Dict[str, TestNode] = {}
510510
for _, file_node in file_nodes_dict.items():
511511
# Iterate through all the files that exist and construct them into nested folders.
512-
root_folder_node: TestNode = build_nested_folders(
513-
file_node, created_files_folders_dict, session
514-
)
512+
root_folder_node: TestNode
513+
try:
514+
root_folder_node: TestNode = build_nested_folders(
515+
file_node, created_files_folders_dict, session_node
516+
)
517+
except ValueError:
518+
# This exception is raised when the session node is not a parent of the file node.
519+
print(
520+
"[vscode-pytest]: Session path not a parent of test paths, adjusting session node to common parent."
521+
)
522+
common_parent = os.path.commonpath([file_node["path"], get_node_path(session)])
523+
common_parent_path = pathlib.Path(common_parent)
524+
print("[vscode-pytest]: Session node now set to: ", common_parent)
525+
session_node["path"] = common_parent_path # pathlib.Path
526+
session_node["id_"] = common_parent # str
527+
session_node["name"] = common_parent_path.name # str
528+
root_folder_node = build_nested_folders(
529+
file_node, created_files_folders_dict, session_node
530+
)
515531
# The final folder we get to is the highest folder in the path
516532
# and therefore we add this as a child to the session.
517533
root_id = root_folder_node.get("id_")
@@ -524,7 +540,7 @@ def build_test_tree(session: pytest.Session) -> TestNode:
524540
def build_nested_folders(
525541
file_node: TestNode,
526542
created_files_folders_dict: Dict[str, TestNode],
527-
session: pytest.Session,
543+
session_node: TestNode,
528544
) -> TestNode:
529545
"""Takes a file or folder and builds the nested folder structure for it.
530546
@@ -534,11 +550,23 @@ def build_nested_folders(
534550
created_files_folders_dict -- Dictionary of all the folders and files that have been created where the key is the path.
535551
session -- the pytest session object.
536552
"""
537-
prev_folder_node = file_node
553+
# check if session node is a parent of the file node, throw error if not.
554+
session_node_path = session_node["path"]
555+
is_relative = False
556+
try:
557+
is_relative = file_node["path"].is_relative_to(session_node_path)
558+
except AttributeError:
559+
is_relative = file_node["path"].relative_to(session_node_path)
560+
if not is_relative:
561+
# If the session node is not a parent of the file node, we need to find their common parent.
562+
raise ValueError("session and file not relative to each other, fixing now....")
538563

539564
# Begin the iterator_path one level above the current file.
565+
prev_folder_node = file_node
540566
iterator_path = file_node["path"].parent
541-
while iterator_path != get_node_path(session):
567+
counter = 0
568+
max_iter = 100
569+
while iterator_path != session_node_path:
542570
curr_folder_name = iterator_path.name
543571
try:
544572
curr_folder_node: TestNode = created_files_folders_dict[os.fspath(iterator_path)]
@@ -549,6 +577,15 @@ def build_nested_folders(
549577
curr_folder_node["children"].append(prev_folder_node)
550578
iterator_path = iterator_path.parent
551579
prev_folder_node = curr_folder_node
580+
# Handles error where infinite loop occurs.
581+
counter += 1
582+
if counter > max_iter:
583+
raise ValueError(
584+
"[vscode-pytest]: Infinite loop occurred in build_nested_folders. iterator_path: ",
585+
iterator_path,
586+
"session_node_path: ",
587+
session_node_path,
588+
)
552589
return prev_folder_node
553590

554591

0 commit comments

Comments
 (0)