matchy/matching_test.py
Marc Di Luzio 874a24dd1d Implement a history-based matching algorythm
The bot will attempt to keep producing groups with entirely unique matches based on the full history of matches until it can't. It'll then step forward and ignore a week of history and try again, ignoring more history until no history is left
2024-08-10 15:12:14 +01:00

150 lines
4.1 KiB
Python

"""
Test functions for Matchy
"""
import discord
import pytest
import matching
import history
from datetime import datetime, timedelta
def test_protocols():
"""Verify the protocols we're using match the discord ones"""
assert isinstance(discord.Member, matching.Member)
assert isinstance(discord.Guild, matching.Guild)
assert isinstance(discord.Role, matching.Role)
class TestMember():
def __init__(self, id: int):
self._id = id
@property
def mention(self) -> str:
return f"<@{self._id}>"
@property
def id(self) -> int:
return self._id
@id.setter
def id(self, value):
self._id = value
@pytest.mark.parametrize("matchees, per_group", [
# Simplest test possible
([TestMember(1)], 1),
# More requested than we have
([TestMember(1)], 2),
# A selection of hyper-simple checks to validate core functionality
([TestMember(1)] * 100, 3),
([TestMember(1)] * 12, 5),
([TestMember(1)] * 11, 2),
([TestMember(1)] * 356, 8),
])
def test_matchees_to_groups_no_history(matchees, per_group):
"""Test simple group matching works"""
hist = history.History()
core_validate_members_to_groups(matchees, hist, per_group)
def items_found_in_lists(list_of_lists, items):
"""validates if any sets of items are found in individual lists"""
for sublist in list_of_lists:
if all(item in sublist for item in items):
return True
return False
@pytest.mark.parametrize("history_data, matchees, per_group, checks", [
# Slightly more difficult test
# Describe a history where we previously matched up some people and ensure they don't get rematched
(
[
{
"ts": datetime.now() - timedelta(days=1),
"groups": [
[TestMember(1), TestMember(2)],
[TestMember(3), TestMember(4)],
]
}
],
[
TestMember(1),
TestMember(2),
TestMember(3),
TestMember(4),
],
2,
[
lambda groups: not items_found_in_lists(
groups, [TestMember(1), TestMember(2)]),
lambda groups: not items_found_in_lists(
groups, [TestMember(3), TestMember(4)])
]
),
# Feed the system an "impossible" test
# The function should fall back to ignoring history and still give us something
(
[
{
"ts": datetime.now() - timedelta(days=1),
"groups": [
[TestMember(1), TestMember(2), TestMember(3)],
[TestMember(4), TestMember(5), TestMember(6)],
]
}
],
[
TestMember(1),
TestMember(2),
TestMember(3),
TestMember(4),
TestMember(5),
TestMember(6),
],
3,
[
# Nothing specific to validate
]
),
])
def test_matchees_to_groups_with_history(history_data, matchees, per_group, checks):
"""Test simple group matching works"""
hist = history.History()
# Replay the history
for d in history_data:
hist.log_groups_to_history(d["groups"], d["ts"])
groups = core_validate_members_to_groups(matchees, hist, per_group)
# Run the custom validate functions
for check in checks:
assert check(groups)
def core_validate_members_to_groups(matchees: list[TestMember], hist: history.History, per_group: int):
# Convert members to groups
groups = matching.members_to_groups(matchees, hist, per_group)
# We should always have one group
assert len(groups)
# Log the groups to history
# This will validate the internals
hist.log_groups_to_history(groups)
# Ensure each group contains within the bounds of expected members
for group in groups:
if len(matchees) >= per_group:
assert len(group) >= per_group
else:
assert len(group) == len(matchees)
assert len(group) < per_group*2 # TODO: We could be more strict here
return groups