Source code for extra_platforms.pytest

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Pytest decorators to skip tests depending on the platform they're run on.

Generates a pair of ready-to-use `@skip_<id>` and `@unless_<id>` decorators for
each platform and group.

```{currentmodule} extra_platforms
```

```{note}
Type checkers like mypy do not run module code, so they cannot see the decorators
generated at import time. The `MarkDecorator` declarations under `TYPE_CHECKING` at
the end of this module make them visible.
```
"""

from __future__ import annotations

try:
    import pytest
except ImportError:
    raise ImportError(
        "You need to install the pytest extra to use this module: "
        "uv pip install 'extra-platforms[pytest]'"
    )

import sys

from .detection import _detection_registry, is_windows
from .group import Group
from .group_data import ALL_GROUPS, ALL_TRAITS

TYPE_CHECKING = False
if TYPE_CHECKING:
    from collections.abc import Callable
    from pathlib import Path

    from _pytest.mark.structures import MarkDecorator

    from .trait import Trait


[docs] def write_fake_executable( path: Path, *, stdout: str = "", stderr: str = "", returncode: int = 0, ) -> Path: """Write a fake executable for ``path`` and return the path to run. On POSIX the script is written to ``path`` with a Python shebang (the interpreter running the tests), not ``#!/bin/sh``, so it execs even in hermetic build sandboxes that ship no ``/bin/sh`` (Guix, Nixpkgs, ...). On Windows, where a shebang is ignored, the Python body is written to a ``.py`` sidecar and a ``.cmd`` launcher beside ``path`` is returned instead. Either way the command writes ``stdout`` and ``stderr`` verbatim, then exits with ``returncode``, ignoring its arguments: enough to stand in for a command a test drives through a real subprocess. """ lines = ["import sys"] if stdout: lines.append(f"sys.stdout.write({stdout!r})") if stderr: lines.append(f"sys.stderr.write({stderr!r})") lines.append(f"sys.exit({returncode})") script = "\n".join(lines) + "\n" if is_windows(): # Windows ignores the shebang and cannot exec a bare script file, so run # the body from a .py sidecar through a .cmd launcher instead. script_file = path.with_suffix(".py") script_file.write_text(script, encoding="UTF-8") launcher = path.with_suffix(".cmd") launcher.write_text( f'@echo off\n"{sys.executable}" "{script_file}"\n', encoding="UTF-8", ) return launcher path.write_text(f"#!{sys.executable}\n{script}", encoding="UTF-8") path.chmod(0o755) return path
class _DeferredCondition: """Defer the evaluation of a condition. This allows a callable returning a boolean to be evaluated only when the boolean value is requested. Pytest's marks can have a condition attached to them, which implements our ready-to-use `@skip` and `@unless` decorators. But Pytest evaluates a boolean condition when the mark is created, which would call every detection heuristic while this module generates its decorators. ```{seealso} The issue is discussed upstream in [`pytest-dev/pytest#7395`](https://github.com/pytest-dev/pytest/issues/7395) and [`pytest-dev/pytest#9650`](https://github.com/pytest-dev/pytest/issues/9650). ``` """ def __init__(self, condition: Callable[[], bool], invert: bool = False) -> None: self.condition = condition self.invert = invert def __bool__(self) -> bool: """Call the deferred condition and return its result.""" result = self.condition() return not result if self.invert else result def __call__(self) -> bool: """Allow the condition to be called as a function.""" return bool(self) def _decorator_docstring(obj: Trait | Group, skip: bool) -> str: """Generate the docstring of a Pytest decorator. Links the trait or group the decorator is about, and its detection function. Full paths are required: this module is `extra_platforms.pytest`, so a short reference like `` {data}`~AARCH64` `` would resolve against it. :param obj: The trait or group to document. :param skip: Whether this is a skip decorator, or an unless decorator. """ symbol = f"{{data}}`~extra_platforms.{obj.symbol_id}`" if isinstance(obj, Group): condition = f"current environment matches any member of the {symbol} group" else: condition = f"current environment is {symbol}" detection = ( f"when {{func}}`~extra_platforms.{obj.detection_func_id}` returns `True`" ) if skip: return f"Skip test if {condition} ({detection})." return f"Run test only if {condition} ({detection}). Skip otherwise." def _generate_decorators() -> None: """Generate a pair of skip/unless decorators for each trait and group. Decorators are registered as module-level attributes, named after each object's ``skip_decorator_id`` and ``unless_decorator_id``. """ objects: tuple[Trait | Group, ...] = (*ALL_TRAITS, *ALL_GROUPS) for obj in objects: detect = _detection_registry[obj.detection_func_id] for skip, decorator_id, reason in ( (True, obj.skip_decorator_id, f"Skip {obj.name}"), (False, obj.unless_decorator_id, f"Requires {obj.name}"), ): decorator = pytest.mark.skipif( _DeferredCondition(detect, invert=not skip), # type: ignore[arg-type] # Use the name verbatim: down-casing its first letter would # mangle acronyms and brands (like CI, GitHub or iTerm2). reason=reason, ) # Set a custom docstring for Sphinx autodoc. decorator.__doc__ = _decorator_docstring(obj, skip=skip) globals()[decorator_id] = decorator _generate_decorators() # Explicitly declare all generated decorators after their generation, as mypy # doesn't understand dynamic type annotation. See the module docstring. These # annotations are checked and enforced in unittests. if TYPE_CHECKING: skip_aarch64: MarkDecorator skip_aix: MarkDecorator skip_alacritty: MarkDecorator skip_all_agents: MarkDecorator skip_all_architectures: MarkDecorator skip_all_arm: MarkDecorator skip_all_ci: MarkDecorator skip_all_mips: MarkDecorator skip_all_platforms: MarkDecorator skip_all_shells: MarkDecorator skip_all_sparc: MarkDecorator skip_all_terminals: MarkDecorator skip_all_traits: MarkDecorator skip_all_windows: MarkDecorator skip_almalinux: MarkDecorator skip_alpine: MarkDecorator skip_altlinux: MarkDecorator skip_amzn: MarkDecorator skip_android: MarkDecorator skip_apple_terminal: MarkDecorator skip_arch: MarkDecorator skip_arch_32_bit: MarkDecorator skip_arch_64_bit: MarkDecorator skip_arm: MarkDecorator skip_armv5tel: MarkDecorator skip_armv6l: MarkDecorator skip_armv7l: MarkDecorator skip_armv8l: MarkDecorator skip_artix: MarkDecorator skip_ash: MarkDecorator skip_azure_pipelines: MarkDecorator skip_bamboo: MarkDecorator skip_bash: MarkDecorator skip_big_endian: MarkDecorator skip_bourne_shells: MarkDecorator skip_bsd: MarkDecorator skip_bsd_not_macos: MarkDecorator skip_buildkite: MarkDecorator skip_buildroot: MarkDecorator skip_c_shells: MarkDecorator skip_cachyos: MarkDecorator skip_centos: MarkDecorator skip_chromeos: MarkDecorator skip_circle_ci: MarkDecorator skip_cirrus_ci: MarkDecorator skip_claude_code: MarkDecorator skip_clearlinux: MarkDecorator skip_cline: MarkDecorator skip_cloudlinux: MarkDecorator skip_cmd: MarkDecorator skip_codebuild: MarkDecorator skip_codex: MarkDecorator skip_contour: MarkDecorator skip_copilot_cli: MarkDecorator skip_crush: MarkDecorator skip_crux: MarkDecorator skip_csh: MarkDecorator skip_cursor: MarkDecorator skip_cygwin: MarkDecorator skip_dash: MarkDecorator skip_debian: MarkDecorator skip_dragonfly_bsd: MarkDecorator skip_endeavouros: MarkDecorator skip_exherbo: MarkDecorator skip_fedora: MarkDecorator skip_fish: MarkDecorator skip_foot: MarkDecorator skip_freebsd: MarkDecorator skip_gemini_cli: MarkDecorator skip_generic_linux: MarkDecorator skip_gentoo: MarkDecorator skip_ghostty: MarkDecorator skip_github_ci: MarkDecorator skip_gitlab_ci: MarkDecorator skip_gnome_terminal: MarkDecorator skip_gnu_screen: MarkDecorator skip_gpu_terminals: MarkDecorator skip_guix: MarkDecorator skip_haiku: MarkDecorator skip_hermetic_build: MarkDecorator skip_heroku_ci: MarkDecorator skip_hurd: MarkDecorator skip_hyper: MarkDecorator skip_i386: MarkDecorator skip_i586: MarkDecorator skip_i686: MarkDecorator skip_ibm_mainframe: MarkDecorator skip_ibm_powerkvm: MarkDecorator skip_illumos: MarkDecorator skip_iterm2: MarkDecorator skip_kali: MarkDecorator skip_kitty: MarkDecorator skip_konsole: MarkDecorator skip_ksh: MarkDecorator skip_kvmibm: MarkDecorator skip_linux: MarkDecorator skip_linux_layers: MarkDecorator skip_linux_like: MarkDecorator skip_linuxmint: MarkDecorator skip_little_endian: MarkDecorator skip_loongarch: MarkDecorator skip_loongarch64: MarkDecorator skip_macos: MarkDecorator skip_mageia: MarkDecorator skip_mandriva: MarkDecorator skip_manjaro: MarkDecorator skip_midnightbsd: MarkDecorator skip_mips: MarkDecorator skip_mips64: MarkDecorator skip_mips64el: MarkDecorator skip_mipsel: MarkDecorator skip_multiplexers: MarkDecorator skip_native_terminals: MarkDecorator skip_netbsd: MarkDecorator skip_nixos: MarkDecorator skip_nobara: MarkDecorator skip_nushell: MarkDecorator skip_nutyx: MarkDecorator skip_openbsd: MarkDecorator skip_opensuse: MarkDecorator skip_openwrt: MarkDecorator skip_oracle: MarkDecorator skip_os400: MarkDecorator skip_other_posix: MarkDecorator skip_other_shells: MarkDecorator skip_parallels: MarkDecorator skip_pi: MarkDecorator skip_pidora: MarkDecorator skip_pikaos: MarkDecorator skip_powerpc: MarkDecorator skip_powershell: MarkDecorator skip_ppc: MarkDecorator skip_ppc64: MarkDecorator skip_ppc64le: MarkDecorator skip_raspbian: MarkDecorator skip_rhel: MarkDecorator skip_rio: MarkDecorator skip_riscv: MarkDecorator skip_riscv32: MarkDecorator skip_riscv64: MarkDecorator skip_rocky: MarkDecorator skip_s390x: MarkDecorator skip_scientific: MarkDecorator skip_sh: MarkDecorator skip_slackware: MarkDecorator skip_sles: MarkDecorator skip_slitaz: MarkDecorator skip_solaris: MarkDecorator skip_solus: MarkDecorator skip_sourcemage: MarkDecorator skip_sparc: MarkDecorator skip_sparc64: MarkDecorator skip_sunos: MarkDecorator skip_system_v: MarkDecorator skip_tabby: MarkDecorator skip_tcsh: MarkDecorator skip_teamcity: MarkDecorator skip_tilix: MarkDecorator skip_tmux: MarkDecorator skip_travis_ci: MarkDecorator skip_tuxedo: MarkDecorator skip_ubuntu: MarkDecorator skip_ultramarine: MarkDecorator skip_unix: MarkDecorator skip_unix_layers: MarkDecorator skip_unix_not_macos: MarkDecorator skip_unknown: MarkDecorator skip_unknown_agent: MarkDecorator skip_unknown_architecture: MarkDecorator skip_unknown_ci: MarkDecorator skip_unknown_platform: MarkDecorator skip_unknown_shell: MarkDecorator skip_unknown_terminal: MarkDecorator skip_void: MarkDecorator skip_vscode_terminal: MarkDecorator skip_wasm32: MarkDecorator skip_wasm64: MarkDecorator skip_web_terminals: MarkDecorator skip_webassembly: MarkDecorator skip_webos: MarkDecorator skip_wezterm: MarkDecorator skip_windows: MarkDecorator skip_windows_shells: MarkDecorator skip_windows_terminal: MarkDecorator skip_wsl1: MarkDecorator skip_wsl2: MarkDecorator skip_x86: MarkDecorator skip_x86_64: MarkDecorator skip_xenserver: MarkDecorator skip_xonsh: MarkDecorator skip_xterm: MarkDecorator skip_zellij: MarkDecorator skip_zsh: MarkDecorator unless_aarch64: MarkDecorator unless_aix: MarkDecorator unless_alacritty: MarkDecorator unless_almalinux: MarkDecorator unless_alpine: MarkDecorator unless_altlinux: MarkDecorator unless_amzn: MarkDecorator unless_android: MarkDecorator unless_any_agent: MarkDecorator unless_any_architecture: MarkDecorator unless_any_arm: MarkDecorator unless_any_ci: MarkDecorator unless_any_mips: MarkDecorator unless_any_platform: MarkDecorator unless_any_shell: MarkDecorator unless_any_sparc: MarkDecorator unless_any_terminal: MarkDecorator unless_any_trait: MarkDecorator unless_any_windows: MarkDecorator unless_apple_terminal: MarkDecorator unless_arch: MarkDecorator unless_arch_32_bit: MarkDecorator unless_arch_64_bit: MarkDecorator unless_arm: MarkDecorator unless_armv5tel: MarkDecorator unless_armv6l: MarkDecorator unless_armv7l: MarkDecorator unless_armv8l: MarkDecorator unless_artix: MarkDecorator unless_ash: MarkDecorator unless_azure_pipelines: MarkDecorator unless_bamboo: MarkDecorator unless_bash: MarkDecorator unless_big_endian: MarkDecorator unless_bourne_shells: MarkDecorator unless_bsd: MarkDecorator unless_bsd_not_macos: MarkDecorator unless_buildkite: MarkDecorator unless_buildroot: MarkDecorator unless_c_shells: MarkDecorator unless_cachyos: MarkDecorator unless_centos: MarkDecorator unless_chromeos: MarkDecorator unless_circle_ci: MarkDecorator unless_cirrus_ci: MarkDecorator unless_claude_code: MarkDecorator unless_clearlinux: MarkDecorator unless_cline: MarkDecorator unless_cloudlinux: MarkDecorator unless_cmd: MarkDecorator unless_codebuild: MarkDecorator unless_codex: MarkDecorator unless_contour: MarkDecorator unless_copilot_cli: MarkDecorator unless_crush: MarkDecorator unless_crux: MarkDecorator unless_csh: MarkDecorator unless_cursor: MarkDecorator unless_cygwin: MarkDecorator unless_dash: MarkDecorator unless_debian: MarkDecorator unless_dragonfly_bsd: MarkDecorator unless_endeavouros: MarkDecorator unless_exherbo: MarkDecorator unless_fedora: MarkDecorator unless_fish: MarkDecorator unless_foot: MarkDecorator unless_freebsd: MarkDecorator unless_gemini_cli: MarkDecorator unless_generic_linux: MarkDecorator unless_gentoo: MarkDecorator unless_ghostty: MarkDecorator unless_github_ci: MarkDecorator unless_gitlab_ci: MarkDecorator unless_gnome_terminal: MarkDecorator unless_gnu_screen: MarkDecorator unless_gpu_terminals: MarkDecorator unless_guix: MarkDecorator unless_haiku: MarkDecorator unless_hermetic_build: MarkDecorator unless_heroku_ci: MarkDecorator unless_hurd: MarkDecorator unless_hyper: MarkDecorator unless_i386: MarkDecorator unless_i586: MarkDecorator unless_i686: MarkDecorator unless_ibm_mainframe: MarkDecorator unless_ibm_powerkvm: MarkDecorator unless_illumos: MarkDecorator unless_iterm2: MarkDecorator unless_kali: MarkDecorator unless_kitty: MarkDecorator unless_konsole: MarkDecorator unless_ksh: MarkDecorator unless_kvmibm: MarkDecorator unless_linux: MarkDecorator unless_linux_layers: MarkDecorator unless_linux_like: MarkDecorator unless_linuxmint: MarkDecorator unless_little_endian: MarkDecorator unless_loongarch: MarkDecorator unless_loongarch64: MarkDecorator unless_macos: MarkDecorator unless_mageia: MarkDecorator unless_mandriva: MarkDecorator unless_manjaro: MarkDecorator unless_midnightbsd: MarkDecorator unless_mips: MarkDecorator unless_mips64: MarkDecorator unless_mips64el: MarkDecorator unless_mipsel: MarkDecorator unless_multiplexers: MarkDecorator unless_native_terminals: MarkDecorator unless_netbsd: MarkDecorator unless_nixos: MarkDecorator unless_nobara: MarkDecorator unless_nushell: MarkDecorator unless_nutyx: MarkDecorator unless_openbsd: MarkDecorator unless_opensuse: MarkDecorator unless_openwrt: MarkDecorator unless_oracle: MarkDecorator unless_os400: MarkDecorator unless_other_posix: MarkDecorator unless_other_shells: MarkDecorator unless_parallels: MarkDecorator unless_pi: MarkDecorator unless_pidora: MarkDecorator unless_pikaos: MarkDecorator unless_powerpc: MarkDecorator unless_powershell: MarkDecorator unless_ppc: MarkDecorator unless_ppc64: MarkDecorator unless_ppc64le: MarkDecorator unless_raspbian: MarkDecorator unless_rhel: MarkDecorator unless_rio: MarkDecorator unless_riscv: MarkDecorator unless_riscv32: MarkDecorator unless_riscv64: MarkDecorator unless_rocky: MarkDecorator unless_s390x: MarkDecorator unless_scientific: MarkDecorator unless_sh: MarkDecorator unless_slackware: MarkDecorator unless_sles: MarkDecorator unless_slitaz: MarkDecorator unless_solaris: MarkDecorator unless_solus: MarkDecorator unless_sourcemage: MarkDecorator unless_sparc: MarkDecorator unless_sparc64: MarkDecorator unless_sunos: MarkDecorator unless_system_v: MarkDecorator unless_tabby: MarkDecorator unless_tcsh: MarkDecorator unless_teamcity: MarkDecorator unless_tilix: MarkDecorator unless_tmux: MarkDecorator unless_travis_ci: MarkDecorator unless_tuxedo: MarkDecorator unless_ubuntu: MarkDecorator unless_ultramarine: MarkDecorator unless_unix: MarkDecorator unless_unix_layers: MarkDecorator unless_unix_not_macos: MarkDecorator unless_unknown: MarkDecorator unless_unknown_agent: MarkDecorator unless_unknown_architecture: MarkDecorator unless_unknown_ci: MarkDecorator unless_unknown_platform: MarkDecorator unless_unknown_shell: MarkDecorator unless_unknown_terminal: MarkDecorator unless_void: MarkDecorator unless_vscode_terminal: MarkDecorator unless_wasm32: MarkDecorator unless_wasm64: MarkDecorator unless_web_terminals: MarkDecorator unless_webassembly: MarkDecorator unless_webos: MarkDecorator unless_wezterm: MarkDecorator unless_windows: MarkDecorator unless_windows_shells: MarkDecorator unless_windows_terminal: MarkDecorator unless_wsl1: MarkDecorator unless_wsl2: MarkDecorator unless_x86: MarkDecorator unless_x86_64: MarkDecorator unless_xenserver: MarkDecorator unless_xonsh: MarkDecorator unless_xterm: MarkDecorator unless_zellij: MarkDecorator unless_zsh: MarkDecorator