# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
from __future__ import annotations
import ast
from itertools import chain
from pathlib import Path
import pytest
import extra_platforms
from extra_platforms import ( # type: ignore[attr-defined]
ALL_GROUPS,
ALL_TRAITS,
UNKNOWN,
Group,
Trait,
current_traits,
is_any_architecture,
is_any_ci,
is_any_platform,
is_any_windows,
is_github_ci,
is_linux,
is_macos,
is_ubuntu,
is_unknown_architecture,
is_unknown_ci,
is_unknown_platform,
is_windows,
)
from extra_platforms.pytest import (
_DeferredCondition,
skip_all_architectures,
skip_all_ci,
skip_all_platforms,
skip_github_ci,
skip_linux,
skip_macos,
skip_ubuntu,
skip_unknown,
skip_unknown_architecture,
skip_unknown_ci,
skip_unknown_platform,
skip_windows,
unless_any_architecture,
unless_any_ci,
unless_any_platform,
unless_github_ci,
unless_linux,
unless_macos,
unless_ubuntu,
unless_unknown,
unless_unknown_architecture,
unless_unknown_ci,
unless_unknown_platform,
unless_windows,
)
def _all_decorator_ids() -> list[str]:
"""Generate the list of decorators IDs we expect to find."""
all_decorator_ids = []
for _obj in chain(ALL_TRAITS, ALL_GROUPS):
assert isinstance(_obj, (Trait, Group))
all_decorator_ids.extend([_obj.skip_decorator_id, _obj.unless_decorator_id])
return sorted(all_decorator_ids)
[docs]
def test_all_definition():
# Pick the actual list of decorators from the module.
collected_decorator_ids = [
name
for name in dir(extra_platforms.pytest)
if name.startswith(("skip_", "unless_"))
]
# Ensure we collected them all and they're naturally sorted.
assert collected_decorator_ids == _all_decorator_ids()
[docs]
def test_type_annotations():
"""Check all @skip_*/@unless_* annotations are defined and sorted."""
pytest_file = Path(__file__).parent.parent / "extra_platforms" / "pytest.py"
tree = ast.parse(pytest_file.read_text(encoding="utf-8"))
# Collect all annotated assignments in the TYPE_CHECKING block.
decorator_annotations = []
for node in ast.walk(tree):
if (
isinstance(node, ast.If)
and isinstance(node.test, ast.Name)
and node.test.id == "TYPE_CHECKING"
):
for line in node.body:
if (
isinstance(line, ast.AnnAssign)
and isinstance(line.target, ast.Name)
and line.target.id.startswith(("skip_", "unless_"))
):
decorator_annotations.append(line.target.id)
assert len(decorator_annotations), "No @skip_*/@unless_* annotations found."
assert decorator_annotations == sorted(decorator_annotations), (
"@skip_*/@unless_* annotations not sorted alphabetically."
)
expected_annotations = _all_decorator_ids()
assert decorator_annotations == expected_annotations, (
f"@skip_*/@unless_* annotations don't match expectations:\n"
f"- Missing: {set(expected_annotations) - set(decorator_annotations)}\n"
f"- Extra: {set(decorator_annotations) - set(expected_annotations)}"
)
[docs]
@skip_unknown
def test_skip_unknown():
assert not current_traits().intersection(UNKNOWN)
[docs]
@unless_unknown
def test_unless_unknown():
assert current_traits().intersection(UNKNOWN)
[docs]
@skip_all_architectures
def test_skip_all_architectures():
assert is_unknown_architecture()
assert False, (
"This test should always be skipped as we expect to always detect "
"an architecture."
)
[docs]
@unless_any_architecture
def test_unless_any_architecture():
assert not is_unknown_architecture()
assert True, (
"This test should always be run as we expect to always detect an architecture."
)
[docs]
@skip_all_ci
def test_skip_all_ci():
assert is_unknown_ci()
[docs]
@unless_any_ci
def test_unless_any_ci():
assert not is_unknown_ci()
[docs]
@skip_unknown_architecture
def test_skip_unknown_architecture():
assert is_any_architecture()
assert not is_unknown_architecture()
assert is_any_platform() or is_unknown_platform()
assert is_any_ci() or is_unknown_ci()
[docs]
@unless_unknown_architecture
def test_unless_unknown_architecture():
assert not is_any_architecture()
assert is_unknown_architecture()
assert is_any_platform() or is_unknown_platform()
assert is_any_ci() or is_unknown_ci()
[docs]
@skip_unknown_ci
def test_skip_unknown_ci():
assert is_any_architecture() or is_unknown_architecture()
assert is_any_platform() or is_unknown_platform()
assert is_any_ci()
assert not is_unknown_ci()
[docs]
@unless_unknown_ci
def test_unless_unknown_ci():
assert is_any_architecture() or is_unknown_architecture()
assert is_any_platform() or is_unknown_platform()
assert not is_any_ci()
assert is_unknown_ci()
[docs]
@skip_linux
def test_skip_linux():
assert is_any_platform()
assert not is_linux()
assert not is_ubuntu()
assert is_any_windows() or is_macos() or is_windows()
[docs]
@unless_linux
def test_unless_linux():
assert is_any_platform()
assert not is_any_windows()
assert is_linux()
assert not is_macos()
# assert is_ubuntu()
assert not is_windows()
[docs]
@skip_macos
def test_skip_macos():
assert is_any_platform()
assert not is_macos()
assert is_any_windows() or is_linux() or is_ubuntu() or is_windows()
[docs]
@unless_macos
def test_unless_macos():
assert is_any_platform()
assert not is_any_windows()
assert not is_linux()
assert is_macos()
assert not is_ubuntu()
assert not is_windows()
[docs]
@skip_ubuntu
def test_skip_ubuntu():
assert is_any_platform()
assert not is_ubuntu()
assert is_any_windows() or is_linux() or is_macos() or is_windows()
[docs]
@unless_ubuntu
def test_unless_ubuntu():
assert is_any_platform()
assert not is_any_windows()
assert is_linux()
assert not is_macos()
assert is_ubuntu()
assert not is_windows()
[docs]
@skip_windows
def test_skip_windows():
assert is_any_platform()
assert not is_windows()
assert not is_any_windows()
assert is_linux() or is_macos() or is_ubuntu()
[docs]
@unless_windows
def test_unless_windows():
assert is_any_platform()
# assert is_any_windows()
assert not is_linux()
assert not is_macos()
assert not is_ubuntu()
assert is_windows()
[docs]
@skip_github_ci
def test_skip_github_ci():
assert is_any_ci() or is_unknown_ci()
assert not is_github_ci()
[docs]
@unless_github_ci
def test_unless_github_ci():
assert is_any_ci()
assert not is_unknown_ci()
assert is_github_ci()
[docs]
def test_deferred_condition_defers_evaluation():
"""Test that _DeferredCondition defers evaluation until needed."""
# Track whether the condition was called.
called = []
def condition():
called.append(True)
return True
# Creating the _DeferredCondition should NOT call the condition.
deferred = _DeferredCondition(condition)
assert len(called) == 0
# Only when we evaluate it as a bool should it call the condition.
result = bool(deferred)
assert len(called) == 1
assert result is True
[docs]
def test_deferred_condition_bool():
"""Test that _DeferredCondition.__bool__() calls the condition."""
def true_condition():
return True
def false_condition():
return False
assert bool(_DeferredCondition(true_condition)) is True
assert bool(_DeferredCondition(false_condition)) is False
[docs]
def test_deferred_condition_call():
"""Test that _DeferredCondition.__call__() works."""
def true_condition():
return True
deferred = _DeferredCondition(true_condition)
result = deferred()
assert result is True
[docs]
def test_deferred_condition_invert_false():
"""Test that _DeferredCondition with invert=False returns the condition result."""
def true_condition():
return True
def false_condition():
return False
assert bool(_DeferredCondition(true_condition, invert=False)) is True
assert bool(_DeferredCondition(false_condition, invert=False)) is False
[docs]
def test_deferred_condition_invert_true():
"""Test that _DeferredCondition with invert=True inverts the condition result."""
from extra_platforms.pytest import _DeferredCondition
def true_condition():
return True
def false_condition():
return False
assert bool(_DeferredCondition(true_condition, invert=True)) is False
assert bool(_DeferredCondition(false_condition, invert=True)) is True
[docs]
def test_deferred_condition_with_pytest_skipif():
"""Test that _DeferredCondition works with pytest.mark.skipif."""
# Create a condition that always returns True.
def always_skip():
return True
condition = _DeferredCondition(always_skip)
# Create a skipif mark.
mark = pytest.mark.skipif(condition, reason="Testing deferred condition")
# The mark should have the condition.
assert mark is not None
# We can't easily test if the skip actually happens without running pytest,
# but we can verify the condition evaluates correctly.
assert bool(condition) is True