@@ -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,105 @@ 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.
761760 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 ),
766761 ('stx_atime' , os .STATX_ATIME ),
767762 ('stx_atime_ns' , os .STATX_ATIME ),
768- ('stx_mtime' , os .STATX_MTIME ),
769- ('stx_mtime_ns' , os .STATX_MTIME ),
763+ ('stx_atomic_write_segments_max' , os .STATX_WRITE_ATOMIC ),
764+ ('stx_atomic_write_unit_max' , os .STATX_WRITE_ATOMIC ),
765+ ('stx_atomic_write_unit_max_opt' , os .STATX_WRITE_ATOMIC ),
766+ ('stx_atomic_write_unit_min' , os .STATX_WRITE_ATOMIC ),
767+ ('stx_attributes' , 0 ),
768+ ('stx_attributes_mask' , 0 ),
769+ ('stx_blksize' , 0 ),
770+ ('stx_blocks' , os .STATX_BLOCKS ),
771+ ('stx_btime' , os .STATX_BTIME ),
772+ ('stx_btime_ns' , os .STATX_BTIME ),
770773 ('stx_ctime' , os .STATX_CTIME ),
771774 ('stx_ctime_ns' , os .STATX_CTIME ),
775+ ('stx_dev' , 0 ),
776+ ('stx_dev_major' , 0 ),
777+ ('stx_dev_minor' , 0 ),
778+ ('stx_dio_mem_align' , os .STATX_DIOALIGN ),
779+ ('stx_dio_offset_align' , os .STATX_DIOALIGN ),
780+ ('stx_dio_read_offset_align' , os .STATX_DIO_READ_ALIGN ),
781+ ('stx_gid' , os .STATX_GID ),
772782 ('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 ),
783+ ('stx_mask ' , 0 ),
784+ ('stx_mnt_id ' , os .STATX_MNT_ID ),
785+ ('stx_mode ' , os .STATX_TYPE | os . STATX_MODE ),
786+ ('stx_mtime ' , os .STATX_MTIME ),
787+ ( 'stx_mtime_ns' , os . STATX_MTIME ),
788+ ('stx_nlink ' , os . STATX_NLINK ),
779789 ('stx_rdev' , 0 ),
780- ('stx_dev' , 0 ),
790+ ('stx_rdev_major' , 0 ),
791+ ('stx_rdev_minor' , 0 ),
792+ ('stx_size' , os .STATX_SIZE ),
793+ ('stx_subvol' , os .STATX_SUBVOL ),
794+ ('stx_uid' , os .STATX_UID ),
781795 )
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 )
796+ optional_members = {
797+ 'stx_atomic_write_segments_max' ,
798+ 'stx_atomic_write_unit_max' ,
799+ 'stx_atomic_write_unit_max_opt' ,
800+ 'stx_atomic_write_unit_min' ,
801+ 'stx_dio_mem_align' ,
802+ 'stx_dio_offset_align' ,
803+ 'stx_dio_read_offset_align' ,
804+ 'stx_mnt_id' ,
805+ 'stx_subvol' ,
806+ }
807+ float_type = {
808+ 'stx_atime' ,
809+ 'stx_btime' ,
810+ 'stx_ctime' ,
811+ 'stx_mtime' ,
812+ }
813+
814+ members = set (name for name in dir (result )
815+ if name .startswith ('stx_' ))
816+ tested = set (name for name , mask in requirements )
817+ if members ^ tested :
818+ raise ValueError (f"statx members not tested: { members ^ tested } " )
819+
820+ for name , mask in requirements :
821+ with self .subTest (name = name ):
822+ try :
823+ x = getattr (result , name )
824+ except AttributeError :
825+ if name in optional_members :
826+ continue
827+ else :
828+ raise
829+
830+ if not (result .stx_mask & mask == mask ):
831+ self .assertIsNone (x )
832+ continue
833+
834+ if name in float_type :
835+ self .assertIsInstance (x , float )
836+ else :
837+ self .assertIsInstance (x , int )
838+
839+ # Compare with stat_result
840+ try :
841+ b = getattr (stat_result , "st_" + name [4 :])
842+ except AttributeError :
843+ pass
790844 else :
791- self .assertEqual (x , b , msg = name )
845+ self .assertEqual (type (x ), type (b ))
846+ if isinstance (x , float ):
847+ self .assertAlmostEqual (x , b )
848+ else :
849+ self .assertEqual (x , b )
792850
793851 self .assertEqual (result .stx_rdev_major , os .major (result .stx_rdev ))
794852 self .assertEqual (result .stx_rdev_minor , os .minor (result .stx_rdev ))
795853 self .assertEqual (result .stx_dev_major , os .major (result .stx_dev ))
796854 self .assertEqual (result .stx_dev_minor , os .minor (result .stx_dev ))
797855
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-
807856 self .assertEqual (result .stx_attributes & result .stx_attributes_mask ,
808857 result .stx_attributes )
809858
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-
816859 @unittest .skipUnless (hasattr (os , 'statx' ), 'test needs os.statx()' )
817860 def test_statx_attributes (self ):
818861 self .check_statx_attributes (self .fname )
@@ -829,6 +872,27 @@ def test_statx_attributes_bytes(self):
829872 def test_statx_attributes_pathlike (self ):
830873 self .check_statx_attributes (FakePath (self .fname ))
831874
875+ @unittest .skipUnless (hasattr (os , 'statx' ), 'test needs os.statx()' )
876+ def test_statx_result (self ):
877+ result = os .statx (self .fname , os .STATX_BASIC_STATS )
878+
879+ # Check that attributes are read-only
880+ members = [name for name in dir (result )
881+ if name .startswith ('stx_' )]
882+ for name in members :
883+ try :
884+ setattr (result , name , 1 )
885+ except AttributeError :
886+ pass
887+ else :
888+ self .fail ("No exception raised" )
889+
890+ # statx_result is not a tuple or tuple-like object.
891+ with self .assertRaisesRegex (TypeError , 'not subscriptable' ):
892+ result [0 ]
893+ with self .assertRaisesRegex (TypeError , 'cannot unpack' ):
894+ _ , _ = result
895+
832896 @unittest .skipUnless (hasattr (os , 'statvfs' ), 'test needs os.statvfs()' )
833897 def test_statvfs_attributes (self ):
834898 result = os .statvfs (self .fname )
0 commit comments