@@ -748,7 +748,7 @@ def check_statx_attributes(self, filename):
748748 if name .startswith ('STATX_' ):
749749 maximal_mask |= getattr (os , name )
750750 result = os .statx (filename , maximal_mask )
751- basic_result = os .stat (filename )
751+ stat_result = os .stat (filename )
752752
753753 time_attributes = ('stx_atime' , 'stx_btime' , 'stx_ctime' , 'stx_mtime' )
754754 # gh-83714: stx_btime can be None on tmpfs even if STATX_BTIME mask
@@ -757,62 +757,108 @@ def check_statx_attributes(self, filename):
757757 if getattr (result , name ) is not None ]
758758 self .check_timestamp_agreement (result , time_attributes )
759759
760- # Check that valid attributes match os.stat.
760+ def getmask (name ):
761+ return getattr (os , name , 0 )
762+
761763 requirements = (
762- ('stx_mode' , os .STATX_TYPE | os .STATX_MODE ),
763- ('stx_nlink' , os .STATX_NLINK ),
764- ('stx_uid' , os .STATX_UID ),
765- ('stx_gid' , os .STATX_GID ),
766764 ('stx_atime' , os .STATX_ATIME ),
767765 ('stx_atime_ns' , os .STATX_ATIME ),
768- ('stx_mtime' , os .STATX_MTIME ),
769- ('stx_mtime_ns' , os .STATX_MTIME ),
766+ ('stx_atomic_write_segments_max' , getmask ('STATX_WRITE_ATOMIC' )),
767+ ('stx_atomic_write_unit_max' , getmask ('STATX_WRITE_ATOMIC' )),
768+ ('stx_atomic_write_unit_max_opt' , getmask ('STATX_WRITE_ATOMIC' )),
769+ ('stx_atomic_write_unit_min' , getmask ('STATX_WRITE_ATOMIC' )),
770+ ('stx_attributes' , 0 ),
771+ ('stx_attributes_mask' , 0 ),
772+ ('stx_blksize' , 0 ),
773+ ('stx_blocks' , os .STATX_BLOCKS ),
774+ ('stx_btime' , os .STATX_BTIME ),
775+ ('stx_btime_ns' , os .STATX_BTIME ),
770776 ('stx_ctime' , os .STATX_CTIME ),
771777 ('stx_ctime_ns' , os .STATX_CTIME ),
778+ ('stx_dev' , 0 ),
779+ ('stx_dev_major' , 0 ),
780+ ('stx_dev_minor' , 0 ),
781+ ('stx_dio_mem_align' , getmask ('STATX_DIOALIGN' )),
782+ ('stx_dio_offset_align' , getmask ('STATX_DIOALIGN' )),
783+ ('stx_dio_read_offset_align' , getmask ('STATX_DIO_READ_ALIGN' )),
784+ ('stx_gid' , os .STATX_GID ),
772785 ('stx_ino' , os .STATX_INO ),
773- ('stx_size ' , os . STATX_SIZE ),
774- ('stx_blocks ' , os . STATX_BLOCKS ),
775- ('stx_birthtime ' , os .STATX_BTIME ),
776- ('stx_birthtime_ns ' , os .STATX_BTIME ),
777- # unconditionally valid members
778- ('stx_blksize ' , 0 ),
786+ ('stx_mask ' , 0 ),
787+ ('stx_mnt_id ' , getmask ( 'STATX_MNT_ID' ) ),
788+ ('stx_mode ' , os .STATX_TYPE | os . STATX_MODE ),
789+ ('stx_mtime ' , os .STATX_MTIME ),
790+ ( 'stx_mtime_ns' , os . STATX_MTIME ),
791+ ('stx_nlink ' , os . STATX_NLINK ),
779792 ('stx_rdev' , 0 ),
780- ('stx_dev' , 0 ),
793+ ('stx_rdev_major' , 0 ),
794+ ('stx_rdev_minor' , 0 ),
795+ ('stx_size' , os .STATX_SIZE ),
796+ ('stx_subvol' , getmask ('STATX_SUBVOL' )),
797+ ('stx_uid' , os .STATX_UID ),
781798 )
782- for name , bits in requirements :
783- st_name = "st_" + name [4 :]
784- if result .stx_mask & bits == bits and hasattr (basic_result , st_name ):
785- x = getattr (result , name )
786- b = getattr (basic_result , st_name )
787- self .assertEqual (type (x ), type (b ))
788- if isinstance (x , float ):
789- self .assertAlmostEqual (x , b , msg = name )
799+ optional_members = {
800+ 'stx_atomic_write_segments_max' ,
801+ 'stx_atomic_write_unit_max' ,
802+ 'stx_atomic_write_unit_max_opt' ,
803+ 'stx_atomic_write_unit_min' ,
804+ 'stx_dio_mem_align' ,
805+ 'stx_dio_offset_align' ,
806+ 'stx_dio_read_offset_align' ,
807+ 'stx_mnt_id' ,
808+ 'stx_subvol' ,
809+ }
810+ float_type = {
811+ 'stx_atime' ,
812+ 'stx_btime' ,
813+ 'stx_ctime' ,
814+ 'stx_mtime' ,
815+ }
816+
817+ members = set (name for name in dir (result )
818+ if name .startswith ('stx_' ))
819+ tested = set (name for name , mask in requirements )
820+ if members - tested :
821+ raise ValueError (f"statx members not tested: { members - tested } " )
822+
823+ for name , mask in requirements :
824+ with self .subTest (name = name ):
825+ try :
826+ x = getattr (result , name )
827+ except AttributeError :
828+ if name in optional_members :
829+ continue
830+ else :
831+ raise
832+
833+ if not (result .stx_mask & mask == mask ):
834+ self .assertIsNone (x )
835+ continue
836+
837+ if name in float_type :
838+ self .assertIsInstance (x , float )
790839 else :
791- self .assertEqual (x , b , msg = name )
840+ self .assertIsInstance (x , int )
841+
842+ # Compare with stat_result
843+ try :
844+ b = getattr (stat_result , "st_" + name [4 :])
845+ except AttributeError :
846+ pass
847+ else :
848+ self .assertEqual (type (x ), type (b ))
849+ if isinstance (x , float ):
850+ self .assertAlmostEqual (x , b )
851+ else :
852+ self .assertEqual (x , b )
792853
793854 self .assertEqual (result .stx_rdev_major , os .major (result .stx_rdev ))
794855 self .assertEqual (result .stx_rdev_minor , os .minor (result .stx_rdev ))
795856 self .assertEqual (result .stx_dev_major , os .major (result .stx_dev ))
796857 self .assertEqual (result .stx_dev_minor , os .minor (result .stx_dev ))
797858
798- members = [name for name in dir (result )
799- if name .startswith ('stx_' )]
800- for name in members :
801- try :
802- setattr (result , name , 1 )
803- self .fail ("No exception raised" )
804- except AttributeError :
805- pass
806-
807859 self .assertEqual (result .stx_attributes & result .stx_attributes_mask ,
808860 result .stx_attributes )
809861
810- # statx_result is not a tuple or tuple-like object.
811- with self .assertRaisesRegex (TypeError , 'not subscriptable' ):
812- result [0 ]
813- with self .assertRaisesRegex (TypeError , 'cannot unpack' ):
814- _ , _ = result
815-
816862 @unittest .skipUnless (hasattr (os , 'statx' ), 'test needs os.statx()' )
817863 def test_statx_attributes (self ):
818864 self .check_statx_attributes (self .fname )
@@ -829,6 +875,27 @@ def test_statx_attributes_bytes(self):
829875 def test_statx_attributes_pathlike (self ):
830876 self .check_statx_attributes (FakePath (self .fname ))
831877
878+ @unittest .skipUnless (hasattr (os , 'statx' ), 'test needs os.statx()' )
879+ def test_statx_result (self ):
880+ result = os .statx (self .fname , os .STATX_BASIC_STATS )
881+
882+ # Check that attributes are read-only
883+ members = [name for name in dir (result )
884+ if name .startswith ('stx_' )]
885+ for name in members :
886+ try :
887+ setattr (result , name , 1 )
888+ except AttributeError :
889+ pass
890+ else :
891+ self .fail ("No exception raised" )
892+
893+ # statx_result is not a tuple or tuple-like object.
894+ with self .assertRaisesRegex (TypeError , 'not subscriptable' ):
895+ result [0 ]
896+ with self .assertRaisesRegex (TypeError , 'cannot unpack' ):
897+ _ , _ = result
898+
832899 @unittest .skipUnless (hasattr (os , 'statvfs' ), 'test needs os.statvfs()' )
833900 def test_statvfs_attributes (self ):
834901 result = os .statvfs (self .fname )
0 commit comments