mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
gh-83714: Set os.statx().stx_mode to None if missing from stx_mask (#140484)
* Set stx_mode to None if STATX_TYPE|STATX_MODE is missing from stx_mask. * Enhance os.statx() tests. * statx_result structure: remove atime_sec, btime_sec, ctime_sec and mtime_sec members. Compute them on demand when stx_atime, stx_btime, stx_ctime and stx_mtime are read. * Doc: fix statx members sorting.
This commit is contained in:
parent
f0291c3f2d
commit
5d2edf72d2
3 changed files with 148 additions and 75 deletions
|
|
@ -748,7 +748,7 @@ def check_statx_attributes(self, filename):
|
|||
if name.startswith('STATX_'):
|
||||
maximal_mask |= getattr(os, name)
|
||||
result = os.statx(filename, maximal_mask)
|
||||
basic_result = os.stat(filename)
|
||||
stat_result = os.stat(filename)
|
||||
|
||||
time_attributes = ('stx_atime', 'stx_btime', 'stx_ctime', 'stx_mtime')
|
||||
# gh-83714: stx_btime can be None on tmpfs even if STATX_BTIME mask
|
||||
|
|
@ -757,62 +757,108 @@ def check_statx_attributes(self, filename):
|
|||
if getattr(result, name) is not None]
|
||||
self.check_timestamp_agreement(result, time_attributes)
|
||||
|
||||
# Check that valid attributes match os.stat.
|
||||
def getmask(name):
|
||||
return getattr(os, name, 0)
|
||||
|
||||
requirements = (
|
||||
('stx_mode', os.STATX_TYPE | os.STATX_MODE),
|
||||
('stx_nlink', os.STATX_NLINK),
|
||||
('stx_uid', os.STATX_UID),
|
||||
('stx_gid', os.STATX_GID),
|
||||
('stx_atime', os.STATX_ATIME),
|
||||
('stx_atime_ns', os.STATX_ATIME),
|
||||
('stx_mtime', os.STATX_MTIME),
|
||||
('stx_mtime_ns', os.STATX_MTIME),
|
||||
('stx_atomic_write_segments_max', getmask('STATX_WRITE_ATOMIC')),
|
||||
('stx_atomic_write_unit_max', getmask('STATX_WRITE_ATOMIC')),
|
||||
('stx_atomic_write_unit_max_opt', getmask('STATX_WRITE_ATOMIC')),
|
||||
('stx_atomic_write_unit_min', getmask('STATX_WRITE_ATOMIC')),
|
||||
('stx_attributes', 0),
|
||||
('stx_attributes_mask', 0),
|
||||
('stx_blksize', 0),
|
||||
('stx_blocks', os.STATX_BLOCKS),
|
||||
('stx_btime', os.STATX_BTIME),
|
||||
('stx_btime_ns', os.STATX_BTIME),
|
||||
('stx_ctime', os.STATX_CTIME),
|
||||
('stx_ctime_ns', os.STATX_CTIME),
|
||||
('stx_ino', os.STATX_INO),
|
||||
('stx_size', os.STATX_SIZE),
|
||||
('stx_blocks', os.STATX_BLOCKS),
|
||||
('stx_birthtime', os.STATX_BTIME),
|
||||
('stx_birthtime_ns', os.STATX_BTIME),
|
||||
# unconditionally valid members
|
||||
('stx_blksize', 0),
|
||||
('stx_rdev', 0),
|
||||
('stx_dev', 0),
|
||||
('stx_dev_major', 0),
|
||||
('stx_dev_minor', 0),
|
||||
('stx_dio_mem_align', getmask('STATX_DIOALIGN')),
|
||||
('stx_dio_offset_align', getmask('STATX_DIOALIGN')),
|
||||
('stx_dio_read_offset_align', getmask('STATX_DIO_READ_ALIGN')),
|
||||
('stx_gid', os.STATX_GID),
|
||||
('stx_ino', os.STATX_INO),
|
||||
('stx_mask', 0),
|
||||
('stx_mnt_id', getmask('STATX_MNT_ID')),
|
||||
('stx_mode', os.STATX_TYPE | os.STATX_MODE),
|
||||
('stx_mtime', os.STATX_MTIME),
|
||||
('stx_mtime_ns', os.STATX_MTIME),
|
||||
('stx_nlink', os.STATX_NLINK),
|
||||
('stx_rdev', 0),
|
||||
('stx_rdev_major', 0),
|
||||
('stx_rdev_minor', 0),
|
||||
('stx_size', os.STATX_SIZE),
|
||||
('stx_subvol', getmask('STATX_SUBVOL')),
|
||||
('stx_uid', os.STATX_UID),
|
||||
)
|
||||
for name, bits in requirements:
|
||||
st_name = "st_" + name[4:]
|
||||
if result.stx_mask & bits == bits and hasattr(basic_result, st_name):
|
||||
x = getattr(result, name)
|
||||
b = getattr(basic_result, st_name)
|
||||
self.assertEqual(type(x), type(b))
|
||||
if isinstance(x, float):
|
||||
self.assertAlmostEqual(x, b, msg=name)
|
||||
optional_members = {
|
||||
'stx_atomic_write_segments_max',
|
||||
'stx_atomic_write_unit_max',
|
||||
'stx_atomic_write_unit_max_opt',
|
||||
'stx_atomic_write_unit_min',
|
||||
'stx_dio_mem_align',
|
||||
'stx_dio_offset_align',
|
||||
'stx_dio_read_offset_align',
|
||||
'stx_mnt_id',
|
||||
'stx_subvol',
|
||||
}
|
||||
float_type = {
|
||||
'stx_atime',
|
||||
'stx_btime',
|
||||
'stx_ctime',
|
||||
'stx_mtime',
|
||||
}
|
||||
|
||||
members = set(name for name in dir(result)
|
||||
if name.startswith('stx_'))
|
||||
tested = set(name for name, mask in requirements)
|
||||
if members - tested:
|
||||
raise ValueError(f"statx members not tested: {members - tested}")
|
||||
|
||||
for name, mask in requirements:
|
||||
with self.subTest(name=name):
|
||||
try:
|
||||
x = getattr(result, name)
|
||||
except AttributeError:
|
||||
if name in optional_members:
|
||||
continue
|
||||
else:
|
||||
raise
|
||||
|
||||
if not(result.stx_mask & mask == mask):
|
||||
self.assertIsNone(x)
|
||||
continue
|
||||
|
||||
if name in float_type:
|
||||
self.assertIsInstance(x, float)
|
||||
else:
|
||||
self.assertEqual(x, b, msg=name)
|
||||
self.assertIsInstance(x, int)
|
||||
|
||||
# Compare with stat_result
|
||||
try:
|
||||
b = getattr(stat_result, "st_" + name[4:])
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.assertEqual(type(x), type(b))
|
||||
if isinstance(x, float):
|
||||
self.assertAlmostEqual(x, b)
|
||||
else:
|
||||
self.assertEqual(x, b)
|
||||
|
||||
self.assertEqual(result.stx_rdev_major, os.major(result.stx_rdev))
|
||||
self.assertEqual(result.stx_rdev_minor, os.minor(result.stx_rdev))
|
||||
self.assertEqual(result.stx_dev_major, os.major(result.stx_dev))
|
||||
self.assertEqual(result.stx_dev_minor, os.minor(result.stx_dev))
|
||||
|
||||
members = [name for name in dir(result)
|
||||
if name.startswith('stx_')]
|
||||
for name in members:
|
||||
try:
|
||||
setattr(result, name, 1)
|
||||
self.fail("No exception raised")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
self.assertEqual(result.stx_attributes & result.stx_attributes_mask,
|
||||
result.stx_attributes)
|
||||
|
||||
# statx_result is not a tuple or tuple-like object.
|
||||
with self.assertRaisesRegex(TypeError, 'not subscriptable'):
|
||||
result[0]
|
||||
with self.assertRaisesRegex(TypeError, 'cannot unpack'):
|
||||
_, _ = result
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()')
|
||||
def test_statx_attributes(self):
|
||||
self.check_statx_attributes(self.fname)
|
||||
|
|
@ -829,6 +875,27 @@ def test_statx_attributes_bytes(self):
|
|||
def test_statx_attributes_pathlike(self):
|
||||
self.check_statx_attributes(FakePath(self.fname))
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()')
|
||||
def test_statx_result(self):
|
||||
result = os.statx(self.fname, os.STATX_BASIC_STATS)
|
||||
|
||||
# Check that attributes are read-only
|
||||
members = [name for name in dir(result)
|
||||
if name.startswith('stx_')]
|
||||
for name in members:
|
||||
try:
|
||||
setattr(result, name, 1)
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
self.fail("No exception raised")
|
||||
|
||||
# statx_result is not a tuple or tuple-like object.
|
||||
with self.assertRaisesRegex(TypeError, 'not subscriptable'):
|
||||
result[0]
|
||||
with self.assertRaisesRegex(TypeError, 'cannot unpack'):
|
||||
_, _ = result
|
||||
|
||||
@unittest.skipUnless(hasattr(os, 'statvfs'), 'test needs os.statvfs()')
|
||||
def test_statvfs_attributes(self):
|
||||
result = os.statvfs(self.fname)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue