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 22fed605e0)

Co-authored-by: Christian Heimes <christian@python.org>
This commit is contained in:
Miss Islington (bot) 2022-06-06 11:09:51 -07:00 committed by GitHub
parent 986ce4e613
commit 6787a8f146
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 81 additions and 5 deletions

View File

@ -237,6 +237,42 @@ def skip_unless_xattr(test):
return test if ok else unittest.skip(msg)(test) return test if ok else unittest.skip(msg)(test)
_can_chmod = None
def can_chmod():
global _can_chmod
if _can_chmod is not None:
return _can_chmod
if not hasattr(os, "chown"):
_can_chmod = False
return _can_chmod
try:
with open(TESTFN, "wb") as f:
try:
os.chmod(TESTFN, 0o777)
mode1 = os.stat(TESTFN).st_mode
os.chmod(TESTFN, 0o666)
mode2 = os.stat(TESTFN).st_mode
except OSError as e:
can = False
else:
can = stat.S_IMODE(mode1) != stat.S_IMODE(mode2)
finally:
os.unlink(TESTFN)
_can_chmod = can
return can
def skip_unless_working_chmod(test):
"""Skip tests that require working os.chmod()
WASI SDK 15.0 cannot change file mode bits.
"""
ok = can_chmod()
msg = "requires working os.chmod()"
return test if ok else unittest.skip(msg)(test)
def unlink(filename): def unlink(filename):
try: try:
_unlink(filename) _unlink(filename)

View File

@ -45,6 +45,7 @@ class TestCase(unittest.TestCase):
env['COLUMNS'] = '80' env['COLUMNS'] = '80'
@os_helper.skip_unless_working_chmod
class TempDirMixin(object): class TempDirMixin(object):
def setUp(self): def setUp(self):

View File

@ -42,6 +42,7 @@ class DumbDBMTestCase(unittest.TestCase):
self.read_helper(f) self.read_helper(f)
@unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()') @unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()')
@os_helper.skip_unless_working_chmod
def test_dumbdbm_creation_mode(self): def test_dumbdbm_creation_mode(self):
try: try:
old_umask = os.umask(0o002) old_umask = os.umask(0o002)
@ -265,6 +266,7 @@ class DumbDBMTestCase(unittest.TestCase):
"'r', 'w', 'c', or 'n'"): "'r', 'w', 'c', or 'n'"):
dumbdbm.open(_fname, flag) dumbdbm.open(_fname, flag)
@os_helper.skip_unless_working_chmod
def test_readonly_files(self): def test_readonly_files(self):
with os_helper.temp_dir() as dir: with os_helper.temp_dir() as dir:
fname = os.path.join(dir, 'db') fname = os.path.join(dir, 'db')

View File

@ -557,6 +557,7 @@ class FilePermissionTests(unittest.TestCase):
@unittest.skipUnless(os.name == 'posix', @unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems") "test meaningful only on posix systems")
@os_helper.skip_unless_working_chmod
def test_cached_mode_issue_2051(self): def test_cached_mode_issue_2051(self):
# permissions of .pyc should match those of .py, regardless of mask # permissions of .pyc should match those of .py, regardless of mask
mode = 0o600 mode = 0o600
@ -573,6 +574,7 @@ class FilePermissionTests(unittest.TestCase):
@unittest.skipUnless(os.name == 'posix', @unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems") "test meaningful only on posix systems")
@os_helper.skip_unless_working_chmod
def test_cached_readonly(self): def test_cached_readonly(self):
mode = 0o400 mode = 0o400
with temp_umask(0o022), _ready_to_import() as (name, path): with temp_umask(0o022), _ready_to_import() as (name, path):
@ -886,6 +888,7 @@ class PycacheTests(unittest.TestCase):
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"due to varying filesystem permission semantics (issue #11956)") "due to varying filesystem permission semantics (issue #11956)")
@skip_if_dont_write_bytecode @skip_if_dont_write_bytecode
@os_helper.skip_unless_working_chmod
def test_unwritable_directory(self): def test_unwritable_directory(self):
# When the umask causes the new __pycache__ directory to be # When the umask causes the new __pycache__ directory to be
# unwritable, the import still succeeds but no .pyc file is written. # unwritable, the import still succeeds but no .pyc file is written.

View File

@ -272,6 +272,7 @@ class NetrcTestCase(unittest.TestCase):
@unittest.skipUnless(os.name == 'posix', 'POSIX only test') @unittest.skipUnless(os.name == 'posix', 'POSIX only test')
@unittest.skipIf(pwd is None, 'security check requires pwd module') @unittest.skipIf(pwd is None, 'security check requires pwd module')
@os_helper.skip_unless_working_chmod
def test_security(self): def test_security(self):
# This test is incomplete since we are normally not run as root and # This test is incomplete since we are normally not run as root and
# therefore can't test the file ownership being wrong. # therefore can't test the file ownership being wrong.

View File

@ -1670,7 +1670,7 @@ class MakedirTests(unittest.TestCase):
os.removedirs(path) os.removedirs(path)
@unittest.skipUnless(hasattr(os, 'chown'), "Test needs chown") @os_helper.skip_unless_working_chmod
class ChownFileTests(unittest.TestCase): class ChownFileTests(unittest.TestCase):
@classmethod @classmethod
@ -3784,7 +3784,6 @@ class OSErrorTests(unittest.TestCase):
def test_oserror_filename(self): def test_oserror_filename(self):
funcs = [ funcs = [
(self.filenames, os.chdir,), (self.filenames, os.chdir,),
(self.filenames, os.chmod, 0o777),
(self.filenames, os.lstat,), (self.filenames, os.lstat,),
(self.filenames, os.open, os.O_RDONLY), (self.filenames, os.open, os.O_RDONLY),
(self.filenames, os.rmdir,), (self.filenames, os.rmdir,),
@ -3805,6 +3804,8 @@ class OSErrorTests(unittest.TestCase):
(self.filenames, os.rename, "dst"), (self.filenames, os.rename, "dst"),
(self.filenames, os.replace, "dst"), (self.filenames, os.replace, "dst"),
)) ))
if os_helper.can_chmod():
funcs.append((self.filenames, os.chmod, 0o777))
if hasattr(os, "chown"): if hasattr(os, "chown"):
funcs.append((self.filenames, os.chown, 0, 0)) funcs.append((self.filenames, os.chown, 0, 0))
if hasattr(os, "lchown"): if hasattr(os, "lchown"):

View File

@ -1902,6 +1902,7 @@ class _BasePathTest(object):
with p: with p:
pass pass
@os_helper.skip_unless_working_chmod
def test_chmod(self): def test_chmod(self):
p = self.cls(BASE) / 'fileA' p = self.cls(BASE) / 'fileA'
mode = p.stat().st_mode mode = p.stat().st_mode
@ -1916,6 +1917,7 @@ class _BasePathTest(object):
# On Windows, os.chmod does not follow symlinks (issue #15411) # On Windows, os.chmod does not follow symlinks (issue #15411)
@only_posix @only_posix
@os_helper.skip_unless_working_chmod
def test_chmod_follow_symlinks_true(self): def test_chmod_follow_symlinks_true(self):
p = self.cls(BASE) / 'linkA' p = self.cls(BASE) / 'linkA'
q = p.resolve() q = p.resolve()
@ -1931,6 +1933,7 @@ class _BasePathTest(object):
# XXX also need a test for lchmod. # XXX also need a test for lchmod.
@os_helper.skip_unless_working_chmod
def test_stat(self): def test_stat(self):
p = self.cls(BASE) / 'fileA' p = self.cls(BASE) / 'fileA'
st = p.stat() st = p.stat()

View File

@ -786,7 +786,7 @@ class PosixTester(unittest.TestCase):
self.assertRaises(TypeError, chown_func, first_param, uid, t(gid)) self.assertRaises(TypeError, chown_func, first_param, uid, t(gid))
check_stat(uid, gid) check_stat(uid, gid)
@unittest.skipUnless(hasattr(posix, 'chown'), "test needs os.chown()") @os_helper.skip_unless_working_chmod
def test_chown(self): def test_chown(self):
# raise an OSError if the file does not exist # raise an OSError if the file does not exist
os.unlink(os_helper.TESTFN) os.unlink(os_helper.TESTFN)
@ -796,6 +796,7 @@ class PosixTester(unittest.TestCase):
os_helper.create_empty_file(os_helper.TESTFN) os_helper.create_empty_file(os_helper.TESTFN)
self._test_all_chown_common(posix.chown, os_helper.TESTFN, posix.stat) self._test_all_chown_common(posix.chown, os_helper.TESTFN, posix.stat)
@os_helper.skip_unless_working_chmod
@unittest.skipUnless(hasattr(posix, 'fchown'), "test needs os.fchown()") @unittest.skipUnless(hasattr(posix, 'fchown'), "test needs os.fchown()")
def test_fchown(self): def test_fchown(self):
os.unlink(os_helper.TESTFN) os.unlink(os_helper.TESTFN)
@ -809,6 +810,7 @@ class PosixTester(unittest.TestCase):
finally: finally:
test_file.close() test_file.close()
@os_helper.skip_unless_working_chmod
@unittest.skipUnless(hasattr(posix, 'lchown'), "test needs os.lchown()") @unittest.skipUnless(hasattr(posix, 'lchown'), "test needs os.lchown()")
def test_lchown(self): def test_lchown(self):
os.unlink(os_helper.TESTFN) os.unlink(os_helper.TESTFN)

View File

@ -193,8 +193,7 @@ class PosixPathTest(unittest.TestCase):
self.assertIs(posixpath.ismount('/\x00'), False) self.assertIs(posixpath.ismount('/\x00'), False)
self.assertIs(posixpath.ismount(b'/\x00'), False) self.assertIs(posixpath.ismount(b'/\x00'), False)
@unittest.skipUnless(os_helper.can_symlink(), @os_helper.skip_unless_symlink
"Test requires symlink support")
def test_ismount_symlinks(self): def test_ismount_symlinks(self):
# Symlinks are never mountpoints. # Symlinks are never mountpoints.
try: try:

View File

@ -119,6 +119,7 @@ class PyCompileTestsBase:
'non-root user required') 'non-root user required')
@unittest.skipIf(os.name == 'nt', @unittest.skipIf(os.name == 'nt',
'cannot control directory permissions on Windows') 'cannot control directory permissions on Windows')
@os_helper.skip_unless_working_chmod
def test_exceptions_propagate(self): def test_exceptions_propagate(self):
# Make sure that exceptions raised thanks to issues with writing # Make sure that exceptions raised thanks to issues with writing
# bytecode. # bytecode.

View File

@ -933,6 +933,7 @@ class PydocImportTest(PydocBaseTest):
self.assertEqual(out.getvalue(), '') self.assertEqual(out.getvalue(), '')
self.assertEqual(err.getvalue(), '') self.assertEqual(err.getvalue(), '')
@os_helper.skip_unless_working_chmod
def test_apropos_empty_doc(self): def test_apropos_empty_doc(self):
pkgdir = os.path.join(TESTFN, 'walkpkg') pkgdir = os.path.join(TESTFN, 'walkpkg')
os.mkdir(pkgdir) os.mkdir(pkgdir)

View File

@ -311,6 +311,7 @@ class TestRmTree(BaseTest, unittest.TestCase):
"This test can't be run on Cygwin (issue #1071513).") "This test can't be run on Cygwin (issue #1071513).")
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0, @unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"This test can't be run reliably as root (issue #1076467).") "This test can't be run reliably as root (issue #1076467).")
@os_helper.skip_unless_working_chmod
def test_on_error(self): def test_on_error(self):
self.errorState = 0 self.errorState = 0
os.mkdir(TESTFN) os.mkdir(TESTFN)

View File

@ -113,6 +113,7 @@ class TestFilemode:
else: else:
self.assertFalse(func(mode)) self.assertFalse(func(mode))
@os_helper.skip_unless_working_chmod
def test_mode(self): def test_mode(self):
with open(TESTFN, 'w'): with open(TESTFN, 'w'):
pass pass
@ -151,6 +152,7 @@ class TestFilemode:
self.assertEqual(self.statmod.S_IFMT(st_mode), self.assertEqual(self.statmod.S_IFMT(st_mode),
self.statmod.S_IFREG) self.statmod.S_IFREG)
@os_helper.skip_unless_working_chmod
def test_directory(self): def test_directory(self):
os.mkdir(TESTFN) os.mkdir(TESTFN)
os.chmod(TESTFN, 0o700) os.chmod(TESTFN, 0o700)

View File

@ -630,6 +630,7 @@ class MiscReadTestBase(CommonReadTest):
data = f.read() data = f.read()
self.assertEqual(sha256sum(data), sha256_regtype) self.assertEqual(sha256sum(data), sha256_regtype)
@os_helper.skip_unless_working_chmod
def test_extractall(self): def test_extractall(self):
# Test if extractall() correctly restores directory permissions # Test if extractall() correctly restores directory permissions
# and times (see issue1735). # and times (see issue1735).
@ -660,6 +661,7 @@ class MiscReadTestBase(CommonReadTest):
tar.close() tar.close()
os_helper.rmtree(DIR) os_helper.rmtree(DIR)
@os_helper.skip_unless_working_chmod
def test_extract_directory(self): def test_extract_directory(self):
dirtype = "ustar/dirtype" dirtype = "ustar/dirtype"
DIR = os.path.join(TEMPDIR, "extractdir") DIR = os.path.join(TEMPDIR, "extractdir")

View File

@ -450,6 +450,7 @@ class TestMkstempInner(TestBadTempdir, BaseTestCase):
support.gc_collect() # For PyPy or other GCs. support.gc_collect() # For PyPy or other GCs.
os.rmdir(dir) os.rmdir(dir)
@os_helper.skip_unless_working_chmod
def test_file_mode(self): def test_file_mode(self):
# _mkstemp_inner creates files with the proper mode # _mkstemp_inner creates files with the proper mode
@ -787,6 +788,7 @@ class TestMkdtemp(TestBadTempdir, BaseTestCase):
finally: finally:
os.rmdir(dir) os.rmdir(dir)
@os_helper.skip_unless_working_chmod
def test_mode(self): def test_mode(self):
# mkdtemp creates directories with the proper mode # mkdtemp creates directories with the proper mode

View File

@ -74,6 +74,7 @@ class UUTest(unittest.TestCase):
with self.assertRaises(TypeError): with self.assertRaises(TypeError):
uu.encode(inp, out, "t1", 0o644, True) uu.encode(inp, out, "t1", 0o644, True)
@os_helper.skip_unless_working_chmod
def test_decode(self): def test_decode(self):
for backtick in True, False: for backtick in True, False:
inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick)) inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick))
@ -199,6 +200,8 @@ class UUFileTest(unittest.TestCase):
s = fout.read() s = fout.read()
self.assertEqual(s, encodedtextwrapped(0o644, self.tmpin)) self.assertEqual(s, encodedtextwrapped(0o644, self.tmpin))
# decode() calls chmod()
@os_helper.skip_unless_working_chmod
def test_decode(self): def test_decode(self):
with open(self.tmpin, 'wb') as f: with open(self.tmpin, 'wb') as f:
f.write(encodedtextwrapped(0o644, self.tmpout)) f.write(encodedtextwrapped(0o644, self.tmpout))
@ -211,6 +214,7 @@ class UUFileTest(unittest.TestCase):
self.assertEqual(s, plaintext) self.assertEqual(s, plaintext)
# XXX is there an xp way to verify the mode? # XXX is there an xp way to verify the mode?
@os_helper.skip_unless_working_chmod
def test_decode_filename(self): def test_decode_filename(self):
with open(self.tmpin, 'wb') as f: with open(self.tmpin, 'wb') as f:
f.write(encodedtextwrapped(0o644, self.tmpout)) f.write(encodedtextwrapped(0o644, self.tmpout))
@ -221,6 +225,7 @@ class UUFileTest(unittest.TestCase):
s = f.read() s = f.read()
self.assertEqual(s, plaintext) self.assertEqual(s, plaintext)
@os_helper.skip_unless_working_chmod
def test_decodetwice(self): def test_decodetwice(self):
# Verify that decode() will refuse to overwrite an existing file # Verify that decode() will refuse to overwrite an existing file
with open(self.tmpin, 'wb') as f: with open(self.tmpin, 'wb') as f:
@ -231,6 +236,7 @@ class UUFileTest(unittest.TestCase):
with open(self.tmpin, 'rb') as f: with open(self.tmpin, 'rb') as f:
self.assertRaises(uu.Error, uu.decode, f) self.assertRaises(uu.Error, uu.decode, f)
@os_helper.skip_unless_working_chmod
def test_decode_mode(self): def test_decode_mode(self):
# Verify that decode() will set the given mode for the out_file # Verify that decode() will set the given mode for the out_file
expected_mode = 0o444 expected_mode = 0o444

View File

@ -9,6 +9,7 @@ import unittest
import zipapp import zipapp
import zipfile import zipfile
from test.support import requires_zlib from test.support import requires_zlib
from test.support import os_helper
from unittest.mock import patch from unittest.mock import patch
@ -301,6 +302,7 @@ class ZipAppTest(unittest.TestCase):
# (Unix only) tests that archives with shebang lines are made executable # (Unix only) tests that archives with shebang lines are made executable
@unittest.skipIf(sys.platform == 'win32', @unittest.skipIf(sys.platform == 'win32',
'Windows does not support an executable bit') 'Windows does not support an executable bit')
@os_helper.skip_unless_working_chmod
def test_shebang_is_executable(self): def test_shebang_is_executable(self):
# Test that an archive with a shebang line is made executable. # Test that an archive with a shebang line is made executable.
source = self.tmpdir / 'source' source = self.tmpdir / 'source'

View File

@ -0,0 +1,2 @@
WASI does not have a ``chmod(2)`` syscall. :func:`os.chmod` is now a dummy
function on WASI. Skip all tests that depend on working :func:`os.chmod`.

View File

@ -3308,6 +3308,10 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd,
{ {
#ifdef HAVE_CHMOD #ifdef HAVE_CHMOD
result = chmod(path->narrow, mode); result = chmod(path->narrow, mode);
#elif defined(__wasi__)
// WASI SDK 15.0 does not support chmod.
// Ignore missing syscall for now.
result = 0;
#else #else
result = -1; result = -1;
errno = ENOSYS; errno = ENOSYS;

View File

@ -35,6 +35,11 @@ ac_cv_func_fdopendir=no
# WASIX stubs we don't want to use. # WASIX stubs we don't want to use.
ac_cv_func_kill=no ac_cv_func_kill=no
# WASI SDK 15.0 does not have chmod.
# Ignore WASIX stubs for now.
ac_cv_func_chmod=no
ac_cv_func_fchmod=no
# WASI sockets are limited to operations on given socket fd and inet sockets. # WASI sockets are limited to operations on given socket fd and inet sockets.
# Disable AF_UNIX and AF_PACKET support, see socketmodule.h. # Disable AF_UNIX and AF_PACKET support, see socketmodule.h.
ac_cv_header_sys_un_h=no ac_cv_header_sys_un_h=no