Skip to content

Commit 6787a8f

Browse files
gh-90473: Make chmod a dummy on WASI, skip chmod tests (GH-93534) (GH-93550)
WASI does not have the ``chmod(2)`` syscall yet. (cherry picked from commit 22fed60) Co-authored-by: Christian Heimes <[email protected]>
1 parent 986ce4e commit 6787a8f

20 files changed

+81
-5
lines changed

Lib/test/support/os_helper.py

+36
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,42 @@ def skip_unless_xattr(test):
237237
return test if ok else unittest.skip(msg)(test)
238238

239239

240+
_can_chmod = None
241+
242+
def can_chmod():
243+
global _can_chmod
244+
if _can_chmod is not None:
245+
return _can_chmod
246+
if not hasattr(os, "chown"):
247+
_can_chmod = False
248+
return _can_chmod
249+
try:
250+
with open(TESTFN, "wb") as f:
251+
try:
252+
os.chmod(TESTFN, 0o777)
253+
mode1 = os.stat(TESTFN).st_mode
254+
os.chmod(TESTFN, 0o666)
255+
mode2 = os.stat(TESTFN).st_mode
256+
except OSError as e:
257+
can = False
258+
else:
259+
can = stat.S_IMODE(mode1) != stat.S_IMODE(mode2)
260+
finally:
261+
os.unlink(TESTFN)
262+
_can_chmod = can
263+
return can
264+
265+
266+
def skip_unless_working_chmod(test):
267+
"""Skip tests that require working os.chmod()
268+
269+
WASI SDK 15.0 cannot change file mode bits.
270+
"""
271+
ok = can_chmod()
272+
msg = "requires working os.chmod()"
273+
return test if ok else unittest.skip(msg)(test)
274+
275+
240276
def unlink(filename):
241277
try:
242278
_unlink(filename)

Lib/test/test_argparse.py

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def setUp(self):
4545
env['COLUMNS'] = '80'
4646

4747

48+
@os_helper.skip_unless_working_chmod
4849
class TempDirMixin(object):
4950

5051
def setUp(self):

Lib/test/test_dbm_dumb.py

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def test_dumbdbm_creation(self):
4242
self.read_helper(f)
4343

4444
@unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()')
45+
@os_helper.skip_unless_working_chmod
4546
def test_dumbdbm_creation_mode(self):
4647
try:
4748
old_umask = os.umask(0o002)
@@ -265,6 +266,7 @@ def test_invalid_flag(self):
265266
"'r', 'w', 'c', or 'n'"):
266267
dumbdbm.open(_fname, flag)
267268

269+
@os_helper.skip_unless_working_chmod
268270
def test_readonly_files(self):
269271
with os_helper.temp_dir() as dir:
270272
fname = os.path.join(dir, 'db')

Lib/test/test_import/__init__.py

+3
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,7 @@ def test_creation_mode(self):
557557

558558
@unittest.skipUnless(os.name == 'posix',
559559
"test meaningful only on posix systems")
560+
@os_helper.skip_unless_working_chmod
560561
def test_cached_mode_issue_2051(self):
561562
# permissions of .pyc should match those of .py, regardless of mask
562563
mode = 0o600
@@ -573,6 +574,7 @@ def test_cached_mode_issue_2051(self):
573574

574575
@unittest.skipUnless(os.name == 'posix',
575576
"test meaningful only on posix systems")
577+
@os_helper.skip_unless_working_chmod
576578
def test_cached_readonly(self):
577579
mode = 0o400
578580
with temp_umask(0o022), _ready_to_import() as (name, path):
@@ -886,6 +888,7 @@ def test_import_pyc_path(self):
886888
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
887889
"due to varying filesystem permission semantics (issue #11956)")
888890
@skip_if_dont_write_bytecode
891+
@os_helper.skip_unless_working_chmod
889892
def test_unwritable_directory(self):
890893
# When the umask causes the new __pycache__ directory to be
891894
# unwritable, the import still succeeds but no .pyc file is written.

Lib/test/test_netrc.py

+1
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ def test_comment_at_end_of_machine_line_pass_has_hash(self):
272272

273273
@unittest.skipUnless(os.name == 'posix', 'POSIX only test')
274274
@unittest.skipIf(pwd is None, 'security check requires pwd module')
275+
@os_helper.skip_unless_working_chmod
275276
def test_security(self):
276277
# This test is incomplete since we are normally not run as root and
277278
# therefore can't test the file ownership being wrong.

Lib/test/test_os.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1670,7 +1670,7 @@ def tearDown(self):
16701670
os.removedirs(path)
16711671

16721672

1673-
@unittest.skipUnless(hasattr(os, 'chown'), "Test needs chown")
1673+
@os_helper.skip_unless_working_chmod
16741674
class ChownFileTests(unittest.TestCase):
16751675

16761676
@classmethod
@@ -3784,7 +3784,6 @@ class Str(str):
37843784
def test_oserror_filename(self):
37853785
funcs = [
37863786
(self.filenames, os.chdir,),
3787-
(self.filenames, os.chmod, 0o777),
37883787
(self.filenames, os.lstat,),
37893788
(self.filenames, os.open, os.O_RDONLY),
37903789
(self.filenames, os.rmdir,),
@@ -3805,6 +3804,8 @@ def test_oserror_filename(self):
38053804
(self.filenames, os.rename, "dst"),
38063805
(self.filenames, os.replace, "dst"),
38073806
))
3807+
if os_helper.can_chmod():
3808+
funcs.append((self.filenames, os.chmod, 0o777))
38083809
if hasattr(os, "chown"):
38093810
funcs.append((self.filenames, os.chown, 0, 0))
38103811
if hasattr(os, "lchown"):

Lib/test/test_pathlib.py

+3
Original file line numberDiff line numberDiff line change
@@ -1902,6 +1902,7 @@ def test_with(self):
19021902
with p:
19031903
pass
19041904

1905+
@os_helper.skip_unless_working_chmod
19051906
def test_chmod(self):
19061907
p = self.cls(BASE) / 'fileA'
19071908
mode = p.stat().st_mode
@@ -1916,6 +1917,7 @@ def test_chmod(self):
19161917

19171918
# On Windows, os.chmod does not follow symlinks (issue #15411)
19181919
@only_posix
1920+
@os_helper.skip_unless_working_chmod
19191921
def test_chmod_follow_symlinks_true(self):
19201922
p = self.cls(BASE) / 'linkA'
19211923
q = p.resolve()
@@ -1931,6 +1933,7 @@ def test_chmod_follow_symlinks_true(self):
19311933

19321934
# XXX also need a test for lchmod.
19331935

1936+
@os_helper.skip_unless_working_chmod
19341937
def test_stat(self):
19351938
p = self.cls(BASE) / 'fileA'
19361939
st = p.stat()

Lib/test/test_posix.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -786,7 +786,7 @@ def check_stat(uid, gid):
786786
self.assertRaises(TypeError, chown_func, first_param, uid, t(gid))
787787
check_stat(uid, gid)
788788

789-
@unittest.skipUnless(hasattr(posix, 'chown'), "test needs os.chown()")
789+
@os_helper.skip_unless_working_chmod
790790
def test_chown(self):
791791
# raise an OSError if the file does not exist
792792
os.unlink(os_helper.TESTFN)
@@ -796,6 +796,7 @@ def test_chown(self):
796796
os_helper.create_empty_file(os_helper.TESTFN)
797797
self._test_all_chown_common(posix.chown, os_helper.TESTFN, posix.stat)
798798

799+
@os_helper.skip_unless_working_chmod
799800
@unittest.skipUnless(hasattr(posix, 'fchown'), "test needs os.fchown()")
800801
def test_fchown(self):
801802
os.unlink(os_helper.TESTFN)
@@ -809,6 +810,7 @@ def test_fchown(self):
809810
finally:
810811
test_file.close()
811812

813+
@os_helper.skip_unless_working_chmod
812814
@unittest.skipUnless(hasattr(posix, 'lchown'), "test needs os.lchown()")
813815
def test_lchown(self):
814816
os.unlink(os_helper.TESTFN)

Lib/test/test_posixpath.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,7 @@ def test_ismount_non_existent(self):
193193
self.assertIs(posixpath.ismount('/\x00'), False)
194194
self.assertIs(posixpath.ismount(b'/\x00'), False)
195195

196-
@unittest.skipUnless(os_helper.can_symlink(),
197-
"Test requires symlink support")
196+
@os_helper.skip_unless_symlink
198197
def test_ismount_symlinks(self):
199198
# Symlinks are never mountpoints.
200199
try:

Lib/test/test_py_compile.py

+1
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ def test_relative_path(self):
119119
'non-root user required')
120120
@unittest.skipIf(os.name == 'nt',
121121
'cannot control directory permissions on Windows')
122+
@os_helper.skip_unless_working_chmod
122123
def test_exceptions_propagate(self):
123124
# Make sure that exceptions raised thanks to issues with writing
124125
# bytecode.

Lib/test/test_pydoc.py

+1
Original file line numberDiff line numberDiff line change
@@ -933,6 +933,7 @@ def test_apropos_with_unreadable_dir(self):
933933
self.assertEqual(out.getvalue(), '')
934934
self.assertEqual(err.getvalue(), '')
935935

936+
@os_helper.skip_unless_working_chmod
936937
def test_apropos_empty_doc(self):
937938
pkgdir = os.path.join(TESTFN, 'walkpkg')
938939
os.mkdir(pkgdir)

Lib/test/test_shutil.py

+1
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,7 @@ def onerror(*args):
311311
"This test can't be run on Cygwin (issue #1071513).")
312312
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
313313
"This test can't be run reliably as root (issue #1076467).")
314+
@os_helper.skip_unless_working_chmod
314315
def test_on_error(self):
315316
self.errorState = 0
316317
os.mkdir(TESTFN)

Lib/test/test_stat.py

+2
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ def assertS_IS(self, name, mode):
113113
else:
114114
self.assertFalse(func(mode))
115115

116+
@os_helper.skip_unless_working_chmod
116117
def test_mode(self):
117118
with open(TESTFN, 'w'):
118119
pass
@@ -151,6 +152,7 @@ def test_mode(self):
151152
self.assertEqual(self.statmod.S_IFMT(st_mode),
152153
self.statmod.S_IFREG)
153154

155+
@os_helper.skip_unless_working_chmod
154156
def test_directory(self):
155157
os.mkdir(TESTFN)
156158
os.chmod(TESTFN, 0o700)

Lib/test/test_tarfile.py

+2
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ def test_extract_hardlink(self):
630630
data = f.read()
631631
self.assertEqual(sha256sum(data), sha256_regtype)
632632

633+
@os_helper.skip_unless_working_chmod
633634
def test_extractall(self):
634635
# Test if extractall() correctly restores directory permissions
635636
# and times (see issue1735).
@@ -660,6 +661,7 @@ def format_mtime(mtime):
660661
tar.close()
661662
os_helper.rmtree(DIR)
662663

664+
@os_helper.skip_unless_working_chmod
663665
def test_extract_directory(self):
664666
dirtype = "ustar/dirtype"
665667
DIR = os.path.join(TEMPDIR, "extractdir")

Lib/test/test_tempfile.py

+2
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,7 @@ def test_choose_directory(self):
450450
support.gc_collect() # For PyPy or other GCs.
451451
os.rmdir(dir)
452452

453+
@os_helper.skip_unless_working_chmod
453454
def test_file_mode(self):
454455
# _mkstemp_inner creates files with the proper mode
455456

@@ -787,6 +788,7 @@ def test_choose_directory(self):
787788
finally:
788789
os.rmdir(dir)
789790

791+
@os_helper.skip_unless_working_chmod
790792
def test_mode(self):
791793
# mkdtemp creates directories with the proper mode
792794

Lib/test/test_uu.py

+6
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def test_encode(self):
7474
with self.assertRaises(TypeError):
7575
uu.encode(inp, out, "t1", 0o644, True)
7676

77+
@os_helper.skip_unless_working_chmod
7778
def test_decode(self):
7879
for backtick in True, False:
7980
inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick))
@@ -199,6 +200,8 @@ def test_encode(self):
199200
s = fout.read()
200201
self.assertEqual(s, encodedtextwrapped(0o644, self.tmpin))
201202

203+
# decode() calls chmod()
204+
@os_helper.skip_unless_working_chmod
202205
def test_decode(self):
203206
with open(self.tmpin, 'wb') as f:
204207
f.write(encodedtextwrapped(0o644, self.tmpout))
@@ -211,6 +214,7 @@ def test_decode(self):
211214
self.assertEqual(s, plaintext)
212215
# XXX is there an xp way to verify the mode?
213216

217+
@os_helper.skip_unless_working_chmod
214218
def test_decode_filename(self):
215219
with open(self.tmpin, 'wb') as f:
216220
f.write(encodedtextwrapped(0o644, self.tmpout))
@@ -221,6 +225,7 @@ def test_decode_filename(self):
221225
s = f.read()
222226
self.assertEqual(s, plaintext)
223227

228+
@os_helper.skip_unless_working_chmod
224229
def test_decodetwice(self):
225230
# Verify that decode() will refuse to overwrite an existing file
226231
with open(self.tmpin, 'wb') as f:
@@ -231,6 +236,7 @@ def test_decodetwice(self):
231236
with open(self.tmpin, 'rb') as f:
232237
self.assertRaises(uu.Error, uu.decode, f)
233238

239+
@os_helper.skip_unless_working_chmod
234240
def test_decode_mode(self):
235241
# Verify that decode() will set the given mode for the out_file
236242
expected_mode = 0o444

Lib/test/test_zipapp.py

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import zipapp
1010
import zipfile
1111
from test.support import requires_zlib
12+
from test.support import os_helper
1213

1314
from unittest.mock import patch
1415

@@ -301,6 +302,7 @@ def test_content_of_copied_archive(self):
301302
# (Unix only) tests that archives with shebang lines are made executable
302303
@unittest.skipIf(sys.platform == 'win32',
303304
'Windows does not support an executable bit')
305+
@os_helper.skip_unless_working_chmod
304306
def test_shebang_is_executable(self):
305307
# Test that an archive with a shebang line is made executable.
306308
source = self.tmpdir / 'source'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
WASI does not have a ``chmod(2)`` syscall. :func:`os.chmod` is now a dummy
2+
function on WASI. Skip all tests that depend on working :func:`os.chmod`.

Modules/posixmodule.c

+4
Original file line numberDiff line numberDiff line change
@@ -3308,6 +3308,10 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd,
33083308
{
33093309
#ifdef HAVE_CHMOD
33103310
result = chmod(path->narrow, mode);
3311+
#elif defined(__wasi__)
3312+
// WASI SDK 15.0 does not support chmod.
3313+
// Ignore missing syscall for now.
3314+
result = 0;
33113315
#else
33123316
result = -1;
33133317
errno = ENOSYS;

Tools/wasm/config.site-wasm32-wasi

+5
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ ac_cv_func_fdopendir=no
3535
# WASIX stubs we don't want to use.
3636
ac_cv_func_kill=no
3737

38+
# WASI SDK 15.0 does not have chmod.
39+
# Ignore WASIX stubs for now.
40+
ac_cv_func_chmod=no
41+
ac_cv_func_fchmod=no
42+
3843
# WASI sockets are limited to operations on given socket fd and inet sockets.
3944
# Disable AF_UNIX and AF_PACKET support, see socketmodule.h.
4045
ac_cv_header_sys_un_h=no

0 commit comments

Comments
 (0)