Source code for tests.test_root

# 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
import inspect
import sys
from pathlib import Path

import requests

import extra_platforms
from extra_platforms import (
    ALL_ARCHITECTURES,
    ALL_GROUPS,
    ALL_PLATFORMS,
    ALL_TRAITS,
    GITHUB_CI,
    SYSTEM_V,
    UNIX,
    UNKNOWN_ARCHITECTURE,
    UNKNOWN_CI,
    UNKNOWN_LINUX,
    WSL1,
    WSL2,
    current_architecture,
    current_platform,
    current_traits,
    invalidate_caches,
    is_github_ci,
    is_macos,
    is_ubuntu,
    is_windows,
)
from extra_platforms import architecture as architecture_module
from extra_platforms import architecture_data as architecture_data_module
from extra_platforms import ci as ci_module
from extra_platforms import ci_data as ci_data_module
from extra_platforms import detection as detection_module
from extra_platforms import group as group_module
from extra_platforms import group_data as group_data_module
from extra_platforms import operations as operations_module
from extra_platforms import platform as platform_module
from extra_platforms import platform_data as platform_data_module
from extra_platforms import trait as trait_module

from .test_detection import github_runner_os

if sys.version_info >= (3, 11):
    import tomllib
else:
    import tomli as tomllib  # type: ignore[import-not-found]


PROJECT_ROOT = Path(__file__).parent.parent
"""The root path of the project."""

PYPROJECT_PATH = PROJECT_ROOT / "pyproject.toml"
"""The path to the ``pyproject.toml`` file."""

PYPROJECT = tomllib.loads(PYPROJECT_PATH.read_text(encoding="utf-8"))
"""The parsed content of the ``pyproject.toml`` file."""


[docs] def test_pyproject_keywords(): """Check that keywords in ``pyproject.toml`` are correct.""" # Add all platforms and architectures. ideal_keywords = [ p.name for p in ( ALL_TRAITS # Remove generic unknown platforms. - UNKNOWN_ARCHITECTURE - UNKNOWN_LINUX - UNKNOWN_CI # Remove versioned WSL platforms. - WSL1 - WSL2 ) ] # Re-add un-versioned platform names. ideal_keywords.append("Windows Subsystem for Linux") # Manually add group names that are not platforms per se. ideal_keywords.extend(( UNIX.name.lstrip("Any "), SYSTEM_V.name, )) # Manually add extra keywords. ideal_keywords.extend(( "multiplatform", "Pytest", "OS detection", "Platform detection", "Architecture detection", )) # Sort and deduplicate keywords (case-insensitive). ideal_keywords = sorted(set(ideal_keywords), key=lambda k: k.lower()) # Load our keywords from pyproject.toml. keywords = PYPROJECT["project"]["keywords"] assert keywords == ideal_keywords
[docs] def test_pypoject_classifiers(): """Check that Trove classifiers in ``pyproject.toml`` are correct.""" # Fetch official trove classifiers from PyPI. response = requests.get("https://pypi.org/pypi?%3Aaction=list_classifiers") assert response.ok, f"{response.url} is not reachable: {response}" official_classifiers = response.text.splitlines() # Load our trove classifiers from pyproject.toml. classifiers = PYPROJECT["project"]["classifiers"] for classifier in classifiers: assert classifier in official_classifiers, ( f"Classifier '{classifier}' is not an official trove classifier." ) assert len(classifiers) == len(set(classifiers)), ( "Classifiers must not contain duplicates." ) sorted_classifiers = [c for c in official_classifiers if c in classifiers] assert sorted_classifiers == classifiers, ( "Classifiers must be sorted in the same order as official trove classifiers." ) os_classifiers = [ c for c in official_classifiers if c.startswith("Operating System :: ") ] assert set(c for c in classifiers if c.startswith("Operating System :: ")) == set( os_classifiers ), "All Operating System classifiers must be present in our metadata."
[docs] def test_module_root_declarations(): def fetch_module_implements(module) -> set[str]: """Fetch all methods, classes and constants implemented locally in a module's file.""" members = set() tree = ast.parse(Path(inspect.getfile(module)).read_bytes()) for node in tree.body: if isinstance(node, ast.Assign): for target in node.targets: # Skip TYPE_CHECKING variable. See _types.py for more details. if target.id == "TYPE_CHECKING": # type: ignore[attr-defined] continue members.add(target.id) # type: ignore[attr-defined] elif isinstance(node, ast.AnnAssign): members.add(node.target.id) # type: ignore[union-attr] elif isinstance(node, ast.FunctionDef): members.add(node.name) elif isinstance(node, ast.ClassDef): members.add(node.name) return {m for m in members if not m.startswith("_")} detection_members = fetch_module_implements(detection_module) architecture_members = fetch_module_implements(architecture_module) architecture_data_members = fetch_module_implements(architecture_data_module) ci_members = fetch_module_implements(ci_module) ci_data_members = fetch_module_implements(ci_data_module) group_members = fetch_module_implements(group_module) group_data_members = fetch_module_implements(group_data_module) platform_members = fetch_module_implements(platform_module) platform_data_members = fetch_module_implements(platform_data_module) operations_members = fetch_module_implements(operations_module) trait_members = fetch_module_implements(trait_module) root_members = fetch_module_implements(extra_platforms) # Update root members with auto-generated ``is_<group.id>`` variables. root_members.update((f"is_{g.id}" for g in ALL_GROUPS)) # Check all members are exposed at the module root. tree = ast.parse(Path(inspect.getfile(extra_platforms)).read_bytes()) extra_platforms_members = [] for node in tree.body: if isinstance(node, ast.Assign): for target in node.targets: if target.id == "__all__": for element in node.value.elts: extra_platforms_members.append(element.value) assert detection_members <= set(extra_platforms_members) assert architecture_members <= set(extra_platforms_members) assert architecture_data_members <= set(extra_platforms_members) assert ci_members <= set(extra_platforms_members) assert ci_data_members <= set(extra_platforms_members) assert group_members <= set(extra_platforms_members) assert group_data_members <= set(extra_platforms_members) assert platform_members <= set(extra_platforms_members) assert platform_data_members <= set(extra_platforms_members) assert operations_members <= set(extra_platforms_members) assert trait_members <= set(extra_platforms_members) expected_members = sorted( detection_members.union(group_members) .union(architecture_members) .union(architecture_data_members) .union(ci_members) .union(ci_data_members) .union(group_data_members) .union(platform_members) .union(platform_data_members) .union(operations_members) .union(trait_members) .union(root_members), key=lambda m: (m.lower(), m), ) assert expected_members == extra_platforms_members
[docs] def test_current_funcs(): current_traits_results = current_traits() assert ALL_TRAITS.issuperset(current_traits_results) if is_github_ci(): assert GITHUB_CI in current_traits_results if github_runner_os() == "ubuntu-slim": # 1 platform + 1 architecture + 1 CI = 3 traits, plus possible WSL. assert len(current_traits_results) >= 3 else: # 1 platform + 1 architecture + 1 CI = 3 traits. assert len(current_traits_results) >= 2 else: # 1 platform + 1 architecture = 2 traits. assert len(current_traits_results) == 2 current_platform_result = current_platform() assert current_platform_result in ALL_PLATFORMS assert current_platform_result in current_traits_results current_architecture_result = current_architecture() assert current_architecture_result in ALL_ARCHITECTURES
[docs] def test_group_membership_funcs(): for group in ALL_GROUPS: func_id = f"is_{group.id}" assert func_id in extra_platforms.__dict__ func = extra_platforms.__dict__[func_id] assert getattr(extra_platforms, func_id) is func assert isinstance(func(), bool) assert func() == any(t in group for t in current_traits()) assert group.name.lower() in func.__doc__.lower()
[docs] def test_invalidate_caches(): """Test that invalidate_caches() properly clears all caches.""" # Call detection functions to populate caches. _ = is_ubuntu() _ = is_macos() # Call global functions. _ = current_traits() _ = current_platform() # Call group membership functions. _ = is_windows() # Verify caches are populated. assert hasattr(is_ubuntu, "__wrapped__") assert hasattr(is_macos, "__wrapped__") assert hasattr(current_traits, "__wrapped__") assert hasattr(current_platform, "__wrapped__") assert hasattr(is_windows, "__wrapped__") # Access Platform.current to populate their caches. for platform_obj in ALL_PLATFORMS: _ = platform_obj.current # For cached_property, the value is stored in the instance's __dict__. assert "current" in vars(platform_obj), ( f"'current' not cached for {platform_obj.id}" ) # Check that caches have hits (values are cached). assert is_ubuntu.cache_info().hits > 0 or is_ubuntu.cache_info().currsize > 0 assert is_macos.cache_info().hits > 0 or is_macos.cache_info().currsize > 0 assert ( current_traits.cache_info().hits > 0 or current_traits.cache_info().currsize > 0 ) assert ( current_platform.cache_info().hits > 0 or current_platform.cache_info().currsize > 0 ) assert is_windows.cache_info().hits > 0 or is_windows.cache_info().currsize > 0 # Invalidate all caches. invalidate_caches() # Verify caches were cleared (currsize should be 0). assert is_ubuntu.cache_info().currsize == 0 assert is_macos.cache_info().currsize == 0 assert current_traits.cache_info().currsize == 0 assert current_platform.cache_info().currsize == 0 assert is_windows.cache_info().currsize == 0 for platform_obj in ALL_PLATFORMS: assert "current" not in vars(platform_obj), ( f"'current' cache not cleared for {platform_obj.id}" )