@@ -783,31 +783,70 @@ def test_complete_multi_phase_init_module(self):
783783 self .run_with_own_gil (script )
784784
785785
786- class MiscTests (unittest .TestCase ):
787- def test_atomic_write_should_notice_incomplete_writes (self ):
786+ class PatchAtomicWrites :
787+ def __init__ (self , truncate_at_length , never_complete = False ):
788+ self .truncate_at_length = truncate_at_length
789+ self .never_complete = never_complete
790+ self .seen_write = False
791+ self ._children = []
792+
793+ def __enter__ (self ):
788794 import _pyio
789795
790796 oldwrite = os .write
791- seen_write = False
792-
793- truncate_at_length = 100
794797
795798 # Emulate an os.write that only writes partial data.
796799 def write (fd , data ):
797- nonlocal seen_write
798- seen_write = True
799- return oldwrite (fd , data [:truncate_at_length ])
800+ if self .seen_write and self .never_complete :
801+ return None
802+ self .seen_write = True
803+ return oldwrite (fd , data [:self .truncate_at_length ])
800804
801805 # Need to patch _io to be _pyio, so that io.FileIO is affected by the
802806 # os.write patch.
803- with (support .swap_attr (_bootstrap_external , '_io' , _pyio ),
804- support .swap_attr (os , 'write' , write )):
805- with self .assertRaises (OSError ):
806- # Make sure we write something longer than the point where we
807- # truncate.
808- content = b'x' * (truncate_at_length * 2 )
809- _bootstrap_external ._write_atomic (os_helper .TESTFN , content )
810- assert seen_write
807+ self .children = [
808+ support .swap_attr (_bootstrap_external , '_io' , _pyio ),
809+ support .swap_attr (os , 'write' , write )
810+ ]
811+ for child in self .children :
812+ child .__enter__ ()
813+ return self
814+
815+ def __exit__ (self , exc_type , exc_val , exc_tb ):
816+ for child in self .children :
817+ child .__exit__ (exc_type , exc_val , exc_tb )
818+
819+
820+ class MiscTests (unittest .TestCase ):
821+
822+ def test_atomic_write_retries_incomplete_writes (self ):
823+ truncate_at_length = 100
824+ length = truncate_at_length * 2
825+
826+ with PatchAtomicWrites (truncate_at_length = truncate_at_length ) as cm :
827+ # Make sure we write something longer than the point where we
828+ # truncate.
829+ content = b'x' * length
830+ _bootstrap_external ._write_atomic (os_helper .TESTFN , content )
831+ self .assertTrue (cm .seen_write )
832+
833+ self .assertEqual (os .stat (support .os_helper .TESTFN ).st_size , length )
834+ os .unlink (support .os_helper .TESTFN )
835+
836+ def test_atomic_write_errors_if_unable_to_complete (self ):
837+ truncate_at_length = 100
838+
839+ with (
840+ PatchAtomicWrites (
841+ truncate_at_length = truncate_at_length , never_complete = True ,
842+ ) as cm ,
843+ self .assertRaises (OSError )
844+ ):
845+ # Make sure we write something longer than the point where we
846+ # truncate.
847+ content = b'x' * (truncate_at_length * 2 )
848+ _bootstrap_external ._write_atomic (os_helper .TESTFN , content )
849+ self .assertTrue (cm .seen_write )
811850
812851 with self .assertRaises (OSError ):
813852 os .stat (support .os_helper .TESTFN ) # Check that the file did not get written.
0 commit comments