diff --git a/python_files/tests/pytestadapter/test_utils.py b/python_files/tests/pytestadapter/test_utils.py index 70201db7d097..939430e833d2 100644 --- a/python_files/tests/pytestadapter/test_utils.py +++ b/python_files/tests/pytestadapter/test_utils.py @@ -55,3 +55,38 @@ def test_cached_fsdecode(): result3 = cached_fsdecode(test_path2) assert result3 == os.fspath(test_path2) assert result3 != result1 + + +def test_hidden_param_discovery(tmp_path): + """Test that parameterized test with pytest.HIDDEN_PARAM builds tree without error.""" + import pytest + + test_file = tmp_path / "test_hidden.py" + test_file.write_text( + "import pytest\n" + "@pytest.mark.parametrize('setup', [pytest.param('setup', id=pytest.HIDDEN_PARAM)])\n" + "def test_main(setup):\n" + " assert True\n" + ) + + class Collector: + def __init__(self): + self.tree = None + + def pytest_sessionfinish(self, session, exitstatus): # noqa: ARG002 + from vscode_pytest import build_test_tree + + self.tree = build_test_tree(session) + + collector = Collector() + pytest.main(["--collect-only", f"--rootdir={tmp_path}", str(test_file)], plugins=[collector]) + assert collector.tree is not None + file_nodes = list(collector.tree["children"].values()) + assert len(file_nodes) == 1 + file_node = file_nodes[0] + assert file_node["name"] == "test_hidden.py" + children = file_node["children"] + test_nodes = list(children.values() if hasattr(children, "values") else children) + assert len(test_nodes) == 1 + assert test_nodes[0]["name"] == "test_main" + assert test_nodes[0]["type_"] == "test" diff --git a/python_files/vscode_pytest/__init__.py b/python_files/vscode_pytest/__init__.py index 88f6327f6e8b..0446aec1e10f 100644 --- a/python_files/vscode_pytest/__init__.py +++ b/python_files/vscode_pytest/__init__.py @@ -717,7 +717,8 @@ def build_test_tree(session: pytest.Session) -> TestNode: for test_case in session.items: test_node = create_test_node(test_case) - if hasattr(test_case, "callspec"): # This means it is a parameterized test. + is_parameterized = hasattr(test_case, "callspec") and "[" in test_node["name"] + if is_parameterized: # This means it is a parameterized test. # Process parameterized test and get the function node to use for further processing test_node = process_parameterized_test( test_case, test_node, function_nodes_dict, file_nodes_dict @@ -758,7 +759,7 @@ def build_test_tree(session: pytest.Session) -> TestNode: # Check if the class is already a child of the file node. if test_class_node is not None: test_file_node["children"].add(test_class_node) - elif not hasattr(test_case, "callspec"): + elif not is_parameterized: # This includes test cases that are pytest functions or a doctests. if test_case.parent is None: ERRORS.append(f"Test case {test_case.name} has no parent")