11from __future__ import annotations
22
3+ import contextlib
4+ import inspect
5+
36# System Imports
47import logging
58import os
69import re
10+ import sys
711import time
812import warnings
9- from typing import TYPE_CHECKING
13+ from typing import TYPE_CHECKING , Any , Callable
1014from unittest import TestCase
1115
1216# PyTest Imports
@@ -133,6 +137,9 @@ def pytest_runtestloop(self, session: Session) -> bool:
133137 item ._nodeid = self ._set_nodeid (item ._nodeid , count )
134138
135139 next_item : pytest .Item = session .items [index + 1 ] if index + 1 < len (session .items ) else None
140+
141+ self ._clear_lru_caches (item )
142+
136143 item .config .hook .pytest_runtest_protocol (item = item , nextitem = next_item )
137144 if session .shouldfail :
138145 raise session .Failed (session .shouldfail )
@@ -143,6 +150,59 @@ def pytest_runtestloop(self, session: Session) -> bool:
143150 time .sleep (self ._get_delay_time (session ))
144151 return True
145152
153+ def _clear_lru_caches (self , item : pytest .Item ) -> None :
154+ processed_functions : set [Callable ] = set ()
155+ protected_modules = {
156+ "gc" ,
157+ "inspect" ,
158+ "os" ,
159+ "sys" ,
160+ "time" ,
161+ "functools" ,
162+ "pathlib" ,
163+ "typing" ,
164+ "dill" ,
165+ "pytest" ,
166+ "importlib" ,
167+ }
168+
169+ def _clear_cache_for_object (obj : Any ) -> None : # noqa: ANN401
170+ if obj in processed_functions :
171+ return
172+ processed_functions .add (obj )
173+
174+ if hasattr (obj , "__wrapped__" ):
175+ module_name = obj .__wrapped__ .__module__
176+ else :
177+ try :
178+ obj_module = inspect .getmodule (obj )
179+ module_name = obj_module .__name__ .split ("." )[0 ] if obj_module is not None else None
180+ except Exception : # noqa: BLE001
181+ module_name = None
182+
183+ if module_name in protected_modules :
184+ return
185+
186+ if hasattr (obj , "cache_clear" ) and callable (obj .cache_clear ):
187+ with contextlib .suppress (Exception ):
188+ obj .cache_clear ()
189+
190+ _clear_cache_for_object (item .function ) # type: ignore[attr-defined]
191+
192+ try :
193+ if hasattr (item .function , "__module__" ): # type: ignore[attr-defined]
194+ module_name = item .function .__module__ # type: ignore[attr-defined]
195+ try :
196+ module = sys .modules .get (module_name )
197+ if module :
198+ for _ , obj in inspect .getmembers (module ):
199+ if callable (obj ):
200+ _clear_cache_for_object (obj )
201+ except Exception : # noqa: BLE001, S110
202+ pass
203+ except Exception : # noqa: BLE001, S110
204+ pass
205+
146206 def _set_nodeid (self , nodeid : str , count : int ) -> str :
147207 """Set loop count when using duration.
148208
@@ -205,8 +265,7 @@ def __pytest_loop_step_number(self, request: pytest.FixtureRequest) -> int:
205265 warnings .warn ("Repeating unittest class tests not supported" )
206266 else :
207267 raise UnexpectedError (
208- "This call couldn't work with pytest-loops. "
209- "Please consider raising an issue with your usage."
268+ "This call couldn't work with pytest-loops. Please consider raising an issue with your usage."
210269 )
211270 return count
212271
@@ -226,7 +285,7 @@ def pytest_generate_tests(self, metafunc: Metafunc) -> None:
226285 metafunc .fixturenames .append ("__pytest_loop_step_number" )
227286
228287 def make_progress_id (i : int , n : int = count ) -> str :
229- return f"{ n } /{ i + 1 } "
288+ return f"{ n } /{ i + 1 } "
230289
231290 scope = metafunc .config .option .codeflash_loops_scope
232291 metafunc .parametrize (
0 commit comments