From cb9e8be301cc32d285fe7a0dc50b2efb904f5344 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 28 Feb 2023 11:05:56 -0300 Subject: [PATCH] Move logic to get_user_id in compat --- src/_pytest/compat.py | 38 ++++++++++++++++++++++++-------------- src/_pytest/tmpdir.py | 24 ++++++++++-------------- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 6cede2133..352211de8 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -1,4 +1,6 @@ """Python version compatibility code.""" +from __future__ import annotations + import dataclasses import enum import functools @@ -12,11 +14,8 @@ from typing import Any from typing import Callable from typing import Generic from typing import NoReturn -from typing import Optional -from typing import Tuple from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union import py @@ -46,7 +45,7 @@ LEGACY_PATH = py.path. local # fmt: on -def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: +def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: """Internal wrapper to prepare lazy proxies for legacy_path instances""" return LEGACY_PATH(path) @@ -56,7 +55,7 @@ def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: # https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions class NotSetType(enum.Enum): token = 0 -NOTSET: "Final" = NotSetType.token # noqa: E305 +NOTSET: Final = NotSetType.token # noqa: E305 # fmt: on if sys.version_info >= (3, 8): @@ -94,7 +93,7 @@ def is_async_function(func: object) -> bool: return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) -def getlocation(function, curdir: Optional[str] = None) -> str: +def getlocation(function, curdir: str | None = None) -> str: function = get_real_func(function) fn = Path(inspect.getfile(function)) lineno = function.__code__.co_firstlineno @@ -132,8 +131,8 @@ def getfuncargnames( *, name: str = "", is_method: bool = False, - cls: Optional[type] = None, -) -> Tuple[str, ...]: + cls: type | None = None, +) -> tuple[str, ...]: """Return the names of a function's mandatory arguments. Should return the names of all function arguments that: @@ -197,7 +196,7 @@ def getfuncargnames( return arg_names -def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]: +def get_default_arg_names(function: Callable[..., Any]) -> tuple[str, ...]: # Note: this code intentionally mirrors the code at the beginning of # getfuncargnames, to get the arguments which were excluded from its result # because they had default values. @@ -228,7 +227,7 @@ def _bytes_to_ascii(val: bytes) -> str: return val.decode("ascii", "backslashreplace") -def ascii_escaped(val: Union[bytes, str]) -> str: +def ascii_escaped(val: bytes | str) -> str: r"""If val is pure ASCII, return it as an str, otherwise, escape bytes objects into a sequence of escaped bytes: @@ -355,7 +354,6 @@ else: if sys.version_info >= (3, 8): from functools import cached_property as cached_property else: - from typing import Type class cached_property(Generic[_S, _T]): __slots__ = ("func", "__doc__") @@ -366,12 +364,12 @@ else: @overload def __get__( - self, instance: None, owner: Optional[Type[_S]] = ... - ) -> "cached_property[_S, _T]": + self, instance: None, owner: type[_S] | None = ... + ) -> cached_property[_S, _T]: ... @overload - def __get__(self, instance: _S, owner: Optional[Type[_S]] = ...) -> _T: + def __get__(self, instance: _S, owner: type[_S] | None = ...) -> _T: ... def __get__(self, instance, owner=None): @@ -381,6 +379,18 @@ else: return value +def get_user_id() -> int | None: + """Return the current user id, or None if we cannot get it reliably on the current platform.""" + # win32 does not have a getuid() function. + # On Emscripten, getuid() is a stub that always returns 0. + if sys.platform in ("win32", "emscripten"): + return None + # getuid shouldn't fail, but cpython defines such a case. + # Let's hope for the best. + uid = os.getuid() + return uid if uid != -1 else None + + # Perform exhaustiveness checking. # # Consider this example: diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 64f1de072..5f347665f 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -2,7 +2,6 @@ import dataclasses import os import re -import sys import tempfile from pathlib import Path from shutil import rmtree @@ -30,7 +29,7 @@ from .pathlib import make_numbered_dir from .pathlib import make_numbered_dir_with_cleanup from .pathlib import rm_rf from .pathlib import cleanup_dead_symlink -from _pytest.compat import final +from _pytest.compat import final, get_user_id from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import hookimpl @@ -176,19 +175,16 @@ class TempPathFactory: # Also, to keep things private, fixup any world-readable temp # rootdir's permissions. Historically 0o755 was used, so we can't # just error out on this, at least for a while. - if sys.platform != "win32" and sys.platform != "emscripten": - uid = os.getuid() + uid = get_user_id() + if uid is not None: rootdir_stat = rootdir.stat() - # getuid shouldn't fail, but cpython defines such a case. - # Let's hope for the best. - if uid != -1: - if rootdir_stat.st_uid != uid: - raise OSError( - f"The temporary directory {rootdir} is not owned by the current user. " - "Fix this and try again." - ) - if (rootdir_stat.st_mode & 0o077) != 0: - os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) + if rootdir_stat.st_uid != uid: + raise OSError( + f"The temporary directory {rootdir} is not owned by the current user. " + "Fix this and try again." + ) + if (rootdir_stat.st_mode & 0o077) != 0: + os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) keep = self._retention_count if self._retention_policy == "none": keep = 0