diff --git a/python_files/tests/pytestadapter/.data/config_sub_folder/config/pytest.ini b/python_files/tests/pytestadapter/.data/config_sub_folder/config/pytest.ini new file mode 100644 index 000000000000..dfac39a723e8 --- /dev/null +++ b/python_files/tests/pytestadapter/.data/config_sub_folder/config/pytest.ini @@ -0,0 +1,8 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +[pytest] +python_files = + test_*.py +testpaths = + tests diff --git a/python_files/tests/pytestadapter/.data/config_sub_folder/tests/test_hello.py b/python_files/tests/pytestadapter/.data/config_sub_folder/tests/test_hello.py new file mode 100644 index 000000000000..2fd5e2b0a309 --- /dev/null +++ b/python_files/tests/pytestadapter/.data/config_sub_folder/tests/test_hello.py @@ -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 diff --git a/python_files/tests/pytestadapter/test_discovery.py b/python_files/tests/pytestadapter/test_discovery.py index e8274bcd9717..e27750dc174c 100644 --- a/python_files/tests/pytestadapter/test_discovery.py +++ b/python_files/tests/pytestadapter/test_discovery.py @@ -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, @@ -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 @@ -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" diff --git a/python_files/vscode_pytest/__init__.py b/python_files/vscode_pytest/__init__.py index 34ff903e64d9..1d855232cbd8 100644 --- a/python_files/vscode_pytest/__init__.py +++ b/python_files/vscode_pytest/__init__.py @@ -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_") @@ -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. @@ -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)] @@ -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