Skip to content

Commit

Permalink
pythongh-90473: Make chmod a dummy on WASI, skip chmod tests (pythonG…
Browse files Browse the repository at this point in the history
…H-93534)

WASI does not have the ``chmod(2)`` syscall yet.
  • Loading branch information
tiran committed Jun 6, 2022
1 parent 56b5daf commit 22fed60
Show file tree
Hide file tree
Showing 20 changed files with 81 additions and 5 deletions.
36 changes: 36 additions & 0 deletions Lib/test/support/os_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,42 @@ def skip_unless_xattr(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):
try:
_unlink(filename)
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def setUp(self):
env['COLUMNS'] = '80'


@os_helper.skip_unless_working_chmod
class TempDirMixin(object):

def setUp(self):
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_dbm_dumb.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def test_dumbdbm_creation(self):
self.read_helper(f)

@unittest.skipUnless(hasattr(os, 'umask'), 'test needs os.umask()')
@os_helper.skip_unless_working_chmod
def test_dumbdbm_creation_mode(self):
try:
old_umask = os.umask(0o002)
Expand Down Expand Up @@ -265,6 +266,7 @@ def test_invalid_flag(self):
"'r', 'w', 'c', or 'n'"):
dumbdbm.open(_fname, flag)

@os_helper.skip_unless_working_chmod
def test_readonly_files(self):
with os_helper.temp_dir() as dir:
fname = os.path.join(dir, 'db')
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_import/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ def test_creation_mode(self):

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

@unittest.skipUnless(os.name == 'posix',
"test meaningful only on posix systems")
@os_helper.skip_unless_working_chmod
def test_cached_readonly(self):
mode = 0o400
with temp_umask(0o022), _ready_to_import() as (name, path):
Expand Down Expand Up @@ -886,6 +888,7 @@ def test_import_pyc_path(self):
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"due to varying filesystem permission semantics (issue #11956)")
@skip_if_dont_write_bytecode
@os_helper.skip_unless_working_chmod
def test_unwritable_directory(self):
# When the umask causes the new __pycache__ directory to be
# unwritable, the import still succeeds but no .pyc file is written.
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_netrc.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ def test_comment_at_end_of_machine_line_pass_has_hash(self):

@unittest.skipUnless(os.name == 'posix', 'POSIX only test')
@unittest.skipIf(pwd is None, 'security check requires pwd module')
@os_helper.skip_unless_working_chmod
def test_security(self):
# This test is incomplete since we are normally not run as root and
# therefore can't test the file ownership being wrong.
Expand Down
5 changes: 3 additions & 2 deletions Lib/test/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -1670,7 +1670,7 @@ def tearDown(self):
os.removedirs(path)


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

@classmethod
Expand Down Expand Up @@ -3784,7 +3784,6 @@ class Str(str):
def test_oserror_filename(self):
funcs = [
(self.filenames, os.chdir,),
(self.filenames, os.chmod, 0o777),
(self.filenames, os.lstat,),
(self.filenames, os.open, os.O_RDONLY),
(self.filenames, os.rmdir,),
Expand All @@ -3805,6 +3804,8 @@ def test_oserror_filename(self):
(self.filenames, os.rename, "dst"),
(self.filenames, os.replace, "dst"),
))
if os_helper.can_chmod():
funcs.append((self.filenames, os.chmod, 0o777))
if hasattr(os, "chown"):
funcs.append((self.filenames, os.chown, 0, 0))
if hasattr(os, "lchown"):
Expand Down
3 changes: 3 additions & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1902,6 +1902,7 @@ def test_with(self):
with p:
pass

@os_helper.skip_unless_working_chmod
def test_chmod(self):
p = self.cls(BASE) / 'fileA'
mode = p.stat().st_mode
Expand All @@ -1916,6 +1917,7 @@ def test_chmod(self):

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

# XXX also need a test for lchmod.

@os_helper.skip_unless_working_chmod
def test_stat(self):
p = self.cls(BASE) / 'fileA'
st = p.stat()
Expand Down
4 changes: 3 additions & 1 deletion Lib/test/test_posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ def check_stat(uid, gid):
self.assertRaises(TypeError, chown_func, first_param, uid, t(gid))
check_stat(uid, gid)

@unittest.skipUnless(hasattr(posix, 'chown'), "test needs os.chown()")
@os_helper.skip_unless_working_chmod
def test_chown(self):
# raise an OSError if the file does not exist
os.unlink(os_helper.TESTFN)
Expand All @@ -796,6 +796,7 @@ def test_chown(self):
os_helper.create_empty_file(os_helper.TESTFN)
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()")
def test_fchown(self):
os.unlink(os_helper.TESTFN)
Expand All @@ -809,6 +810,7 @@ def test_fchown(self):
finally:
test_file.close()

@os_helper.skip_unless_working_chmod
@unittest.skipUnless(hasattr(posix, 'lchown'), "test needs os.lchown()")
def test_lchown(self):
os.unlink(os_helper.TESTFN)
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_posixpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,7 @@ def test_ismount_non_existent(self):
self.assertIs(posixpath.ismount('/\x00'), False)
self.assertIs(posixpath.ismount(b'/\x00'), False)

@unittest.skipUnless(os_helper.can_symlink(),
"Test requires symlink support")
@os_helper.skip_unless_symlink
def test_ismount_symlinks(self):
# Symlinks are never mountpoints.
try:
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_py_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def test_relative_path(self):
'non-root user required')
@unittest.skipIf(os.name == 'nt',
'cannot control directory permissions on Windows')
@os_helper.skip_unless_working_chmod
def test_exceptions_propagate(self):
# Make sure that exceptions raised thanks to issues with writing
# bytecode.
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,7 @@ def test_apropos_with_unreadable_dir(self):
self.assertEqual(out.getvalue(), '')
self.assertEqual(err.getvalue(), '')

@os_helper.skip_unless_working_chmod
def test_apropos_empty_doc(self):
pkgdir = os.path.join(TESTFN, 'walkpkg')
os.mkdir(pkgdir)
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_shutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ def onerror(*args):
"This test can't be run on Cygwin (issue #1071513).")
@unittest.skipIf(hasattr(os, 'geteuid') and os.geteuid() == 0,
"This test can't be run reliably as root (issue #1076467).")
@os_helper.skip_unless_working_chmod
def test_on_error(self):
self.errorState = 0
os.mkdir(TESTFN)
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_stat.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ def assertS_IS(self, name, mode):
else:
self.assertFalse(func(mode))

@os_helper.skip_unless_working_chmod
def test_mode(self):
with open(TESTFN, 'w'):
pass
Expand Down Expand Up @@ -151,6 +152,7 @@ def test_mode(self):
self.assertEqual(self.statmod.S_IFMT(st_mode),
self.statmod.S_IFREG)

@os_helper.skip_unless_working_chmod
def test_directory(self):
os.mkdir(TESTFN)
os.chmod(TESTFN, 0o700)
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ def test_extract_hardlink(self):
data = f.read()
self.assertEqual(sha256sum(data), sha256_regtype)

@os_helper.skip_unless_working_chmod
def test_extractall(self):
# Test if extractall() correctly restores directory permissions
# and times (see issue1735).
Expand Down Expand Up @@ -660,6 +661,7 @@ def format_mtime(mtime):
tar.close()
os_helper.rmtree(DIR)

@os_helper.skip_unless_working_chmod
def test_extract_directory(self):
dirtype = "ustar/dirtype"
DIR = os.path.join(TEMPDIR, "extractdir")
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_tempfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ def test_choose_directory(self):
support.gc_collect() # For PyPy or other GCs.
os.rmdir(dir)

@os_helper.skip_unless_working_chmod
def test_file_mode(self):
# _mkstemp_inner creates files with the proper mode

Expand Down Expand Up @@ -787,6 +788,7 @@ def test_choose_directory(self):
finally:
os.rmdir(dir)

@os_helper.skip_unless_working_chmod
def test_mode(self):
# mkdtemp creates directories with the proper mode

Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_uu.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def test_encode(self):
with self.assertRaises(TypeError):
uu.encode(inp, out, "t1", 0o644, True)

@os_helper.skip_unless_working_chmod
def test_decode(self):
for backtick in True, False:
inp = io.BytesIO(encodedtextwrapped(0o666, "t1", backtick=backtick))
Expand Down Expand Up @@ -199,6 +200,8 @@ def test_encode(self):
s = fout.read()
self.assertEqual(s, encodedtextwrapped(0o644, self.tmpin))

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

@os_helper.skip_unless_working_chmod
def test_decode_filename(self):
with open(self.tmpin, 'wb') as f:
f.write(encodedtextwrapped(0o644, self.tmpout))
Expand All @@ -221,6 +225,7 @@ def test_decode_filename(self):
s = f.read()
self.assertEqual(s, plaintext)

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

@os_helper.skip_unless_working_chmod
def test_decode_mode(self):
# Verify that decode() will set the given mode for the out_file
expected_mode = 0o444
Expand Down
2 changes: 2 additions & 0 deletions Lib/test/test_zipapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import zipapp
import zipfile
from test.support import requires_zlib
from test.support import os_helper

from unittest.mock import patch

Expand Down Expand Up @@ -317,6 +318,7 @@ def test_content_of_copied_archive(self):
# (Unix only) tests that archives with shebang lines are made executable
@unittest.skipIf(sys.platform == 'win32',
'Windows does not support an executable bit')
@os_helper.skip_unless_working_chmod
def test_shebang_is_executable(self):
# Test that an archive with a shebang line is made executable.
source = self.tmpdir / 'source'
Expand Down
Original file line number Diff line number Diff line change
@@ -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`.
4 changes: 4 additions & 0 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -3282,6 +3282,10 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd,
{
#ifdef HAVE_CHMOD
result = chmod(path->narrow, mode);
#elif defined(__wasi__)
// WASI SDK 15.0 does not support chmod.
// Ignore missing syscall for now.
result = 0;
#else
result = -1;
errno = ENOSYS;
Expand Down
5 changes: 5 additions & 0 deletions Tools/wasm/config.site-wasm32-wasi
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ ac_cv_func_fdopendir=no
# WASIX stubs we don't want to use.
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.
# Disable AF_UNIX and AF_PACKET support, see socketmodule.h.
ac_cv_header_sys_un_h=no
Expand Down

0 comments on commit 22fed60

Please sign in to comment.