@@ -61,10 +61,21 @@ class TestItem(TestData):
6161class TestNode (TestData ):
6262 """A general class that handles all test data which contains children."""
6363
64- children : list [ TestNode | TestItem | None ]
64+ children : Children
6565 lineno : NotRequired [str ] # Optional field for class/function nodes
6666
6767
68+ class Children :
69+ def __init__ (self , init = None ):
70+ self ._children = dict (init ) if init is not None else {}
71+
72+ def add (self , child : TestNode | TestItem ):
73+ self ._children [child ["id_" ]] = child
74+
75+ def values (self ):
76+ return list (self ._children .values ())
77+
78+
6879class VSCodePytestError (Exception ):
6980 """A custom exception class for pytest errors."""
7081
@@ -439,7 +450,7 @@ def pytest_sessionfinish(session, exitstatus):
439450 "name" : "" ,
440451 "path" : test_root_path ,
441452 "type_" : "error" ,
442- "children" : [] ,
453+ "children" : Children () ,
443454 "id_" : "" ,
444455 }
445456 send_discovery_message (os .fsdecode (test_root_path ), error_node )
@@ -459,7 +470,7 @@ def pytest_sessionfinish(session, exitstatus):
459470 "name" : "" ,
460471 "path" : test_root_path ,
461472 "type_" : "error" ,
462- "children" : [] ,
473+ "children" : Children () ,
463474 "id_" : "" ,
464475 }
465476 send_discovery_message (os .fsdecode (test_root_path ), error_node )
@@ -664,8 +675,7 @@ def process_parameterized_test(
664675 )
665676 function_nodes_dict [parent_id ] = function_test_node
666677
667- if test_node not in function_test_node ["children" ]:
668- function_test_node ["children" ].append (test_node )
678+ function_test_node ["children" ].add (test_node )
669679
670680 # Check if the parent node of the function is file, if so create/add to this file node.
671681 if isinstance (test_case .parent , pytest .File ):
@@ -676,8 +686,7 @@ def process_parameterized_test(
676686 if parent_test_case is None :
677687 parent_test_case = create_file_node (parent_path )
678688 file_nodes_dict [parent_path_key ] = parent_test_case
679- if function_test_node not in parent_test_case ["children" ]:
680- parent_test_case ["children" ].append (function_test_node )
689+ parent_test_case ["children" ].add (function_test_node )
681690
682691 # Return the function node as the test node to handle subsequent nesting
683692 return function_test_node
@@ -725,8 +734,7 @@ def build_test_tree(session: pytest.Session) -> TestNode:
725734 test_class_node = create_class_node (case_iter )
726735 class_nodes_dict [case_iter .nodeid ] = test_class_node
727736 # Check if the class already has the child node. This will occur if the test is parameterized.
728- if node_child_iter not in test_class_node ["children" ]:
729- test_class_node ["children" ].append (node_child_iter )
737+ test_class_node ["children" ].add (node_child_iter )
730738 # Iterate up.
731739 node_child_iter = test_class_node
732740 case_iter = case_iter .parent
@@ -744,8 +752,8 @@ def build_test_tree(session: pytest.Session) -> TestNode:
744752 test_file_node = create_file_node (parent_path )
745753 file_nodes_dict [parent_path_key ] = test_file_node
746754 # Check if the class is already a child of the file node.
747- if test_class_node is not None and test_class_node not in test_file_node [ "children" ] :
748- test_file_node ["children" ].append (test_class_node )
755+ if test_class_node is not None :
756+ test_file_node ["children" ].add (test_class_node )
749757 elif not hasattr (test_case , "callspec" ):
750758 # This includes test cases that are pytest functions or a doctests.
751759 if test_case .parent is None :
@@ -762,12 +770,13 @@ def build_test_tree(session: pytest.Session) -> TestNode:
762770 if parent_test_case is None :
763771 parent_test_case = create_file_node (parent_path )
764772 file_nodes_dict [parent_path_key ] = parent_test_case
765- parent_test_case ["children" ].append (test_node )
773+ parent_test_case ["children" ].add (test_node )
766774 # Process all files and construct them into nested folders
767775 session_children_dict = construct_nested_folders (
768776 file_nodes_dict , session_node , session_children_dict
769777 )
770- session_node ["children" ] = list (session_children_dict .values ())
778+ session_node ["children" ] = Children (session_children_dict )
779+
771780 return session_node
772781
773782
@@ -807,8 +816,7 @@ def build_nested_folders(
807816 if curr_folder_node is None :
808817 curr_folder_node = create_folder_node (curr_folder_name , iterator_path )
809818 created_files_folders_dict [iterator_path_key ] = curr_folder_node
810- if prev_folder_node not in curr_folder_node ["children" ]:
811- curr_folder_node ["children" ].append (prev_folder_node )
819+ curr_folder_node ["children" ].add (prev_folder_node )
812820 iterator_path = iterator_path .parent
813821 prev_folder_node = curr_folder_node
814822 # Handles error where infinite loop occurs.
@@ -857,7 +865,7 @@ def create_session_node(session: pytest.Session) -> TestNode:
857865 "name" : node_path .name ,
858866 "path" : node_path ,
859867 "type_" : "folder" ,
860- "children" : [] ,
868+ "children" : Children () ,
861869 "id_" : os .fspath (node_path ),
862870 }
863871
@@ -884,7 +892,7 @@ def create_class_node(class_module: pytest.Class | DescribeBlock) -> TestNode:
884892 "name" : class_module .name ,
885893 "path" : get_node_path (class_module ),
886894 "type_" : "class" ,
887- "children" : [] ,
895+ "children" : Children () ,
888896 "id_" : get_absolute_test_id (class_module .nodeid , get_node_path (class_module )),
889897 "lineno" : class_line ,
890898 }
@@ -905,7 +913,7 @@ def create_parameterized_function_node(
905913 "name" : function_name ,
906914 "path" : test_path ,
907915 "type_" : "function" ,
908- "children" : [] ,
916+ "children" : Children () ,
909917 "id_" : function_id ,
910918 }
911919
@@ -921,7 +929,7 @@ def create_file_node(calculated_node_path: pathlib.Path) -> TestNode:
921929 "path" : calculated_node_path ,
922930 "type_" : "file" ,
923931 "id_" : os .fspath (calculated_node_path ),
924- "children" : [] ,
932+ "children" : Children () ,
925933 }
926934
927935
@@ -937,7 +945,7 @@ def create_folder_node(folder_name: str, path_iterator: pathlib.Path) -> TestNod
937945 "path" : path_iterator ,
938946 "type_" : "folder" ,
939947 "id_" : os .fspath (path_iterator ),
940- "children" : [] ,
948+ "children" : Children () ,
941949 }
942950
943951
@@ -1092,15 +1100,20 @@ def send_discovery_message(cwd: str, session_node: TestNode) -> None:
10921100 }
10931101 if ERRORS is not None :
10941102 payload ["error" ] = ERRORS
1095- send_message (payload , cls_encoder = PathEncoder )
1103+ send_message (payload , cls_encoder = CustomEncoder )
1104+
10961105
1106+ class CustomEncoder (json .JSONEncoder ):
1107+ """JSON encoder for pytest discovery payloads.
10971108
1098- class PathEncoder ( json . JSONEncoder ):
1099- """A custom JSON encoder that encodes pathlib.Path objects as strings."""
1109+ Encodes `pathlib.Path` as strings and `Children` containers as JSON arrays.
1110+ """
11001111
11011112 def default (self , o ):
11021113 if isinstance (o , pathlib .Path ):
11031114 return os .fspath (o )
1115+ if isinstance (o , Children ):
1116+ return o .values ()
11041117 return super ().default (o )
11051118
11061119
0 commit comments