# 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
from random import choice
from typing import Iterable
import pytest
from extra_platforms import (
AIX,
ALL_CI,
ALL_GROUP_IDS,
ALL_GROUPS,
ALL_IDS,
ALL_PLATFORMS,
ALL_TRAIT_IDS,
ALL_TRAITS,
ALL_WINDOWS,
ALTLINUX,
AMZN,
ANDROID,
ARCH,
AZURE_PIPELINES,
BAMBOO,
BSD,
BSD_WITHOUT_MACOS,
BUILDKITE,
BUILDROOT,
CACHYOS,
CENTOS,
CIRCLE_CI,
CIRRUS_CI,
CLOUDLINUX,
CODEBUILD,
CYGWIN,
DEBIAN,
DRAGONFLY_BSD,
EXHERBO,
FEDORA,
FREEBSD,
GENTOO,
GITHUB_CI,
GITLAB_CI,
GUIX,
HAIKU,
HEROKU_CI,
HURD,
IBM_POWERKVM,
ILLUMOS,
KVMIBM,
LINUX,
LINUX_LAYERS,
LINUX_LIKE,
LINUXMINT,
MACOS,
MAGEIA,
MANDRIVA,
MIDNIGHTBSD,
NETBSD,
NOBARA,
NON_OVERLAPPING_GROUPS,
OPENBSD,
OPENSUSE,
ORACLE,
PARALLELS,
PIDORA,
RASPBIAN,
RHEL,
ROCKY,
SCIENTIFIC,
SLACKWARE,
SLES,
SOLARIS,
SUNOS,
TEAMCITY,
TRAVIS_CI,
TUMBLEWEED,
TUXEDO,
UBUNTU,
ULTRAMARINE,
UNIX,
UNIX_LAYERS,
UNIX_WITHOUT_MACOS,
UNKNOWN,
WINDOWS,
WSL1,
WSL2,
XENSERVER,
Group,
Platform,
Trait,
groups_from_ids,
reduce,
traits_from_ids,
)
[docs]
def test_deduplication():
my_group = Group("my_group", "My Group", "β
", (AIX, AIX))
assert len(my_group) == 1
assert len(my_group.members) == 1
assert len(my_group.member_ids) == 1
assert tuple(my_group) == (AIX,)
assert my_group.member_ids == frozenset({"aix"})
[docs]
def test_membership():
my_group = Group("my_group", "My Group", "β
", (AIX, PIDORA))
# Test iteration.
assert list(my_group) == [AIX, PIDORA]
assert tuple(my_group) == (AIX, PIDORA)
assert set(my_group) == {AIX, PIDORA}
# Test platform membership.
assert AIX in my_group
assert PIDORA in my_group
assert RHEL not in my_group
# Test platform ID membership.
assert "aix" in my_group
assert "pidora" in my_group
assert "rhel" not in my_group
# Test platform ID getter.
assert my_group["aix"] == AIX
assert my_group["pidora"] == PIDORA
with pytest.raises(KeyError):
my_group["rhel"]
# Test platform items getter.
assert list(my_group.items()) == [("aix", AIX), ("pidora", PIDORA)]
[docs]
def test_canonical_property():
# A random group is not canonical by default.
my_group = Group("my_group", "My Group", "β
", (AIX, PIDORA))
assert not my_group.canonical
# An empty group is not canonical.
empty_group = Group("empty", "Empty Group")
assert not empty_group.canonical
# A copied canonical group stays canonical.
assert ALL_WINDOWS.canonical
copied_windows = ALL_WINDOWS.copy()
assert copied_windows.canonical
# A modified canonical group is not canonical.
assert LINUX.canonical
modified_linux = LINUX.add(AIX)
assert not modified_linux.canonical
[docs]
def test_simple_union():
new_group = ALL_WINDOWS.union(LINUX_LAYERS)
assert ALL_WINDOWS.issubset(new_group)
assert LINUX_LAYERS.issubset(new_group)
assert new_group.issuperset(ALL_WINDOWS)
assert new_group.issuperset(LINUX_LAYERS)
assert new_group.id == ALL_WINDOWS.id
assert new_group.id != LINUX_LAYERS.id
assert new_group.name == ALL_WINDOWS.name
assert new_group.name != LINUX_LAYERS.name
assert new_group.icon == ALL_WINDOWS.icon
assert new_group.icon != LINUX_LAYERS.icon
assert set(new_group.members) != set(ALL_WINDOWS.members)
assert set(new_group.member_ids) != set(ALL_WINDOWS.member_ids)
assert set(new_group.members) != set(LINUX_LAYERS.members)
assert set(new_group.member_ids) != set(LINUX_LAYERS.member_ids)
assert set(new_group.members) == set(ALL_WINDOWS.members).union(
LINUX_LAYERS.members
)
assert set(new_group.member_ids) == set(ALL_WINDOWS.member_ids).union(
LINUX_LAYERS.member_ids
)
[docs]
def test_multiple_union():
new_group = ALL_WINDOWS.union(LINUX_LAYERS, UNIX_LAYERS)
assert new_group.member_ids == frozenset(("windows", "wsl1", "wsl2", "cygwin"))
assert ALL_WINDOWS.issubset(new_group)
assert LINUX_LAYERS.issubset(new_group)
assert UNIX_LAYERS.issubset(new_group)
assert new_group.issuperset(ALL_WINDOWS)
assert new_group.issuperset(LINUX_LAYERS)
assert new_group.issuperset(UNIX_LAYERS)
assert new_group.id == ALL_WINDOWS.id
assert new_group.id != LINUX_LAYERS.id
assert new_group.id != UNIX_LAYERS.id
assert new_group.name == ALL_WINDOWS.name
assert new_group.name != LINUX_LAYERS.name
assert new_group.name != UNIX_LAYERS.name
assert new_group.icon == ALL_WINDOWS.icon
assert new_group.icon != LINUX_LAYERS.icon
assert new_group.icon != UNIX_LAYERS.icon
assert set(new_group.members) != set(ALL_WINDOWS.members)
assert set(new_group.member_ids) != set(ALL_WINDOWS.member_ids)
assert set(new_group.members) != set(LINUX_LAYERS.members)
assert set(new_group.member_ids) != set(LINUX_LAYERS.member_ids)
assert set(new_group.members) != set(UNIX_LAYERS.members)
assert set(new_group.member_ids) != set(UNIX_LAYERS.member_ids)
assert set(new_group.members) == set(ALL_WINDOWS.members).union(
LINUX_LAYERS.members
).union(UNIX_LAYERS.members)
assert set(new_group.member_ids) == set(ALL_WINDOWS.member_ids).union(
LINUX_LAYERS.member_ids
).union(UNIX_LAYERS.member_ids)
[docs]
def test_single_intersection():
new_group = ALL_PLATFORMS.intersection(ALL_WINDOWS)
assert new_group.member_ids == frozenset(("windows",))
assert ALL_WINDOWS.issubset(new_group)
assert not ALL_PLATFORMS.issubset(new_group)
assert new_group.issuperset(ALL_WINDOWS)
assert not new_group.issuperset(ALL_PLATFORMS)
assert new_group.id == ALL_PLATFORMS.id
assert new_group.id != ALL_WINDOWS.id
assert new_group.name == ALL_PLATFORMS.name
assert new_group.name != ALL_WINDOWS.name
assert new_group.icon == ALL_PLATFORMS.icon
assert new_group.icon != ALL_WINDOWS.icon
assert set(new_group.members) != set(ALL_PLATFORMS.members)
assert set(new_group.member_ids) != set(ALL_PLATFORMS.member_ids)
assert set(new_group.members) == set(ALL_WINDOWS.members)
assert set(new_group.member_ids) == set(ALL_WINDOWS.member_ids)
[docs]
def test_multiple_intersection():
new_group = ALL_PLATFORMS.intersection(UNIX_WITHOUT_MACOS, BSD_WITHOUT_MACOS)
assert new_group.member_ids == frozenset((
"dragonfly_bsd",
"freebsd",
"midnightbsd",
"netbsd",
"openbsd",
"sunos",
))
assert new_group.issubset(ALL_PLATFORMS)
assert new_group.issubset(UNIX_WITHOUT_MACOS)
assert new_group.issubset(BSD_WITHOUT_MACOS)
assert ALL_PLATFORMS.issuperset(new_group)
assert UNIX_WITHOUT_MACOS.issuperset(new_group)
assert BSD_WITHOUT_MACOS.issuperset(new_group)
assert new_group.id == ALL_PLATFORMS.id
assert new_group.id != UNIX_WITHOUT_MACOS.id
assert new_group.id != BSD_WITHOUT_MACOS.id
assert new_group.name == ALL_PLATFORMS.name
assert new_group.name != UNIX_WITHOUT_MACOS.name
assert new_group.name != BSD_WITHOUT_MACOS.name
assert new_group.icon == ALL_PLATFORMS.icon
assert new_group.icon != UNIX_WITHOUT_MACOS.icon
assert new_group.icon != BSD_WITHOUT_MACOS.icon
assert set(new_group.members) != set(ALL_PLATFORMS.members)
assert set(new_group.member_ids) != set(ALL_PLATFORMS.member_ids)
assert set(new_group.members) != set(UNIX_WITHOUT_MACOS.members)
assert set(new_group.member_ids) != set(UNIX_WITHOUT_MACOS.member_ids)
assert set(new_group.members) == set(BSD_WITHOUT_MACOS.members)
assert set(new_group.member_ids) == set(BSD_WITHOUT_MACOS.member_ids)
[docs]
def test_single_difference():
new_group = BSD.difference(BSD_WITHOUT_MACOS)
assert new_group.member_ids == frozenset(("macos",))
assert not BSD.issubset(new_group)
assert BSD.issuperset(new_group)
assert not BSD_WITHOUT_MACOS.issubset(new_group)
assert not BSD_WITHOUT_MACOS.issuperset(new_group)
assert not new_group.issuperset(BSD)
assert not new_group.issuperset(BSD_WITHOUT_MACOS)
assert new_group.issubset(BSD)
assert not new_group.issubset(BSD_WITHOUT_MACOS)
assert new_group.id == BSD.id
assert new_group.id != BSD_WITHOUT_MACOS.id
assert new_group.name == BSD.name
assert new_group.name != BSD_WITHOUT_MACOS.name
assert new_group.icon == BSD.icon
assert new_group.icon != BSD_WITHOUT_MACOS.icon
assert set(new_group.members) != set(BSD.members)
assert set(new_group.member_ids) != set(BSD.member_ids)
assert set(new_group.members) != set(BSD_WITHOUT_MACOS.members)
assert set(new_group.member_ids) != set(BSD_WITHOUT_MACOS.member_ids)
assert set(new_group.members) == set(BSD.members).difference(
BSD_WITHOUT_MACOS.members
)
assert set(new_group.member_ids) == set(BSD.member_ids).difference(
BSD_WITHOUT_MACOS.member_ids
)
[docs]
def test_multiple_difference():
new_group = ALL_PLATFORMS.difference(LINUX, UNIX, ALL_CI)
assert new_group.member_ids == frozenset(("windows",))
assert not ALL_PLATFORMS.issubset(new_group)
assert ALL_PLATFORMS.issuperset(new_group)
assert not LINUX.issubset(new_group)
assert not LINUX.issuperset(new_group)
assert not UNIX.issubset(new_group)
assert not UNIX.issuperset(new_group)
assert not new_group.issuperset(ALL_PLATFORMS)
assert not new_group.issuperset(LINUX)
assert not new_group.issuperset(UNIX)
assert new_group.issubset(ALL_PLATFORMS)
assert not new_group.issubset(LINUX)
assert not new_group.issubset(UNIX)
assert new_group.id == ALL_PLATFORMS.id
assert new_group.id != LINUX.id
assert new_group.id != UNIX.id
assert new_group.name == ALL_PLATFORMS.name
assert new_group.name != LINUX.name
assert new_group.name != UNIX.name
assert new_group.icon == ALL_PLATFORMS.icon
assert new_group.icon != LINUX.icon
assert new_group.icon != UNIX.icon
assert set(new_group.members) != set(ALL_PLATFORMS.members)
assert set(new_group.member_ids) != set(ALL_PLATFORMS.member_ids)
assert set(new_group.members) != set(LINUX.members)
assert set(new_group.member_ids) != set(LINUX.member_ids)
assert set(new_group.members) != set(UNIX.members)
assert set(new_group.member_ids) != set(UNIX.member_ids)
assert set(new_group.members) == set(ALL_PLATFORMS.members).difference(
LINUX.members
).difference(UNIX.members).difference(ALL_CI.members)
assert set(new_group.member_ids) == set(ALL_PLATFORMS.member_ids).difference(
LINUX.member_ids
).difference(UNIX.member_ids).difference(ALL_CI.member_ids)
[docs]
def test_symmetric_difference():
win_and_bsd = ALL_WINDOWS.union(BSD_WITHOUT_MACOS)
new_group = win_and_bsd.symmetric_difference(BSD)
assert new_group.member_ids == frozenset((("macos", "windows")))
assert not win_and_bsd.issubset(new_group)
assert not win_and_bsd.issuperset(new_group)
assert not BSD.issubset(new_group)
assert not BSD.issuperset(new_group)
assert not new_group.issuperset(win_and_bsd)
assert not new_group.issuperset(BSD)
assert not new_group.issubset(win_and_bsd)
assert not new_group.issubset(BSD)
assert new_group.id == win_and_bsd.id
assert new_group.id != BSD.id
assert new_group.name == win_and_bsd.name
assert new_group.name != BSD.name
assert new_group.icon == win_and_bsd.icon
assert new_group.icon != BSD.icon
assert set(new_group.members) != set(BSD.members)
assert set(new_group.member_ids) != set(BSD.member_ids)
assert set(new_group.members) != set(win_and_bsd.members)
assert set(new_group.member_ids) != set(win_and_bsd.member_ids)
assert set(new_group.members) == set(win_and_bsd.members).symmetric_difference(
BSD.members
)
assert set(new_group.member_ids) == set(
win_and_bsd.member_ids
).symmetric_difference(BSD.member_ids)
[docs]
def test_copy():
my_group = Group("my_group", "My Group", "β
", (AIX, AIX))
my_group_copy1 = my_group.copy()
assert my_group == my_group_copy1
assert my_group is not my_group_copy1
my_group_copy2 = my_group.copy(
id="my_group_copy2", name="My Group Copy 2", icon="π"
)
assert my_group_copy2 != my_group
assert my_group_copy2 is not my_group
assert my_group_copy2 != my_group_copy1
assert my_group_copy2 is not my_group_copy1
assert my_group_copy2.id == "my_group_copy2"
assert my_group_copy2.name == "My Group Copy 2"
assert my_group_copy2.icon == "π"
assert my_group_copy2.members == my_group.members
assert my_group_copy2.member_ids == my_group.member_ids
assert my_group_copy2.members == my_group_copy1.members
assert my_group_copy2.member_ids == my_group_copy1.member_ids
[docs]
def test_bool():
"""Test __bool__() method for truth value testing."""
# Empty group should be falsy.
empty_group = Group("empty", "Empty Group", "β", tuple())
assert not empty_group
assert bool(empty_group) is False
# Non-empty group should be truthy.
non_empty_group = Group("non_empty", "Non-Empty Group", "β
", (AIX, PIDORA))
assert non_empty_group
assert bool(non_empty_group) is True
# Group with one member should be truthy.
single_member = Group("single", "Single Member", "β
", (AIX,))
assert single_member
assert bool(single_member) is True
[docs]
def test_add():
"""Test add() method for adding a single trait."""
my_group = Group("my_group", "My Group", "β
", (AIX,))
# Add a new trait.
new_group = my_group.add(PIDORA)
assert PIDORA in new_group
assert AIX in new_group
assert len(new_group) == 2
assert new_group.member_ids == frozenset({"aix", "pidora"})
# Original group should be unchanged.
assert len(my_group) == 1
assert PIDORA not in my_group
# Add a trait that already exists (should return unchanged copy).
same_group = new_group.add(AIX)
assert len(same_group) == 2
assert same_group.member_ids == new_group.member_ids
# Add by trait ID string.
with_rhel = my_group.add("rhel")
assert RHEL in with_rhel
assert "rhel" in with_rhel
assert len(with_rhel) == 2
# Add invalid trait ID should raise ValueError.
with pytest.raises(ValueError, match="Unrecognized group or trait IDs"):
my_group.add("invalid_trait_id")
[docs]
def test_remove():
"""Test remove() method for removing a trait with error checking."""
my_group = Group("my_group", "My Group", "β
", (AIX, PIDORA, RHEL))
# Remove a trait by object.
new_group = my_group.remove(PIDORA)
assert PIDORA not in new_group
assert AIX in new_group
assert RHEL in new_group
assert len(new_group) == 2
assert new_group.member_ids == frozenset({"aix", "rhel"})
# Original group should be unchanged.
assert len(my_group) == 3
assert PIDORA in my_group
# Remove by trait ID string.
without_aix = my_group.remove("aix")
assert AIX not in without_aix
assert "aix" not in without_aix
assert len(without_aix) == 2
# Remove non-existent trait should raise KeyError.
with pytest.raises(KeyError, match="Trait 'invalid' is not in the group"):
my_group.remove("invalid")
with pytest.raises(KeyError, match="is not in the group"):
new_group.remove(PIDORA)
[docs]
def test_discard():
"""Test discard() method for removing a trait without error."""
my_group = Group("my_group", "My Group", "β
", (AIX, PIDORA, RHEL))
# Discard a trait by object.
new_group = my_group.discard(PIDORA)
assert PIDORA not in new_group
assert AIX in new_group
assert RHEL in new_group
assert len(new_group) == 2
# Original group should be unchanged.
assert len(my_group) == 3
assert PIDORA in my_group
# Discard by trait ID string.
without_aix = my_group.discard("aix")
assert AIX not in without_aix
assert len(without_aix) == 2
# Discard non-existent trait should NOT raise error.
same_group = my_group.discard("invalid")
assert len(same_group) == 3
assert same_group.member_ids == my_group.member_ids
# Discard already removed trait should NOT raise error.
still_same = new_group.discard(PIDORA)
assert len(still_same) == 2
[docs]
def test_pop():
"""Test pop() method for removing and returning a trait."""
my_group = Group("my_group", "My Group", "β
", (AIX, PIDORA, RHEL))
# Pop a specific trait by ID.
popped_trait, new_group = my_group.pop("pidora")
assert popped_trait == PIDORA
assert PIDORA not in new_group
assert AIX in new_group
assert RHEL in new_group
assert len(new_group) == 2
# Original group should be unchanged.
assert len(my_group) == 3
assert PIDORA in my_group
# Pop arbitrary trait (first in iteration order).
trait, smaller_group = new_group.pop()
assert trait in {AIX, RHEL}
assert trait not in smaller_group
assert len(smaller_group) == 1
# Pop from empty group should raise KeyError.
empty_group = Group("empty", "Empty", "β", tuple())
with pytest.raises(KeyError, match="pop from an empty group"):
empty_group.pop()
# Pop non-existent trait ID should raise KeyError.
with pytest.raises(KeyError, match="Trait 'invalid' is not in the group"):
my_group.pop("invalid")
[docs]
def test_clear():
"""Test clear() method for emptying a group."""
my_group = Group("my_group", "My Group", "β
", (AIX, PIDORA, RHEL))
# Clear the group.
empty_group = my_group.clear()
assert len(empty_group) == 0
assert not empty_group
assert empty_group.member_ids == frozenset()
# Metadata should be preserved.
assert empty_group.id == my_group.id
assert empty_group.name == my_group.name
assert empty_group.icon == my_group.icon
# Original group should be unchanged.
assert len(my_group) == 3
# Clear an already empty group.
still_empty = empty_group.clear()
assert len(still_empty) == 0
assert still_empty.id == empty_group.id
[docs]
def test_in_place_operators():
"""Test in-place operators that return new instances."""
group_a = Group("group_a", "Group A", "π
°οΈ", (AIX, PIDORA))
group_b = Group("group_b", "Group B", "π
±οΈ", (RHEL, PIDORA))
# Test |= (union).
result = group_a | group_b
result_inplace = group_a.copy()
result_inplace |= group_b
assert result.member_ids == result_inplace.member_ids
assert result.member_ids == frozenset({"aix", "pidora", "rhel"})
# Test &= (intersection).
result = group_a & group_b
result_inplace = group_a.copy()
result_inplace &= group_b
assert result.member_ids == result_inplace.member_ids
assert result.member_ids == frozenset({"pidora"})
# Test -= (difference).
result = group_a - group_b
result_inplace = group_a.copy()
result_inplace -= group_b
assert result.member_ids == result_inplace.member_ids
assert result.member_ids == frozenset({"aix"})
# Test ^= (symmetric difference).
result = group_a ^ group_b
result_inplace = group_a.copy()
result_inplace ^= group_b
assert result.member_ids == result_inplace.member_ids
assert result.member_ids == frozenset({"aix", "rhel"})
# Verify original groups are unchanged.
assert group_a.member_ids == frozenset({"aix", "pidora"})
assert group_b.member_ids == frozenset({"rhel", "pidora"})
[docs]
def test_set_operations_with_new_methods():
"""Test combining new methods with existing set operations."""
base_group = Group("base", "Base Group", "π΅", (AIX, PIDORA))
# Add, then union.
with_rhel = base_group.add(RHEL)
union_result = with_rhel.union(BSD_WITHOUT_MACOS)
assert RHEL in union_result
assert AIX in union_result
assert PIDORA in union_result
assert len(union_result) > 3
# Remove, then intersection.
without_aix = base_group.remove(AIX)
intersection_result = without_aix.intersection(ALL_PLATFORMS)
assert AIX not in intersection_result
assert PIDORA in intersection_result
# Clear, then add.
empty = base_group.clear()
with_one = empty.add(RHEL)
assert len(with_one) == 1
assert RHEL in with_one
# Pop, then discard.
popped_trait, remaining = base_group.pop("aix")
assert popped_trait == AIX
after_discard = remaining.discard("pidora")
assert len(after_discard) == 0
[docs]
def test_getitem_with_missing_key():
"""Test that __getitem__ raises KeyError with proper message."""
with pytest.raises(KeyError, match="No trait found whose ID is nonexistent"):
_ = LINUX["nonexistent"]
[docs]
def test_empty_group_bool():
"""Test that empty groups evaluate to False."""
empty_group = Group(id="empty", name="Empty Group", icon="β", members=tuple())
assert bool(empty_group) is False
assert len(empty_group) == 0
[docs]
def test_group_operations_with_empty_groups():
"""Test group operations with empty groups."""
empty_group = Group(id="empty", name="Empty Group", icon="β", members=tuple())
# Union with empty group.
result = LINUX.union(empty_group)
assert result == LINUX
# Intersection with empty group.
result = LINUX.intersection(empty_group)
assert len(result) == 0
# Difference with empty group.
result = LINUX.difference(empty_group)
assert result == LINUX
# Empty group is disjoint with everything.
assert LINUX.isdisjoint(empty_group)
assert empty_group.isdisjoint(LINUX)
[docs]
def test_group_with_duplicate_members():
"""Test that duplicate members in constructor are deduplicated."""
# Create a group with duplicate members.
group = Group(
id="test_dupe",
name="Test Duplicate",
icon="π",
members=[UBUNTU, UBUNTU, UBUNTU],
)
# Should only have one UBUNTU.
assert len(group) == 1
assert UBUNTU in group
[docs]
def test_group_contains_with_string():
"""Test Group.__contains__ with string IDs."""
assert "ubuntu" in LINUX
assert "nonexistent" not in LINUX
[docs]
def test_group_contains_with_wrong_trait_object():
"""Test that Group.__contains__ checks object identity, not just ID."""
# Create a fake trait with the same ID as Ubuntu but different object.
fake_ubuntu = Platform(
id="ubuntu",
name="Fake Ubuntu",
icon="π§",
url="https://example.com",
)
# The real UBUNTU should be in LINUX.
assert UBUNTU in LINUX
# The fake ubuntu with the same ID but different object should NOT be in LINUX.
assert fake_ubuntu not in LINUX
[docs]
def test_group_items():
"""Test Group.items() returns key-value pairs."""
items = list(BSD.items())
assert len(items) > 0
for trait_id, trait in items:
assert isinstance(trait_id, str)
assert isinstance(trait, Trait)
assert trait.id == trait_id
[docs]
def test_group_fullyintersects():
"""Test fullyintersects method."""
# LINUX and BSD should be disjoint (no full intersection).
assert not LINUX.fullyintersects(BSD)
# LINUX should fully intersect with itself.
assert LINUX.fullyintersects(LINUX)
[docs]
def test_group_canonical_property():
"""Test that Group.canonical property works correctly."""
# LINUX is canonical.
if LINUX in NON_OVERLAPPING_GROUPS:
assert LINUX.canonical is True
# UNIX is not canonical (overlaps with others).
if UNIX not in NON_OVERLAPPING_GROUPS:
assert UNIX.canonical is False
[docs]
def test_unique_ids():
"""Traits and group IDs must be unique."""
all_trait_ids = [p.id for p in ALL_TRAITS]
# Traits are expected to be sorted by ID.
assert sorted(all_trait_ids) == all_trait_ids
assert len(set(all_trait_ids)) == len(all_trait_ids)
assert len(all_trait_ids) == len(ALL_TRAITS)
assert len(all_trait_ids) == len(ALL_TRAITS.member_ids)
all_group_ids = {g.id for g in ALL_GROUPS}
assert len(all_group_ids) == len(ALL_GROUPS)
# Check there is no overlap between trait and group IDs.
assert all_group_ids.isdisjoint(all_trait_ids)
assert len(ALL_TRAIT_IDS) == len(ALL_TRAITS) - len(UNKNOWN)
assert ALL_TRAIT_IDS.issubset(ALL_IDS)
assert ALL_TRAIT_IDS.isdisjoint(ALL_GROUP_IDS)
assert len(ALL_GROUP_IDS) == len(ALL_GROUPS) - 1 # Exclude UNKNOWN group.
assert ALL_GROUP_IDS.issubset(ALL_IDS)
assert ALL_GROUP_IDS.isdisjoint(ALL_TRAIT_IDS)
[docs]
def randomize_case(strings: Iterable[str]) -> set[str]:
"""Generate variations of strings with different case."""
test_strings = set()
for string in strings:
test_strings.add(string)
for str_func in (
str.upper,
str.lower,
str.title,
str.capitalize,
str.casefold,
str.swapcase,
):
test_strings.add(str_func(string))
test_strings.add(
"".join(choice((str.upper, str.lower))(char) for char in string)
)
return test_strings
[docs]
@pytest.mark.parametrize("trait_id", randomize_case(ALL_TRAIT_IDS))
def test_traits_from_ids(trait_id):
"""Test traits_from_ids with various case variations."""
traits = traits_from_ids(trait_id)
assert traits
assert len(traits) == 1
trait = traits[0]
assert trait.id == trait_id.lower()
assert trait in ALL_TRAITS
[docs]
@pytest.mark.parametrize("group_id", randomize_case(ALL_GROUP_IDS))
def test_traits_from_ids_group_resolve(group_id):
"""traits_from_ids() can also resolve group IDs."""
traits = traits_from_ids(group_id)
assert traits
assert len(traits) >= 1
groups = groups_from_ids(group_id)
assert len(groups) == 1
group = groups[0]
assert traits == tuple(group)
[docs]
@pytest.mark.parametrize("group_id", randomize_case(ALL_GROUP_IDS))
def test_groups_from_ids(group_id):
"""Test groups_from_ids with various case variations."""
groups = groups_from_ids(group_id)
assert len(groups) == 1
group = groups[0]
assert group.id == group_id.lower()
assert group in ALL_GROUPS
[docs]
@pytest.mark.parametrize(
("items", "expected"),
[
([], frozenset()),
((), frozenset()),
(set(), frozenset()),
(frozenset(), frozenset()),
([AIX], {AIX}),
([AIX, AIX], {AIX}),
([UNIX], {UNIX}),
([UNIX, UNIX], {UNIX}),
([UNIX, AIX], {UNIX}),
([WINDOWS], {ALL_WINDOWS}),
([ALL_PLATFORMS, WINDOWS], {ALL_PLATFORMS}),
([UNIX, WINDOWS, ALL_CI], {ALL_PLATFORMS, ALL_CI}),
([UNIX, ALL_WINDOWS, ALL_CI], {ALL_PLATFORMS, ALL_CI}),
([BSD_WITHOUT_MACOS, UNIX], {UNIX}),
([BSD_WITHOUT_MACOS, MACOS], {BSD}),
(
[
AIX,
ALTLINUX,
AMZN,
ANDROID,
ARCH,
AZURE_PIPELINES,
BAMBOO,
BUILDKITE,
BUILDROOT,
CACHYOS,
CENTOS,
ALL_CI,
CIRCLE_CI,
CIRRUS_CI,
CLOUDLINUX,
CODEBUILD,
CYGWIN,
DEBIAN,
DRAGONFLY_BSD,
EXHERBO,
FEDORA,
FREEBSD,
GENTOO,
GITHUB_CI,
GITLAB_CI,
GUIX,
HAIKU,
HEROKU_CI,
HURD,
IBM_POWERKVM,
ILLUMOS,
KVMIBM,
LINUXMINT,
MACOS,
MAGEIA,
MANDRIVA,
MIDNIGHTBSD,
NETBSD,
NOBARA,
OPENBSD,
OPENSUSE,
ORACLE,
PARALLELS,
PIDORA,
RASPBIAN,
RHEL,
ROCKY,
SCIENTIFIC,
SLACKWARE,
SLES,
SOLARIS,
SUNOS,
TEAMCITY,
TRAVIS_CI,
TUMBLEWEED,
TUXEDO,
UBUNTU,
ULTRAMARINE,
WINDOWS,
WSL1,
WSL2,
XENSERVER,
],
{ALL_PLATFORMS, ALL_CI},
),
],
)
def test_reduction(items, expected):
"""Test reduce function with various inputs."""
results = reduce(items)
assert results == expected
assert isinstance(results, frozenset)
[docs]
@pytest.mark.parametrize(
("items", "expected"),
[
([], frozenset()),
((), frozenset()),
(set(), frozenset()),
(frozenset(), frozenset()),
([AIX], {AIX}),
([AIX, AIX], {AIX}),
([WINDOWS], {ALL_WINDOWS}),
(
[BSD_WITHOUT_MACOS, MACOS],
{DRAGONFLY_BSD, FREEBSD, MACOS, MIDNIGHTBSD, NETBSD, OPENBSD, SUNOS},
),
([MACOS, WINDOWS, WSL1], {MACOS, ALL_WINDOWS, WSL1}),
],
)
def test_reduce_custom_targets(items, expected):
"""Test reduce with custom target pool."""
target_pool = (
MACOS,
UNIX_WITHOUT_MACOS.copy(
id="unix",
name="Unix",
members=tuple(UNIX_WITHOUT_MACOS - BSD_WITHOUT_MACOS - LINUX_LIKE),
),
ALL_WINDOWS,
)
results = reduce(items, target_pool=target_pool)
assert results == expected
assert isinstance(results, frozenset)
[docs]
def test_traits_from_ids_with_invalid_id():
"""Test that traits_from_ids raises ValueError for invalid IDs."""
with pytest.raises(ValueError, match="Unrecognized group or trait IDs"):
traits_from_ids("invalid_id_that_does_not_exist")
[docs]
def test_groups_from_ids_with_invalid_id():
"""Test that groups_from_ids raises ValueError for invalid IDs."""
with pytest.raises(ValueError, match="Unrecognized group IDs"):
groups_from_ids("invalid_group_id_that_does_not_exist")
[docs]
def test_traits_from_ids_with_empty_list():
"""Test that traits_from_ids works with empty input."""
result = traits_from_ids()
assert result == tuple()
[docs]
def test_groups_from_ids_with_empty_list():
"""Test that groups_from_ids works with empty input."""
result = groups_from_ids()
assert result == tuple()
[docs]
def test_traits_from_ids_with_multiple_valid_ids():
"""Test that traits_from_ids works with multiple valid IDs."""
result = traits_from_ids("linux", "ubuntu", "macos")
# Should include LINUX group members plus UBUNTU and MACOS.
assert UBUNTU in result
assert MACOS in result
# LINUX is a group, so its members should be in the result.
assert DEBIAN in result
[docs]
def test_groups_from_ids_with_multiple_valid_ids():
"""Test that groups_from_ids works with multiple valid IDs."""
result = groups_from_ids("linux", "bsd", "unix")
assert LINUX in result
assert BSD in result
assert UNIX in result
[docs]
def test_reduce_with_already_minimal_group():
"""Test reduce with groups that cannot be further reduced."""
# AIX is a single platform, can't be reduced further.
result = reduce([AIX])
assert result == {AIX}
[docs]
def test_reduce_with_complex_overlap():
"""Test reduce with complex overlapping groups."""
# Test with multiple groups that have complex overlaps.
result = reduce([LINUX, BSD, UNIX])
# Should return the minimal covering set.
assert isinstance(result, frozenset)
# UNIX covers both LINUX and BSD, so it's the minimal representation.
assert UNIX in result
assert len(result) == 1
[docs]
def test_reduce_returns_frozenset():
"""Test that reduce always returns a frozenset."""
result1 = reduce([])
result2 = reduce([MACOS])
result3 = reduce([LINUX, BSD])
assert isinstance(result1, frozenset)
assert isinstance(result2, frozenset)
assert isinstance(result3, frozenset)
[docs]
def test_all_ids_constant():
"""Test that ALL_IDS constant contains all trait and group IDs."""
# ALL_IDS should be the union of ALL_TRAIT_IDS and ALL_GROUP_IDS.
assert ALL_IDS == ALL_TRAIT_IDS | ALL_GROUP_IDS
[docs]
def test_all_trait_ids_completeness():
"""Test that ALL_TRAIT_IDS contains IDs from all non-unknown traits."""
# ALL_TRAIT_IDS excludes unknown traits.
expected_ids = {
trait.id for trait in ALL_TRAITS if not trait.id.startswith("unknown")
}
assert ALL_TRAIT_IDS == expected_ids
[docs]
def test_all_group_ids_completeness():
"""Test that ALL_GROUP_IDS contains IDs from all non-unknown groups."""
# ALL_GROUP_IDS excludes the UNKNOWN group.
expected_ids = {group.id for group in ALL_GROUPS if group.id != "unknown"}
assert ALL_GROUP_IDS == expected_ids