@@ -80,6 +80,11 @@ def __init__(self, message):
8080SYMLINK_PATH = None
8181INCLUDE_BRANCHES = False
8282
83+ # Performance optimization caches for path resolution
84+ _path_cache : dict [int , pathlib .Path ] = {} # Cache node paths by object id
85+ _path_to_str_cache : dict [pathlib .Path , str ] = {} # Cache path-to-string conversions
86+ _CACHED_CWD : pathlib .Path | None = None
87+
8388
8489def pytest_load_initial_conftests (early_config , parser , args ): # noqa: ARG001
8590 has_pytest_cov = early_config .pluginmanager .hasplugin ("pytest_cov" )
@@ -619,20 +624,21 @@ def process_parameterized_test(
619624
620625 class_and_method = second_split [1 ] + "::" # This has "::" separator at both ends
621626 # construct the parent id, so it is absolute path :: any class and method :: parent_part
622- parent_id = os . fspath (get_node_path (test_case )) + class_and_method + parent_part
627+ parent_id = cached_fsdecode (get_node_path (test_case )) + class_and_method + parent_part
623628
624629 try :
625630 function_name = test_case .originalname # type: ignore
626- function_test_node = function_nodes_dict [parent_id ]
627631 except AttributeError : # actual error has occurred
628632 ERRORS .append (
629633 f"unable to find original name for { test_case .name } with parameterization detected."
630634 )
631635 raise VSCodePytestError (
632636 "Unable to find original name for parameterized test case"
633637 ) from None
634- except KeyError :
635- function_test_node : TestNode = create_parameterized_function_node (
638+
639+ function_test_node = function_nodes_dict .get (parent_id )
640+ if function_test_node is None :
641+ function_test_node = create_parameterized_function_node (
636642 function_name , get_node_path (test_case ), parent_id
637643 )
638644 function_nodes_dict [parent_id ] = function_test_node
@@ -644,11 +650,11 @@ def process_parameterized_test(
644650 if isinstance (test_case .parent , pytest .File ):
645651 # calculate the parent path of the test case
646652 parent_path = get_node_path (test_case .parent )
647- try :
648- parent_test_case = file_nodes_dict [ os . fspath ( parent_path )]
649- except KeyError :
653+ parent_path_key = cached_fsdecode ( parent_path )
654+ parent_test_case = file_nodes_dict . get ( parent_path_key )
655+ if parent_test_case is None :
650656 parent_test_case = create_file_node (parent_path )
651- file_nodes_dict [os . fspath ( parent_path ) ] = parent_test_case
657+ file_nodes_dict [parent_path_key ] = parent_test_case
652658 if function_test_node not in parent_test_case ["children" ]:
653659 parent_test_case ["children" ].append (function_test_node )
654660
@@ -693,9 +699,8 @@ def build_test_tree(session: pytest.Session) -> TestNode:
693699 USES_PYTEST_DESCRIBE and isinstance (case_iter , DescribeBlock )
694700 ):
695701 # While the given node is a class, create a class and nest the previous node as a child.
696- try :
697- test_class_node = class_nodes_dict [case_iter .nodeid ]
698- except KeyError :
702+ test_class_node = class_nodes_dict .get (case_iter .nodeid )
703+ if test_class_node is None :
699704 test_class_node = create_class_node (case_iter )
700705 class_nodes_dict [case_iter .nodeid ] = test_class_node
701706 # Check if the class already has the child node. This will occur if the test is parameterized.
@@ -712,11 +717,11 @@ def build_test_tree(session: pytest.Session) -> TestNode:
712717 break
713718 parent_path = get_node_path (parent_module )
714719 # Create a file node that has the last class as a child.
715- try :
716- test_file_node : TestNode = file_nodes_dict [ os . fspath ( parent_path )]
717- except KeyError :
720+ parent_path_key = cached_fsdecode ( parent_path )
721+ test_file_node = file_nodes_dict . get ( parent_path_key )
722+ if test_file_node is None :
718723 test_file_node = create_file_node (parent_path )
719- file_nodes_dict [os . fspath ( parent_path ) ] = test_file_node
724+ file_nodes_dict [parent_path_key ] = test_file_node
720725 # Check if the class is already a child of the file node.
721726 if test_class_node is not None and test_class_node not in test_file_node ["children" ]:
722727 test_file_node ["children" ].append (test_class_node )
@@ -731,11 +736,11 @@ def build_test_tree(session: pytest.Session) -> TestNode:
731736 test_case .parent ,
732737 )
733738 )
734- try :
735- parent_test_case = file_nodes_dict [ os . fspath ( parent_path )]
736- except KeyError :
739+ parent_path_key = cached_fsdecode ( parent_path )
740+ parent_test_case = file_nodes_dict . get ( parent_path_key )
741+ if parent_test_case is None :
737742 parent_test_case = create_file_node (parent_path )
738- file_nodes_dict [os . fspath ( parent_path ) ] = parent_test_case
743+ file_nodes_dict [parent_path_key ] = parent_test_case
739744 parent_test_case ["children" ].append (test_node )
740745 # Process all files and construct them into nested folders
741746 session_children_dict = construct_nested_folders (
@@ -776,11 +781,11 @@ def build_nested_folders(
776781 max_iter = 100
777782 while iterator_path != session_node_path :
778783 curr_folder_name = iterator_path .name
779- try :
780- curr_folder_node : TestNode = created_files_folders_dict [ os . fspath ( iterator_path )]
781- except KeyError :
782- curr_folder_node : TestNode = create_folder_node (curr_folder_name , iterator_path )
783- created_files_folders_dict [os . fspath ( iterator_path ) ] = curr_folder_node
784+ iterator_path_key = cached_fsdecode ( iterator_path )
785+ curr_folder_node = created_files_folders_dict . get ( iterator_path_key )
786+ if curr_folder_node is None :
787+ curr_folder_node = create_folder_node (curr_folder_name , iterator_path )
788+ created_files_folders_dict [iterator_path_key ] = curr_folder_node
784789 if prev_folder_node not in curr_folder_node ["children" ]:
785790 curr_folder_node ["children" ].append (prev_folder_node )
786791 iterator_path = iterator_path .parent
@@ -942,6 +947,23 @@ class CoveragePayloadDict(Dict):
942947 error : str | None # Currently unused need to check
943948
944949
950+ def cached_fsdecode (path : pathlib .Path ) -> str :
951+ """Convert path to string with caching for performance.
952+
953+ This function caches path-to-string conversions to avoid redundant
954+ os.fsdecode() calls during test tree building.
955+
956+ Parameters:
957+ path: The pathlib.Path object to convert to string.
958+
959+ Returns:
960+ str: The string representation of the path.
961+ """
962+ if path not in _path_to_str_cache :
963+ _path_to_str_cache [path ] = os .fspath (path )
964+ return _path_to_str_cache [path ]
965+
966+
945967def get_node_path (
946968 node : pytest .Session
947969 | pytest .Item
@@ -961,6 +983,10 @@ def get_node_path(
961983 Returns:
962984 pathlib.Path: The resolved path for the node.
963985 """
986+ cache_key = id (node )
987+ if cache_key in _path_cache :
988+ return _path_cache [cache_key ]
989+
964990 node_path = getattr (node , "path" , None )
965991 if node_path is None :
966992 fspath = getattr (node , "fspath" , None )
@@ -982,19 +1008,28 @@ def get_node_path(
9821008 common_path = os .path .commonpath ([symlink_str , node_path_str ])
9831009 if common_path == os .fsdecode (SYMLINK_PATH ):
9841010 # The node path is already relative to the SYMLINK_PATH root therefore return
985- return node_path
1011+ result = node_path
9861012 else :
9871013 # If the node path is not a symlink, then we need to calculate the equivalent symlink path
9881014 # get the relative path between the cwd and the node path (as the node path is not a symlink).
989- rel_path = node_path .relative_to (pathlib .Path .cwd ())
1015+ # Use cached cwd to avoid repeated system calls
1016+ global _CACHED_CWD
1017+ if _CACHED_CWD is None :
1018+ _CACHED_CWD = pathlib .Path .cwd ()
1019+ rel_path = node_path .relative_to (_CACHED_CWD )
9901020 # combine the difference between the cwd and the node path with the symlink path
991- return pathlib .Path (SYMLINK_PATH , rel_path )
1021+ result = pathlib .Path (SYMLINK_PATH , rel_path )
9921022 except Exception as e :
9931023 raise VSCodePytestError (
9941024 f"Error occurred while calculating symlink equivalent from node path: { e } "
995- f"\n SYMLINK_PATH: { SYMLINK_PATH } , \n node path: { node_path } , \n cwd: { pathlib .Path .cwd ()} "
1025+ f"\n SYMLINK_PATH: { SYMLINK_PATH } , \n node path: { node_path } , \n cwd: { _CACHED_CWD if _CACHED_CWD else pathlib .Path .cwd ()} "
9961026 ) from e
997- return node_path
1027+ else :
1028+ result = node_path
1029+
1030+ # Cache before returning
1031+ _path_cache [cache_key ] = result
1032+ return result
9981033
9991034
10001035__writer = None
0 commit comments