Skip to content

Resolve bug with session not being a parent of tests while building test tree with pytest #23420

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

[pytest]
python_files =
test_*.py
testpaths =
tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.


def test_hello(): # test_marker--test_hello
assert True
35 changes: 34 additions & 1 deletion python_files/tests/pytestadapter/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,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,
),
(
"test_multi_class_nest.py",
expected_discovery_test_output.nested_classes_expected_test_output,
Expand Down Expand Up @@ -178,7 +182,9 @@ def test_pytest_collect(file, expected_const):
if actual_list is not None:
actual_item = actual_list.pop(0)
assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "success"
assert (
actual_item.get("status") == "success"
), f"Status is not 'success', error is: {actual_item.get('error')}"
assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH)
assert is_same_tree(
actual_item.get("tests"), expected_const
Expand Down Expand Up @@ -276,3 +282,30 @@ def test_pytest_config_file():
actual_item.get("tests"),
expected_discovery_test_output.root_with_config_expected_output,
), 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)}"


def test_config_sub_folder():
"""Here the session node will be a subfolder of the workspace root and the test are in another subfolder.
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."""
folder_path = helpers.TEST_DATA_PATH / "config_sub_folder"
actual = helpers.runner_with_cwd(
[
"--collect-only",
"-c=config/pytest.ini",
"--rootdir=config/",
"-vv",
],
folder_path,
)

assert actual
actual_list: List[Dict[str, Any]] = actual
if actual_list is not None:
actual_item = actual_list.pop(0)
assert all(item in actual_item.keys() for item in ("status", "cwd", "error"))
assert actual_item.get("status") == "success"
assert actual_item.get("cwd") == os.fspath(helpers.TEST_DATA_PATH / "config_sub_folder")
assert actual_item.get("tests") is not None
if actual_item.get("tests") is not None:
tests: Any = actual_item.get("tests")
assert tests.get("name") == "config_sub_folder"
49 changes: 43 additions & 6 deletions python_files/vscode_pytest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,9 +509,25 @@ def build_test_tree(session: pytest.Session) -> TestNode:
created_files_folders_dict: Dict[str, TestNode] = {}
for _, file_node in file_nodes_dict.items():
# Iterate through all the files that exist and construct them into nested folders.
root_folder_node: TestNode = build_nested_folders(
file_node, created_files_folders_dict, session
)
root_folder_node: TestNode
try:
root_folder_node: TestNode = build_nested_folders(
file_node, created_files_folders_dict, session_node
)
except ValueError:
# This exception is raised when the session node is not a parent of the file node.
print(
"[vscode-pytest]: Session path not a parent of test paths, adjusting session node to common parent."
)
common_parent = os.path.commonpath([file_node["path"], get_node_path(session)])
common_parent_path = pathlib.Path(common_parent)
print("[vscode-pytest]: Session node now set to: ", common_parent)
session_node["path"] = common_parent_path # pathlib.Path
session_node["id_"] = common_parent # str
session_node["name"] = common_parent_path.name # str
root_folder_node = build_nested_folders(
file_node, created_files_folders_dict, session_node
)
# The final folder we get to is the highest folder in the path
# and therefore we add this as a child to the session.
root_id = root_folder_node.get("id_")
Expand All @@ -524,7 +540,7 @@ def build_test_tree(session: pytest.Session) -> TestNode:
def build_nested_folders(
file_node: TestNode,
created_files_folders_dict: Dict[str, TestNode],
session: pytest.Session,
session_node: TestNode,
) -> TestNode:
"""Takes a file or folder and builds the nested folder structure for it.

Expand All @@ -534,11 +550,23 @@ def build_nested_folders(
created_files_folders_dict -- Dictionary of all the folders and files that have been created where the key is the path.
session -- the pytest session object.
"""
prev_folder_node = file_node
# check if session node is a parent of the file node, throw error if not.
session_node_path = session_node["path"]
is_relative = False
try:
is_relative = file_node["path"].is_relative_to(session_node_path)
except AttributeError:
is_relative = file_node["path"].relative_to(session_node_path)
if not is_relative:
# If the session node is not a parent of the file node, we need to find their common parent.
raise ValueError("session and file not relative to each other, fixing now....")

# Begin the iterator_path one level above the current file.
prev_folder_node = file_node
iterator_path = file_node["path"].parent
while iterator_path != get_node_path(session):
counter = 0
max_iter = 100
while iterator_path != session_node_path:
curr_folder_name = iterator_path.name
try:
curr_folder_node: TestNode = created_files_folders_dict[os.fspath(iterator_path)]
Expand All @@ -549,6 +577,15 @@ def build_nested_folders(
curr_folder_node["children"].append(prev_folder_node)
iterator_path = iterator_path.parent
prev_folder_node = curr_folder_node
# Handles error where infinite loop occurs.
counter += 1
if counter > max_iter:
raise ValueError(
"[vscode-pytest]: Infinite loop occurred in build_nested_folders. iterator_path: ",
iterator_path,
"session_node_path: ",
session_node_path,
)
return prev_folder_node


Expand Down
Loading