# 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