@@ -1049,23 +1049,13 @@ def dumpSessionInfo(self):
1049
1049
# it silently replaces the destination. Ultimately this means that atomic renames are not
1050
1050
# guaranteed to be possible on Windows, but we need this to work anyway, so just remove the
1051
1051
# destination first if it already exists.
1052
- os . remove (dst )
1052
+ remove_file (dst )
1053
1053
1054
1054
os .rename (src , dst )
1055
1055
else :
1056
1056
# success! (and we don't want log files) delete log files
1057
1057
for log_file in log_files_for_this_test :
1058
- try :
1059
- os .unlink (log_file )
1060
- except :
1061
- # We've seen consistent unlink failures on Windows, perhaps because the
1062
- # just-created log file is being scanned by anti-virus. Empirically, this
1063
- # sleep-and-retry approach allows tests to succeed much more reliably.
1064
- # Attempts to figure out exactly what process was still holding a file handle
1065
- # have failed because running instrumentation like Process Monitor seems to
1066
- # slow things down enough that the problem becomes much less consistent.
1067
- time .sleep (0.5 )
1068
- os .unlink (log_file )
1058
+ remove_file (log_file )
1069
1059
1070
1060
# ====================================================
1071
1061
# Config. methods supported through a plugin interface
@@ -1996,4 +1986,17 @@ def DebugPExpect(self, child):
1996
1986
@classmethod
1997
1987
def RemoveTempFile (cls , file ):
1998
1988
if os .path .exists (file ):
1989
+ remove_file (file )
1990
+
1991
+ # On Windows, the first attempt to delete a recently-touched file can fail
1992
+ # because of a race with antimalware scanners. This function will detect a
1993
+ # failure and retry.
1994
+ def remove_file (file , num_retries = 1 , sleep_duration = 0.5 ):
1995
+ for i in range (num_retries + 1 ):
1996
+ try :
1999
1997
os .remove (file )
1998
+ return True
1999
+ except :
2000
+ time .sleep (sleep_duration )
2001
+ continue
2002
+ return False
0 commit comments