diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index a025a2d4ff..7816bb3274 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -13,7 +13,6 @@ import locale import os import pickle -import platform import select import selectors import shutil @@ -188,8 +187,7 @@ def test_access(self): os.close(f) self.assertTrue(os.access(os_helper.TESTFN, os.W_OK)) - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON, BrokenPipeError: (32, 'The process cannot access the file because it is being used by another process. (os error 32)')") + @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; BrokenPipeError: (32, 'The process cannot access the file because it is being used by another process. (os error 32)')") @unittest.skipIf( support.is_emscripten, "Test is unstable under Emscripten." ) @@ -716,8 +714,7 @@ def check_file_attributes(self, result): self.assertTrue(isinstance(result.st_file_attributes, int)) self.assertTrue(0 <= result.st_file_attributes <= 0xFFFFFFFF) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.stat return value doesnt have st_file_attributes attribute") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.stat return value doesnt have st_file_attributes attribute") @unittest.skipUnless(sys.platform == "win32", "st_file_attributes is Win32 specific") def test_file_attributes(self): @@ -739,8 +736,7 @@ def test_file_attributes(self): result.st_file_attributes & stat.FILE_ATTRIBUTE_DIRECTORY, stat.FILE_ATTRIBUTE_DIRECTORY) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.stat (PermissionError: [Errno 5] Access is denied.)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 5] Access is denied") @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") def test_access_denied(self): # Default to FindFirstFile WIN32_FIND_DATA when access is @@ -763,8 +759,7 @@ def test_access_denied(self): self.assertNotEqual(result.st_size, 0) self.assertTrue(os.path.isfile(fname)) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.stat (PermissionError: [Errno 1] Incorrect function.)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.stat (PermissionError: [Errno 1] Incorrect function") @unittest.skipUnless(sys.platform == "win32", "Win32 specific tests") def test_stat_block_device(self): # bpo-38030: os.stat fails for block devices @@ -822,8 +817,7 @@ def _test_utime(self, set_time, filename=None): self.assertEqual(st.st_atime_ns, atime_ns) self.assertEqual(st.st_mtime_ns, mtime_ns) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference))") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference)") def test_utime(self): def set_time(filename, ns): # test the ns keyword parameter @@ -889,8 +883,7 @@ def set_time(filename, ns): os.utime(name, dir_fd=dirfd, ns=ns) self._test_utime(set_time) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference))") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; (AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference)") def test_utime_directory(self): def set_time(filename, ns): # test calling os.utime() on a directory @@ -919,24 +912,21 @@ def _test_utime_current(self, set_time): self.assertAlmostEqual(st.st_mtime, current, delta=delta, msg=msg) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (AssertionError: 3359485824.516508 != 1679742912.516503 within 0.05 delta (1679742912.000005 difference) : st_time=3359485824.516508, current=1679742912.516503, dt=1679742912.000005)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: 3359485824.516508 != 1679742912.516503 within 0.05 delta (1679742912.000005 difference) : st_time=3359485824.516508, current=1679742912.516503, dt=1679742912.000005") def test_utime_current(self): def set_time(filename): # Set to the current time in the new way os.utime(self.fname) self._test_utime_current(set_time) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (AssertionError: 3359485824.5186944 != 1679742912.5186892 within 0.05 delta (1679742912.0000052 difference) : st_time=3359485824.5186944, current=1679742912.5186892, dt=1679742912.0000052)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: 3359485824.5186944 != 1679742912.5186892 within 0.05 delta (1679742912.0000052 difference) : st_time=3359485824.5186944, current=1679742912.5186892, dt=1679742912.0000052") def test_utime_current_old(self): def set_time(filename): # Set to the current time in the old explicit way. os.utime(self.fname, None) self._test_utime_current(set_time) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_utime_nonexistent(self): now = time.time() filename = 'nonexistent' @@ -957,8 +947,7 @@ def get_file_system(self, path): return buf.value # return None if the filesystem is unknown - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (ModuleNotFoundError: No module named '_ctypes')") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ModuleNotFoundError: No module named '_ctypes'") def test_large_time(self): # Many filesystems are limited to the year 2038. At least, the test # pass with NTFS filesystem. @@ -969,7 +958,7 @@ def test_large_time(self): os.utime(self.fname, (large, large)) self.assertEqual(os.stat(self.fname).st_mtime, large) - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (AssertionError: NotImplementedError not raised)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: NotImplementedError not raised") def test_utime_invalid_arguments(self): # seconds and nanoseconds parameters are mutually exclusive with self.assertRaises(ValueError): @@ -1171,9 +1160,8 @@ def test_putenv_unsetenv(self): stdout=subprocess.PIPE, text=True) self.assertEqual(proc.stdout.rstrip(), repr(None)) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (AssertionError: ValueError not raised by putenv)") # On OS X < 10.6, unsetenv() doesn't return a value (bpo-13415). + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: ValueError not raised by putenv") @support.requires_mac_ver(10, 6) def test_putenv_unsetenv_error(self): # Empty variable name is invalid. @@ -1748,18 +1736,15 @@ def walk(self, top, **kwargs): bdirs[:] = list(map(os.fsencode, dirs)) bfiles[:] = list(map(os.fsencode, files)) - # TODO: RUSTPYTHON (TypeError: Can't mix strings and bytes in path components) - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Can't mix strings and bytes in path components def test_compare_to_walk(self): return super().test_compare_to_walk() - # TODO: RUSTPYTHON (TypeError: Can't mix strings and bytes in path components) - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Can't mix strings and bytes in path components def test_dir_fd(self): return super().test_dir_fd() - # TODO: RUSTPYTHON (TypeError: Can't mix strings and bytes in path components) - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Can't mix strings and bytes in path components def test_yields_correct_dir_fd(self): return super().test_yields_correct_dir_fd() @@ -1811,8 +1796,7 @@ def test_mode(self): self.assertEqual(os.stat(path).st_mode & 0o777, 0o555) self.assertEqual(os.stat(parent).st_mode & 0o777, 0o775) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.umask not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.umask not implemented yet for all platforms") @unittest.skipIf( support.is_emscripten or support.is_wasi, "Emscripten's/WASI's umask is a stub." @@ -1831,8 +1815,7 @@ def test_exist_ok_existing_directory(self): # Issue #25583: A drive root could raise PermissionError on Windows os.makedirs(os.path.abspath('/'), exist_ok=True) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.umask not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.umask not implemented yet for all platforms") @unittest.skipIf( support.is_emscripten or support.is_wasi, "Emscripten's/WASI's umask is a stub." @@ -2119,8 +2102,7 @@ def test_urandom_failure(self): """ assert_python_ok('-c', code) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON on Windows (ModuleNotFoundError: No module named 'os')") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ModuleNotFoundError: No module named 'os'") def test_urandom_fd_closed(self): # Issue #21207: urandom() should reopen its fd to /dev/urandom if # closed. @@ -2135,8 +2117,7 @@ def test_urandom_fd_closed(self): """ rc, out, err = assert_python_ok('-Sc', code) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (ModuleNotFoundError: No module named 'os'") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ModuleNotFoundError: No module named 'os'") def test_urandom_fd_reopened(self): # Issue #21207: urandom() should detect its fd to /dev/urandom # changed to something else, and reopen it. @@ -2224,8 +2205,7 @@ def test_execv_with_bad_arglist(self): self.assertRaises(ValueError, os.execv, 'notepad', ('',)) self.assertRaises(ValueError, os.execv, 'notepad', ['']) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.execve not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.execve not implemented yet for all platforms") def test_execvpe_with_bad_arglist(self): self.assertRaises(ValueError, os.execvpe, 'notepad', [], None) self.assertRaises(ValueError, os.execvpe, 'notepad', [], {}) @@ -2285,8 +2265,7 @@ def test_internal_execvpe_str(self): if os.name != "nt": self._test_internal_execvpe(bytes) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.execve not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.execve not implemented yet for all platforms") def test_execve_invalid_env(self): args = [sys.executable, '-c', 'pass'] @@ -2308,8 +2287,7 @@ def test_execve_invalid_env(self): with self.assertRaises(ValueError): os.execve(args[0], args, newenv) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.execve not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.execve not implemented yet for all platforms") @unittest.skipUnless(sys.platform == "win32", "Win32-specific test") def test_execve_with_empty_path(self): # bpo-32890: Check GetLastError() misuse @@ -2390,8 +2368,7 @@ def check_bool(self, f, *args, **kwargs): with self.assertRaises(RuntimeWarning): f(fd, *args, **kwargs) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fdopen(self): self.check(os.fdopen, encoding="utf-8") self.check_bool(os.fdopen, encoding="utf-8") @@ -2448,8 +2425,7 @@ def test_fchmod(self): def test_fchown(self): self.check(os.fchown, -1, -1) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(hasattr(os, 'fpathconf'), 'test needs os.fpathconf()') @unittest.skipIf( support.is_emscripten or support.is_wasi, @@ -2462,22 +2438,19 @@ def test_fpathconf(self): self.check_bool(os.pathconf, "PC_NAME_MAX") self.check_bool(os.fpathconf, "PC_NAME_MAX") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()') def test_ftruncate(self): self.check(os.truncate, 0) self.check(os.ftruncate, 0) self.check_bool(os.truncate, 0) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (OSError: [Errno 18] There are no more files.)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; OSError: [Errno 18] There are no more files") @unittest.skipUnless(hasattr(os, 'lseek'), 'test needs os.lseek()') def test_lseek(self): self.check(os.lseek, 0, 0) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (OSError: [Errno 18] There are no more files.)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; OSError: [Errno 18] There are no more files") @unittest.skipUnless(hasattr(os, 'read'), 'test needs os.read()') def test_read(self): self.check(os.read, 1) @@ -2491,8 +2464,7 @@ def test_readv(self): def test_tcsetpgrpt(self): self.check(os.tcsetpgrp, 0) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (OSError: [Errno 18] There are no more files.)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; OSError: [Errno 18] There are no more files") @unittest.skipUnless(hasattr(os, 'write'), 'test needs os.write()') def test_write(self): self.check(os.write, b" ") @@ -2501,8 +2473,7 @@ def test_write(self): def test_writev(self): self.check(os.writev, [b'abc']) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.get_inheritable not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms") @support.requires_subprocess() def test_inheritable(self): self.check(os.get_inheritable) @@ -2514,13 +2485,11 @@ def test_blocking(self): self.check(os.get_blocking) self.check(os.set_blocking, True) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fchdir(self): return super().test_fchdir() - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fsync(self): return super().test_fsync() @@ -2563,7 +2532,6 @@ def test_unicode_name(self): self.file2 = self.file1 + "2" self._test_link(self.file1, self.file2) - @unittest.skipIf(sys.platform == "win32", "Posix specific tests") class PosixUidGidTests(unittest.TestCase): # uid_t and gid_t are 32-bit unsigned integers on Linux @@ -2765,14 +2733,12 @@ def _kill(self, sig): os.kill(proc.pid, sig) self.assertEqual(proc.wait(), sig) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (ModuleNotFoundError: No module named '_ctypes')") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ModuleNotFoundError: No module named '_ctypes'") def test_kill_sigterm(self): # SIGTERM doesn't mean anything special, but make sure it works self._kill(signal.SIGTERM) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (ModuleNotFoundError: No module named '_ctypes')") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; ModuleNotFoundError: No module named '_ctypes'") def test_kill_int(self): # os.kill on Windows can take an int which gets set as the exit code self._kill(100) @@ -2831,8 +2797,7 @@ def test_CTRL_C_EVENT(self): self._kill_with_event(signal.CTRL_C_EVENT, "CTRL_C_EVENT") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @support.requires_subprocess() def test_CTRL_BREAK_EVENT(self): self._kill_with_event(signal.CTRL_BREAK_EVENT, "CTRL_BREAK_EVENT") @@ -3186,8 +3151,7 @@ def tearDown(self): if os.path.lexists(self.junction): os.unlink(self.junction) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (AttributeError: module '_winapi' has no attribute 'CreateJunction')") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AttributeError: module '_winapi' has no attribute 'CreateJunction'") def test_create_junction(self): _winapi.CreateJunction(self.junction_target, self.junction) self.assertTrue(os.path.lexists(self.junction)) @@ -3201,8 +3165,7 @@ def test_create_junction(self): self.assertEqual(os.path.normcase("\\\\?\\" + self.junction_target), os.path.normcase(os.readlink(self.junction))) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (AttributeError: module '_winapi' has no attribute 'CreateJunction')") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AttributeError: module '_winapi' has no attribute 'CreateJunction'") def test_unlink_removes_junction(self): _winapi.CreateJunction(self.junction_target, self.junction) self.assertTrue(os.path.exists(self.junction)) @@ -3262,8 +3225,7 @@ def test_getfinalpathname_handles(self): self.assertEqual(0, handle_delta) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.stat (PermissionError: [Errno 5] Access is denied.)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.stat PermissionError: [Errno 5] Access is denied") @support.requires_subprocess() def test_stat_unlink_race(self): # bpo-46785: the implementation of os.stat() falls back to reading @@ -3473,8 +3435,7 @@ def test_waitstatus_to_exitcode(self): with self.assertRaises(TypeError): os.waitstatus_to_exitcode(0.0) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.spawnv not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.spawnv not implemented yet for all platforms") @unittest.skipUnless(sys.platform == 'win32', 'win32-specific test') def test_waitpid_windows(self): # bpo-40138: test os.waitpid() and os.waitstatus_to_exitcode() @@ -3483,8 +3444,7 @@ def test_waitpid_windows(self): code = f'import _winapi; _winapi.ExitProcess({STATUS_CONTROL_C_EXIT})' self.check_waitpid(code, exitcode=STATUS_CONTROL_C_EXIT) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (OverflowError: Python int too large to convert to Rust i32)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; OverflowError: Python int too large to convert to Rust i32") @unittest.skipUnless(sys.platform == 'win32', 'win32-specific test') def test_waitstatus_to_exitcode_windows(self): max_exitcode = 2 ** 32 - 1 @@ -3608,8 +3568,7 @@ def test_nowait(self): pid = os.spawnv(os.P_NOWAIT, program, args) support.wait_process(pid, exitcode=self.exitcode) - # TODO: RUSTPYTHON fix spawnv bytes - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; fix spawnv bytes @requires_os_func('spawnve') def test_spawnve_bytes(self): # Test bytes handling in parse_arglist and parse_envlist (#28114) @@ -4224,16 +4183,8 @@ def test_eventfd_select(self): @unittest.skipIf(sys.platform == "android", "gh-124873: Test is flaky on Android") @support.requires_linux_version(2, 6, 30) class TimerfdTests(unittest.TestCase): - # 1 ms accuracy is reliably achievable on every platform except Android - # emulators, where we allow 10 ms (gh-108277). - - # XXX: RUSTPYTHON; AttributeError: module 'platform' has no attribute 'android_ver' - #if sys.platform == "android" and platform.android_ver().is_emulator: - if sys.platform == "android": - CLOCK_RES_PLACES = 2 - else: - CLOCK_RES_PLACES = 3 - + # gh-126112: Use 10 ms to tolerate slow buildbots + CLOCK_RES_PLACES = 2 # 10 ms CLOCK_RES = 10 ** -CLOCK_RES_PLACES CLOCK_RES_NS = 10 ** (9 - CLOCK_RES_PLACES) @@ -4577,8 +4528,7 @@ class Str(str): self.filenames = self.bytes_filenames + self.unicode_filenames - # TODO: RUSTPYTHON (AssertionError: b'@test_22106_tmp\xe7w\xf0' is not b'@test_22106_tmp\xe7w\xf0' : ) - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b'@test_22106_tmp\xe7w\xf0' is not b'@test_22106_tmp\xe7w\xf0' : def test_oserror_filename(self): funcs = [ (self.filenames, os.chdir,), @@ -4671,8 +4621,7 @@ def test_process_cpu_count_affinity(self): # FD inheritance check is only useful for systems with process support. @support.requires_subprocess() class FDInheritanceTests(unittest.TestCase): - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.get_inheritable not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms") def test_get_set_inheritable(self): fd = os.open(__file__, os.O_RDONLY) self.addCleanup(os.close, fd) @@ -4717,8 +4666,7 @@ def test_get_set_inheritable_o_path(self): os.set_inheritable(fd, False) self.assertEqual(os.get_inheritable(fd), False) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.get_inheritable not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms") def test_get_set_inheritable_badf(self): fd = os_helper.make_bad_fd() @@ -4734,8 +4682,7 @@ def test_get_set_inheritable_badf(self): os.set_inheritable(fd, False) self.assertEqual(ctx.exception.errno, errno.EBADF) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.get_inheritable not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.get_inheritable not implemented yet for all platforms") def test_open(self): fd = os.open(__file__, os.O_RDONLY) self.addCleanup(os.close, fd) @@ -4749,7 +4696,6 @@ def test_pipe(self): self.assertEqual(os.get_inheritable(rfd), False) self.assertEqual(os.get_inheritable(wfd), False) - # TODO: RUSTPYTHON @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; os.dup on windows") def test_dup(self): fd1 = os.open(__file__, os.O_RDONLY) @@ -4759,15 +4705,13 @@ def test_dup(self): self.addCleanup(os.close, fd2) self.assertEqual(os.get_inheritable(fd2), False) - # TODO: RUSTPYTHON @unittest.skipIf(sys.platform == 'win32', "TODO: RUSTPYTHON; os.dup on windows") def test_dup_standard_stream(self): fd = os.dup(1) self.addCleanup(os.close, fd) self.assertGreater(fd, 0) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON os.dup not implemented yet for all platforms") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; os.dup not implemented yet for all platforms") @unittest.skipUnless(sys.platform == 'win32', 'win32-specific test') def test_dup_nul(self): # os.dup() was creating inheritable fds for character files. @@ -4907,8 +4851,7 @@ class PathTConverterTests(unittest.TestCase): ('open', False, (os.O_RDONLY,), getattr(os, 'close', None)), ] - # TODO: RUSTPYTHON (AssertionError: TypeError not raised) - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: TypeError not raised def test_path_t_converter(self): str_filename = os_helper.TESTFN if os.name == 'nt': @@ -4993,13 +4936,11 @@ def setUp(self): self.addCleanup(os_helper.rmtree, self.path) os.mkdir(self.path) - # TODO: RUSTPYTHON (AssertionError: TypeError not raised by DirEntry) - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: TypeError not raised by DirEntry def test_uninstantiable(self): self.assertRaises(TypeError, os.DirEntry) - # TODO: RUSTPYTHON (pickle.PicklingError: Can't pickle : it's not found as _os.DirEntry) - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; pickle.PicklingError: Can't pickle : it's not found as _os.DirEntry def test_unpickable(self): filename = create_file(os.path.join(self.path, "file.txt"), b'python') entry = [entry for entry in os.scandir(self.path)].pop() @@ -5044,8 +4985,7 @@ def assert_stat_equal(self, stat1, stat2, skip_fields): else: self.assertEqual(stat1, stat2) - # TODO: RUSTPYTHON (AssertionError: TypeError not raised by ScandirIter) - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: TypeError not raised by ScandirIter def test_uninstantiable(self): scandir_iter = os.scandir(self.path) self.assertRaises(TypeError, type(scandir_iter)) @@ -5088,8 +5028,7 @@ def check_entry(self, entry, name, is_dir, is_file, is_symlink): entry_lstat, os.name == 'nt') - # TODO: RUSTPYTHON - @unittest.skipIf(sys.platform == "linux", "TODO: RUSTPYTHON, flaky test") + @unittest.skipIf(sys.platform == 'linux', "TODO: RUSTPYTHON; flaky test") def test_attributes(self): link = os_helper.can_hardlink() symlink = os_helper.can_symlink() @@ -5189,8 +5128,7 @@ def test_fspath_protocol_bytes(self): self.assertEqual(fspath, os.path.join(os.fsencode(self.path),bytes_filename)) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON entry.is_dir() is False") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; entry.is_dir() is False") def test_removed_dir(self): path = os.path.join(self.path, 'dir') @@ -5213,8 +5151,7 @@ def test_removed_dir(self): self.assertRaises(FileNotFoundError, entry.stat) self.assertRaises(FileNotFoundError, entry.stat, follow_symlinks=False) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON entry.is_file() is False") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; entry.is_file() is False") def test_removed_file(self): entry = self.create_file_entry() os.unlink(entry.path) @@ -5275,8 +5212,7 @@ def test_bytes_like(self): with self.assertRaises(TypeError): os.scandir(path_bytes) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON @unittest.skipUnless(os.listdir in os.supports_fd, 'fd support for listdir required for this test.') def test_fd(self): @@ -5303,8 +5239,7 @@ def test_fd(self): st = os.stat(entry.name, dir_fd=fd, follow_symlinks=False) self.assertEqual(entry.stat(follow_symlinks=False), st) - # TODO: RUSTPYTHON - @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON (AssertionError: FileNotFoundError not raised by scandir)") + @unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; AssertionError: FileNotFoundError not raised by scandir") @unittest.skipIf(support.is_wasi, "WASI maps '' to cwd") def test_empty_path(self): self.assertRaises(FileNotFoundError, os.scandir, '') @@ -5360,8 +5295,7 @@ def test_context_manager_exception(self): with self.check_no_resource_warning(): del iterator - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_resource_warning(self): self.create_file("file.txt") self.create_file("file2.txt") @@ -5434,8 +5368,7 @@ class A(os.PathLike): def test_pathlike_class_getitem(self): self.assertIsInstance(os.PathLike[bytes], types.GenericAlias) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_pathlike_subclass_slots(self): class A(os.PathLike): __slots__ = () @@ -5443,8 +5376,7 @@ def __fspath__(self): return '' self.assertFalse(hasattr(A(), '__dict__')) - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fspath_set_to_None(self): class Foo: __fspath__ = None @@ -5547,8 +5479,7 @@ def test_fork_warns_when_non_python_thread_exists(self): self.assertEqual(err.decode("utf-8"), "") self.assertEqual(out.decode("utf-8"), "") - # TODO: RUSTPYTHON - @unittest.expectedFailure + @unittest.expectedFailure # TODO: RUSTPYTHON def test_fork_at_finalization(self): code = """if 1: import atexit diff --git a/tools/test_marker/confs/test_os.toml b/tools/test_marker/confs/test_os.toml new file mode 100644 index 0000000000..8b39f6b1e8 --- /dev/null +++ b/tools/test_marker/confs/test_os.toml @@ -0,0 +1,103 @@ +path = "Lib/test/test_os.py" + +[FileTests] +test_closerange = { skipIf = { cond = "sys.platform == 'win32'", reason = "BrokenPipeError: (32, 'The process cannot access the file because it is being used by another process. (os error 32)')" } } + +[StatAttributeTests] +test_file_attributes = { expectedFailureIfWindows = { reason = "os.stat return value doesnt have st_file_attributes attribute" } } +test_access_denied = { expectedFailureIfWindows = { reason = "os.stat (PermissionError: [Errno 5] Access is denied" } } +test_stat_block_device = { expectedFailureIfWindows = { reason = "os.stat (PermissionError: [Errno 1] Incorrect function" } } + +[UtimeTests] +test_utime = { expectedFailureIfWindows = { reason = "AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference)" } } +test_utime_directory = { expectedFailureIfWindows = { reason = "(AssertionError: 2.002003 != 1.002003 within 1e-06 delta (1.0000000000000002 difference)" } } +test_utime_current = { expectedFailureIfWindows = { reason = "AssertionError: 3359485824.516508 != 1679742912.516503 within 0.05 delta (1679742912.000005 difference) : st_time=3359485824.516508, current=1679742912.516503, dt=1679742912.000005" } } +test_utime_current_old = { expectedFailureIfWindows = { reason = "AssertionError: 3359485824.5186944 != 1679742912.5186892 within 0.05 delta (1679742912.0000052 difference) : st_time=3359485824.5186944, current=1679742912.5186892, dt=1679742912.0000052" } } +test_utime_nonexistent = { expectedFailure = { reason = "" } } +test_large_time = { expectedFailureIfWindows = { reason = "ModuleNotFoundError: No module named '_ctypes'" } } +test_utime_invalid_arguments = { expectedFailureIfWindows = { reason = "AssertionError: NotImplementedError not raised" } } + +[EnvironTests] +test_putenv_unsetenv_error = { expectedFailureIfWindows = { reason = "AssertionError: ValueError not raised by putenv" } } + +[BytesWalkTests] +test_compare_to_walk = { expectedFailure = { reason = "TypeError: Can't mix strings and bytes in path components" } } +test_dir_fd = { expectedFailure = { reason = "TypeError: Can't mix strings and bytes in path components" } } +test_yields_correct_dir_fd = { expectedFailure = { reason = "TypeError: Can't mix strings and bytes in path components" } } + +[MakedirTests] +test_exist_ok_existing_directory = { expectedFailureIfWindows = { reason = "os.umask not implemented yet for all platforms" } } +test_exist_ok_s_isgid_directory = { expectedFailureIfWindows = { reason = "os.umask not implemented yet for all platforms" } } + +[URandomFDTests] +test_urandom_fd_closed = { expectedFailureIfWindows = { reason = "ModuleNotFoundError: No module named 'os'" } } +test_urandom_fd_reopened = { expectedFailureIfWindows = { reason = "ModuleNotFoundError: No module named 'os'" } } + +[ExecTests] +test_execvpe_with_bad_arglist = { expectedFailureIfWindows = { reason = "os.execve not implemented yet for all platforms" } } +test_execve_invalid_env = { expectedFailureIfWindows = { reason = "os.execve not implemented yet for all platforms" } } +test_execve_with_empty_path = { expectedFailureIfWindows = { reason = "os.execve not implemented yet for all platforms" } } + +[TestInvalidFD] +test_fdopen = { expectedFailure = { reason = "" } } +test_fpathconf = { expectedFailure = { reason = "" } } +test_ftruncate = { expectedFailure = { reason = "" } } +test_lseek = { expectedFailureIfWindows = { reason = "OSError: [Errno 18] There are no more files" } } +test_read = { expectedFailureIfWindows = { reason = "OSError: [Errno 18] There are no more files" } } +test_write = { expectedFailureIfWindows = { reason = "OSError: [Errno 18] There are no more files" } } +test_inheritable = { expectedFailureIfWindows = { reason = "os.get_inheritable not implemented yet for all platforms" } } +test_fchdir = { expectedFailure = { reason = "" } } +test_fsync = { expectedFailure = { reason = "" } } + +[Win32KillTests] +test_kill_sigterm = { expectedFailureIfWindows = { reason = "ModuleNotFoundError: No module named '_ctypes'" } } +test_kill_int = { expectedFailureIfWindows = { reason = "ModuleNotFoundError: No module named '_ctypes'" } } +test_CTRL_BREAK_EVENT = { expectedFailure = { reason = "" } } + +[Win32JunctionTests] +test_create_junction = { expectedFailureIfWindows = { reason = "AttributeError: module '_winapi' has no attribute 'CreateJunction'" } } +test_unlink_removes_junction = { expectedFailureIfWindows = { reason = "AttributeError: module '_winapi' has no attribute 'CreateJunction'" } } + +[Win32NtTests] +test_stat_unlink_race = { expectedFailureIfWindows = { reason = "os.stat PermissionError: [Errno 5] Access is denied" } } + +[PidTests] +test_waitpid_windows = { expectedFailureIfWindows = { reason = "os.spawnv not implemented yet for all platforms" } } +test_waitstatus_to_exitcode_windows = { expectedFailureIfWindows = { reason = "OverflowError: Python int too large to convert to Rust i32" } } + +[SpawnTests] +test_spawnve_bytes = { expectedFailure = { reason = "fix spawnv bytes" } } + +[OSErrorTests] +test_oserror_filename = { expectedFailure = { reason = "AssertionError: b'@test_22106_tmp\\xe7w\\xf0' is not b'@test_22106_tmp\\xe7w\\xf0' : " } } + +[FDInheritanceTests] +test_get_set_inheritable = { expectedFailureIfWindows = { reason = "os.get_inheritable not implemented yet for all platforms" } } +test_get_set_inheritable_badf = { expectedFailureIfWindows = { reason = "os.get_inheritable not implemented yet for all platforms" } } +test_open = { expectedFailureIfWindows = { reason = "os.get_inheritable not implemented yet for all platforms" } } +test_dup = { skipIf = { cond = "sys.platform == 'win32'", reason = "os.dup on windows" } } +test_dup_standard_stream = { skipIf = { cond = "sys.platform == 'win32'", reason = "os.dup on windows" } } +test_dup_nul = { expectedFailureIfWindows = { reason = "os.dup not implemented yet for all platforms" } } + +[PathTConverterTests] +test_path_t_converter = { expectedFailure = { reason = "AssertionError: TypeError not raised" } } + +[TestDirEntry] +test_uninstantiable = { expectedFailure = { reason = "AssertionError: TypeError not raised by DirEntry" } } +test_unpickable = { expectedFailure = { reason = "pickle.PicklingError: Can't pickle : it's not found as _os.DirEntry" } } + +[TestScandir] +test_uninstantiable = { expectedFailure = { reason = "AssertionError: TypeError not raised by ScandirIter" } } +test_attributes = { skipIf = { cond = "sys.platform == 'linux'", reason = "flaky test" } } +test_removed_dir = { expectedFailureIfWindows = { reason = "entry.is_dir() is False" } } +test_removed_file = { expectedFailureIfWindows = { reason = "entry.is_file() is False" } } +test_fd = { expectedFailure = { reason = "" } } +test_empty_path = { expectedFailureIfWindows = { reason = "AssertionError: FileNotFoundError not raised by scandir" } } +test_resource_warning = { expectedFailure = { reason = "" } } + +[TestPEP519] +test_pathlike_subclass_slots = { expectedFailure = { reason = "" } } +test_fspath_set_to_None = { expectedFailure = { reason = "" } } + +[ForkTests] +test_fork_at_finalization = { expectedFailure = { reason = "" } } diff --git a/tools/test_marker/main.py b/tools/test_marker/main.py new file mode 100755 index 0000000000..01a9593329 --- /dev/null +++ b/tools/test_marker/main.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +import ast +import pathlib +import tomllib +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from collections.abc import Iterator + +COL_OFFSET = 4 +INDENT1 = " " * COL_OFFSET +INDENT2 = " " * COL_OFFSET * 2 +COMMENT = "TODO: RUSTPYTHON" + +ROOT_DIR = pathlib.Path(__file__).parents[2] +CONFS = ROOT_DIR / "tools" / "test_marker" / "confs" + + +type Patch = dict[str, dict[str, str]] +type Conf = dict[str, Patch] + + +def format_patch(patch_conf: Patch) -> str: + """ + Transforms a patch definition to a raw python code. + + Parameters + ---------- + patch_conf : Patch + Conf of the patch. + + Returns + ------- + str + Raw python source code. + + Examples + -------- + >>> patch = {"expectedFailure": {"reason": "lorem ipsum"}} + >>> format_patch(patch) + '@unittest.expectedFailure # TODO: RUSTPYTHON; lorem ipsum' + """ + method, conf = next(iter(patch_conf.items())) + prefix = f"@unittest.{method}" + + reason = conf.get("reason", "") + res = "" + match method: + case "expectedFailure": + res = f"{prefix} # {COMMENT}; {reason}" + case "expectedFailureIfWindows" | "skip": + res = f'{prefix}("{COMMENT}; {reason}")' + case "skipIf": + cond = conf["cond"] + res = f'{prefix}({cond}, "{COMMENT}; {reason}")' + + return res.strip().rstrip(";").strip() + + +def is_patch_present(node: ast.Attribute | ast.Call, patch_conf: Patch) -> bool: + """ + Detect whether an AST node (of a decorator) is matching to our patch. + + We accept both `ast.Attribute` and `ast.Call` because: + * ast.Attribute: `@unittest.expectedFailure` + * ast.Call: `@unittest.expectedFailureIfWindows(...)` / `@unittest.skipIf(...)` + + Parameters + ---------- + node : ast.Attribute | ast.Call + AST node to query. + patch_conf : Patch + Patch(es) to match against. + + Returns + ------- + bool + Whether or not we got a match. + """ + is_attr = isinstance(node, ast.Attribute) + attr_node = node if is_attr else node.func + + if isinstance(attr_node, ast.Name): + return False + + if attr_node.value.id != "unittest": + return False + + if is_attr: + return node.attr in patch_conf + + return "RUSTPYTHON" in ast.unparse(node) + + +def iter_patches(tree: ast.Module, conf: Conf) -> "Iterator[tuple[int, str]]": + """ + Get needed patches to apply for given ast tree based on the conf. + + Parameters + ---------- + tree : ast.Module + AST tree to iterate on. + conf : Conf + Dict of `{ClassName: {test_name: Patch}}`. + + Yields + ------ + lineno : int + Line number where to insert the patch. + patch : str + Raw python code to be inserted at `lineno`. + """ + # Phase 1: Iterate and mark existing tests + for key, nodes in ast.iter_fields(tree): + if key != "body": + continue + + for i, cls_node in enumerate(nodes): + if not isinstance(cls_node, ast.ClassDef): + continue + + if not (cls_conf := conf.get(cls_node.name)): + continue + + for fn_node in cls_node.body: + if not isinstance(fn_node, ast.FunctionDef): + continue + + if not (patch_conf := cls_conf.pop(fn_node.name, None)): + continue + + if any( + is_patch_present(dec_node, patch_conf) + for dec_node in fn_node.decorator_list + if isinstance(dec_node, (ast.Attribute, ast.Call)) + ): + continue + + lineno = min( + (dec_node.lineno for dec_node in fn_node.decorator_list), + default=fn_node.lineno, + ) + + indent = " " * fn_node.col_offset + patch = format_patch(patch_conf) + yield (lineno - 1, f"{indent}{patch}") + + # Phase 2: Iterate and mark inhereted tests + for key, nodes in ast.iter_fields(tree): + if key != "body": + continue + + for i, cls_node in enumerate(nodes): + if not isinstance(cls_node, ast.ClassDef): + continue + + if not (cls_conf := conf.get(cls_node.name)): + continue + + for fn_name, patch_conf in cls_conf.items(): + patch = format_patch(patch_conf) + yield ( + cls_node.end_lineno, + f""" +{INDENT1}{patch} +{INDENT1}def {fn_name}(self): +{INDENT2}return super().{fn_name}() +""".rstrip(), + ) + + +def apply_conf(contents: str, conf: dict) -> str: + """ + Patch a given source code based on the conf. + + Parameters + ---------- + contents : str + Raw python source code. + conf : Conf + Dict of `{ClassName: {test_name: Patch}}`. + + Returns + ------- + str + Patched raw python code. + """ + lines = contents.splitlines() + tree = ast.parse(contents) + + # Going in reverse to not distrupt the lines offset + patches = list(iter_patches(tree, conf)) + for lineno, patch in sorted(patches, reverse=True): + lines.insert(lineno, patch) + + return "\n".join(lines) + + +def main(): + for conf_file in CONFS.rglob("*.toml"): + conf = tomllib.loads(conf_file.read_text()) + path = ROOT_DIR / conf.pop("path") + patched = apply_conf(path.read_text(), conf) + path.write_text(patched + "\n") + + +if __name__ == "__main__": + main()