|
49 | 49 | 'encoding (%s). Unicode filename tests may not be effective' |
50 | 50 | % (TESTFN_UNENCODABLE, sys.getfilesystemencoding())) |
51 | 51 | TESTFN_UNENCODABLE = None |
52 | | -# Mac OS X denies unencodable filenames (invalid utf-8) |
53 | | -elif sys.platform != 'darwin': |
| 52 | +# macOS and Emscripten deny unencodable filenames (invalid utf-8) |
| 53 | +elif sys.platform not in {'darwin', 'emscripten', 'wasi'}: |
54 | 54 | try: |
55 | 55 | # ascii and utf-8 cannot encode the byte 0xff |
56 | 56 | b'\xff'.decode(sys.getfilesystemencoding()) |
@@ -171,9 +171,13 @@ def can_symlink(): |
171 | 171 | global _can_symlink |
172 | 172 | if _can_symlink is not None: |
173 | 173 | return _can_symlink |
174 | | - symlink_path = TESTFN + "can_symlink" |
| 174 | + # WASI / wasmtime prevents symlinks with absolute paths, see man |
| 175 | + # openat2(2) RESOLVE_BENEATH. Almost all symlink tests use absolute |
| 176 | + # paths. Skip symlink tests on WASI for now. |
| 177 | + src = os.path.abspath(TESTFN) |
| 178 | + symlink_path = src + "can_symlink" |
175 | 179 | try: |
176 | | - os.symlink(TESTFN, symlink_path) |
| 180 | + os.symlink(src, symlink_path) |
177 | 181 | can = True |
178 | 182 | except (OSError, NotImplementedError, AttributeError): |
179 | 183 | can = False |
@@ -233,6 +237,84 @@ def skip_unless_xattr(test): |
233 | 237 | return test if ok else unittest.skip(msg)(test) |
234 | 238 |
|
235 | 239 |
|
| 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 | + 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 | + |
| 276 | +# Check whether the current effective user has the capability to override |
| 277 | +# DAC (discretionary access control). Typically user root is able to |
| 278 | +# bypass file read, write, and execute permission checks. The capability |
| 279 | +# is independent of the effective user. See capabilities(7). |
| 280 | +_can_dac_override = None |
| 281 | + |
| 282 | +def can_dac_override(): |
| 283 | + global _can_dac_override |
| 284 | + |
| 285 | + if not can_chmod(): |
| 286 | + _can_dac_override = False |
| 287 | + if _can_dac_override is not None: |
| 288 | + return _can_dac_override |
| 289 | + |
| 290 | + try: |
| 291 | + with open(TESTFN, "wb") as f: |
| 292 | + os.chmod(TESTFN, 0o400) |
| 293 | + try: |
| 294 | + with open(TESTFN, "wb"): |
| 295 | + pass |
| 296 | + except OSError: |
| 297 | + _can_dac_override = False |
| 298 | + else: |
| 299 | + _can_dac_override = True |
| 300 | + finally: |
| 301 | + unlink(TESTFN) |
| 302 | + |
| 303 | + return _can_dac_override |
| 304 | + |
| 305 | + |
| 306 | +def skip_if_dac_override(test): |
| 307 | + ok = not can_dac_override() |
| 308 | + msg = "incompatible with CAP_DAC_OVERRIDE" |
| 309 | + return test if ok else unittest.skip(msg)(test) |
| 310 | + |
| 311 | + |
| 312 | +def skip_unless_dac_override(test): |
| 313 | + ok = can_dac_override() |
| 314 | + msg = "requires CAP_DAC_OVERRIDE" |
| 315 | + return test if ok else unittest.skip(msg)(test) |
| 316 | + |
| 317 | + |
236 | 318 | def unlink(filename): |
237 | 319 | try: |
238 | 320 | _unlink(filename) |
@@ -459,7 +541,10 @@ def create_empty_file(filename): |
459 | 541 | def open_dir_fd(path): |
460 | 542 | """Open a file descriptor to a directory.""" |
461 | 543 | assert os.path.isdir(path) |
462 | | - dir_fd = os.open(path, os.O_RDONLY) |
| 544 | + flags = os.O_RDONLY |
| 545 | + if hasattr(os, "O_DIRECTORY"): |
| 546 | + flags |= os.O_DIRECTORY |
| 547 | + dir_fd = os.open(path, flags) |
463 | 548 | try: |
464 | 549 | yield dir_fd |
465 | 550 | finally: |
@@ -502,7 +587,7 @@ def __fspath__(self): |
502 | 587 | def fd_count(): |
503 | 588 | """Count the number of open file descriptors. |
504 | 589 | """ |
505 | | - if sys.platform.startswith(('linux', 'freebsd')): |
| 590 | + if sys.platform.startswith(('linux', 'freebsd', 'emscripten')): |
506 | 591 | try: |
507 | 592 | names = os.listdir("/proc/self/fd") |
508 | 593 | # Subtract one because listdir() internally opens a file |
@@ -568,6 +653,11 @@ def temp_umask(umask): |
568 | 653 | yield |
569 | 654 | finally: |
570 | 655 | os.umask(oldmask) |
| 656 | +else: |
| 657 | + @contextlib.contextmanager |
| 658 | + def temp_umask(umask): |
| 659 | + """no-op on platforms without umask()""" |
| 660 | + yield |
571 | 661 |
|
572 | 662 |
|
573 | 663 | class EnvironmentVarGuard(collections.abc.MutableMapping): |
@@ -610,6 +700,10 @@ def set(self, envvar, value): |
610 | 700 | def unset(self, envvar): |
611 | 701 | del self[envvar] |
612 | 702 |
|
| 703 | + def copy(self): |
| 704 | + # We do what os.environ.copy() does. |
| 705 | + return dict(self) |
| 706 | + |
613 | 707 | def __enter__(self): |
614 | 708 | return self |
615 | 709 |
|
|
0 commit comments