@@ -173,190 +173,149 @@ def test_verify_binary_nonexistent_file(self):
173173 def test_replace_executable_success (self ):
174174 """Test successful executable replacement."""
175175 updater = ExecutableUpdater ("test-owner" , "test-repo" )
176-
177- # Create temporary files
176+
178177 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as old_file :
179178 old_file .write ("old executable" )
180179 old_path = old_file .name
181-
180+
182181 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as new_file :
183182 new_file .write ("new executable" )
184183 new_path = new_file .name
185-
186- backup_path = f"{ old_path } .backup "
187-
184+
185+ old_bak = f"{ old_path } .old "
186+
188187 try :
189- # Replace executable
190188 updater .replace_executable (new_path , old_path )
191-
192- # Verify new content
189+
193190 with open (old_path , 'r' ) as f :
194191 content = f .read ()
195192 assert content == "new executable"
196-
197- # Verify backup was removed
198- assert not os .path .exists (backup_path )
199-
200- # Verify new binary was moved (not copied)
193+
194+ # .old file cleaned up on success
195+ assert not os .path .exists (old_bak )
196+ # new binary was moved, not copied
201197 assert not os .path .exists (new_path )
202-
198+
203199 finally :
204- # Cleanup
205- if os .path .exists (old_path ):
206- os .remove (old_path )
207- if os .path .exists (new_path ):
208- os .remove (new_path )
209- if os .path .exists (backup_path ):
210- os .remove (backup_path )
211-
212- def test_replace_executable_creates_backup (self ):
213- """Test that backup is created before replacement."""
200+ for p in [old_path , new_path , old_bak ]:
201+ if os .path .exists (p ):
202+ os .remove (p )
203+
204+ def test_replace_executable_uses_rename_on_windows (self ):
205+ """On Windows the old exe is renamed before placing the new one (avoids lock)."""
214206 updater = ExecutableUpdater ("test-owner" , "test-repo" )
215-
216- # Create temporary files
207+
217208 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as old_file :
218209 old_file .write ("old executable" )
219210 old_path = old_file .name
220-
211+
221212 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as new_file :
222213 new_file .write ("new executable" )
223214 new_path = new_file .name
224-
225- backup_path = f"{ old_path } .backup"
226-
227- # Patch shutil.copy2 to verify backup is created
228- original_copy2 = __import__ ('shutil' ).copy2
229- copy2_called = [False ]
230-
231- def mock_copy2 (src , dst ):
232- copy2_called [0 ] = True
233- return original_copy2 (src , dst )
234-
215+
216+ old_bak = f"{ old_path } .old"
217+ rename_calls = []
218+ original_rename = os .rename
219+
220+ def tracking_rename (src , dst ):
221+ rename_calls .append ((src , dst ))
222+ return original_rename (src , dst )
223+
235224 try :
236- with patch ('shutil.copy2 ' , side_effect = mock_copy2 ):
225+ with patch ('os.name ' , 'nt' ), patch ( 'os.rename' , side_effect = tracking_rename ):
237226 updater .replace_executable (new_path , old_path )
238-
239- # Verify backup was created during the process
240- assert copy2_called [ 0 ], "Backup creation (copy2) was not called"
241-
242- # Verify final state: new content in place, backup removed
227+
228+ # rename should have been called to move old exe out of the way
229+ assert any ( dst == old_bak for _ , dst in rename_calls ), \
230+ "Expected old exe to be renamed to .old before replacement"
231+
243232 with open (old_path , 'r' ) as f :
244- content = f .read ()
245- assert content == "new executable"
246- assert not os .path .exists (backup_path )
247-
233+ assert f .read () == "new executable"
234+
248235 finally :
249- # Cleanup
250- if os .path .exists (old_path ):
251- os .remove (old_path )
252- if os .path .exists (new_path ):
253- os .remove (new_path )
254- if os .path .exists (backup_path ):
255- os .remove (backup_path )
236+ for p in [old_path , new_path , old_bak ]:
237+ if os .path .exists (p ):
238+ os .remove (p )
256239
257240 def test_replace_executable_restores_on_failure (self ):
258- """Test that backup is restored when replacement fails."""
241+ """Test that old exe is restored when replacement fails."""
259242 updater = ExecutableUpdater ("test-owner" , "test-repo" )
260-
261- # Create temporary files
243+
262244 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as old_file :
263245 old_file .write ("original content" )
264246 old_path = old_file .name
265-
247+
266248 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as new_file :
267249 new_file .write ("new content" )
268250 new_path = new_file .name
269-
270- backup_path = f"{ old_path } .backup "
271-
251+
252+ old_bak = f"{ old_path } .old "
253+
272254 try :
273- # Patch shutil.move to fail during replacement
274- with patch ('shutil.move' , side_effect = OSError ("Simulated disk full" )):
255+ with patch ('os.name' , 'nt' ), patch ('shutil.move' , side_effect = OSError ("Simulated disk full" )):
275256 with pytest .raises (UpdateError ) as exc_info :
276257 updater .replace_executable (new_path , old_path )
277-
258+
278259 assert "Failed to replace executable" in str (exc_info .value )
279-
280- # Verify original file still exists (backup was removed after restore)
260+
261+ # Original file should be restored
281262 assert os .path .exists (old_path )
282263 with open (old_path , 'r' ) as f :
283- content = f .read ()
284- assert content == "original content"
285-
286- # Verify backup was removed after restore
287- assert not os .path .exists (backup_path )
288-
264+ assert f .read () == "original content"
265+
266+ assert not os .path .exists (old_bak )
267+
289268 finally :
290- # Cleanup
291- if os .path .exists (old_path ):
292- os .remove (old_path )
293- if os .path .exists (new_path ):
294- os .remove (new_path )
295- if os .path .exists (backup_path ):
296- os .remove (backup_path )
269+ for p in [old_path , new_path , old_bak ]:
270+ if os .path .exists (p ):
271+ os .remove (p )
297272
298273 @patch ('os.name' , 'posix' )
299274 @patch ('os.chmod' )
300275 def test_replace_executable_sets_permissions_unix (self , mock_chmod ):
301276 """Test that executable permissions are set on Unix systems."""
302277 updater = ExecutableUpdater ("test-owner" , "test-repo" )
303-
304- # Create temporary files
278+
305279 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as old_file :
306280 old_file .write ("old" )
307281 old_path = old_file .name
308-
282+
309283 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as new_file :
310284 new_file .write ("new" )
311285 new_path = new_file .name
312-
286+
313287 try :
314288 updater .replace_executable (new_path , old_path )
315-
316- # Verify chmod was called
317289 assert mock_chmod .called
318-
290+
319291 finally :
320- # Cleanup
321- if os .path .exists (old_path ):
322- os .remove (old_path )
323- if os .path .exists (new_path ):
324- os .remove (new_path )
325- if os .path .exists (f"{ old_path } .backup" ):
326- os .remove (f"{ old_path } .backup" )
292+ for p in [old_path , new_path , f"{ old_path } .old" ]:
293+ if os .path .exists (p ):
294+ os .remove (p )
327295
328296 def test_replace_executable_cleans_up_on_failure (self ):
329297 """Test that temporary files are cleaned up when replacement fails."""
330298 updater = ExecutableUpdater ("test-owner" , "test-repo" )
331-
332- # Create temporary files
299+
333300 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as old_file :
334301 old_file .write ("old" )
335302 old_path = old_file .name
336-
303+
337304 with tempfile .NamedTemporaryFile (delete = False , mode = 'w' ) as new_file :
338305 new_file .write ("new" )
339306 new_path = new_file .name
340-
341- backup_path = f"{ old_path } .backup "
342-
307+
308+ old_bak = f"{ old_path } .old "
309+
343310 try :
344- # Force failure during replacement
345- with patch ('shutil.move' , side_effect = OSError ("Disk full" )):
311+ with patch ('os.name' , 'nt' ), patch ('shutil.move' , side_effect = OSError ("Disk full" )):
346312 with pytest .raises (UpdateError ):
347313 updater .replace_executable (new_path , old_path )
348-
349- # Verify cleanup: backup should be removed after restore
350- assert not os .path .exists (backup_path )
351-
352- # Original file should be restored
314+
315+ assert not os .path .exists (old_bak )
353316 assert os .path .exists (old_path )
354-
317+
355318 finally :
356- # Cleanup
357- if os .path .exists (old_path ):
358- os .remove (old_path )
359- if os .path .exists (new_path ):
360- os .remove (new_path )
361- if os .path .exists (backup_path ):
362- os .remove (backup_path )
319+ for p in [old_path , new_path , old_bak ]:
320+ if os .path .exists (p ):
321+ os .remove (p )
0 commit comments