committed by
GitHub
5 changed files with 320 additions and 0 deletions
@ -0,0 +1,35 @@ |
|||
name: test |
|||
|
|||
on: |
|||
push: |
|||
pull_request: |
|||
types: [ opened, edited ] |
|||
|
|||
jobs: |
|||
pytest: |
|||
runs-on: ubuntu-latest |
|||
strategy: |
|||
fail-fast: false |
|||
matrix: |
|||
python-version: [ '3.8', '3.x' ] |
|||
|
|||
name: pytest ${{ matrix.python-version }} |
|||
steps: |
|||
- uses: actions/checkout@v2 |
|||
with: |
|||
fetch-depth: 0 |
|||
|
|||
- name: Set up CPython ${{ matrix.python-version }} |
|||
uses: actions/setup-python@v2 |
|||
with: |
|||
python-version: ${{ matrix.python-version }} |
|||
|
|||
- name: Install dependencies |
|||
run: | |
|||
python -m pip install --upgrade pip setuptools wheel "coverage[toml]" pytest pytest-asyncio pytest-cov pytest-mock |
|||
pip install -U -r requirements.txt |
|||
|
|||
- name: Run tests |
|||
shell: bash |
|||
run: | |
|||
PYTHONPATH="$(pwd)" pytest -vs --cov=discord --cov-report term-missing:skip-covered |
@ -0,0 +1,261 @@ |
|||
# -*- coding: utf-8 -*- |
|||
|
|||
""" |
|||
|
|||
Tests for discord.utils |
|||
|
|||
""" |
|||
|
|||
import datetime |
|||
import random |
|||
import collections |
|||
import secrets |
|||
import sys |
|||
import time |
|||
import typing |
|||
|
|||
import pytest |
|||
|
|||
from discord import utils |
|||
|
|||
|
|||
# Async generator for async support |
|||
async def async_iterate(array): |
|||
for item in array: |
|||
yield item |
|||
|
|||
|
|||
def test_cached_properties(): |
|||
# cached_property |
|||
class Test: |
|||
@utils.cached_property |
|||
def time(self) -> float: |
|||
return time.perf_counter() |
|||
|
|||
instance = Test() |
|||
|
|||
assert instance.time == instance.time |
|||
|
|||
# cached_slot_property |
|||
class TestSlotted: |
|||
__slots__ = ( |
|||
'_cs_time' |
|||
) |
|||
|
|||
@utils.cached_slot_property('_cs_time') |
|||
def time(self) -> float: |
|||
return time.perf_counter() |
|||
|
|||
instance = TestSlotted() |
|||
|
|||
assert instance.time == instance.time |
|||
assert not hasattr(instance, '__dict__') |
|||
|
|||
|
|||
@pytest.mark.parametrize( |
|||
('snowflake', 'time_tuple'), |
|||
[ |
|||
(10000000000000000, (2015, 1, 28, 14, 16, 25)), |
|||
(12345678901234567, (2015, 2, 4, 1, 37, 19)), |
|||
(100000000000000000, (2015, 10, 3, 22, 44, 17)), |
|||
(123456789012345678, (2015, 12, 7, 16, 13, 12)), |
|||
(661720302316814366, (2020, 1, 1, 0, 0, 14)), |
|||
(1000000000000000000, (2022, 7, 22, 11, 22, 59)), |
|||
] |
|||
) |
|||
def test_snowflake_time(snowflake: int, time_tuple: typing.Tuple[int, int, int, int, int, int]): |
|||
dt = utils.snowflake_time(snowflake) |
|||
|
|||
assert (dt.year, dt.month, dt.day, dt.hour, dt.minute, dt.second) == time_tuple |
|||
|
|||
assert utils.time_snowflake(dt, high=False) <= snowflake <= utils.time_snowflake(dt, high=True) |
|||
|
|||
|
|||
@pytest.mark.asyncio |
|||
async def test_get_find(): |
|||
# Generate a dictionary of random keys to values |
|||
mapping = { |
|||
secrets.token_bytes(32): secrets.token_bytes(32) |
|||
for _ in range(100) |
|||
} |
|||
|
|||
# Turn it into a shuffled iterable of pairs |
|||
pair = collections.namedtuple('pair', 'key value') |
|||
array = [pair(key=k, value=v) for k, v in mapping.items()] |
|||
random.shuffle(array) |
|||
|
|||
# Confirm all values can be found |
|||
for key, value in mapping.items(): |
|||
# Sync get |
|||
item = utils.get(array, key=key) |
|||
assert item is not None |
|||
assert item.value == value |
|||
|
|||
# Async get |
|||
item = await utils.get(async_iterate(array), key=key) |
|||
assert item is not None |
|||
assert item.value == value |
|||
|
|||
# Sync find |
|||
item = utils.find(lambda i: i.key == key, array) |
|||
assert item is not None |
|||
assert item.value == value |
|||
|
|||
# Async find |
|||
item = await utils.find(lambda i: i.key == key, async_iterate(array)) |
|||
assert item is not None |
|||
assert item.value == value |
|||
|
|||
|
|||
def test_get_slots(): |
|||
class A: |
|||
__slots__ = ('one', 'two') |
|||
|
|||
class B(A): |
|||
__slots__ = ('three', 'four') |
|||
|
|||
class C(B): |
|||
__slots__ = ('five', 'six') |
|||
|
|||
assert set(utils.get_slots(C)) == {'one', 'two', 'three', 'four', 'five', 'six'} |
|||
|
|||
|
|||
def test_valid_icon_size(): |
|||
# Valid icon sizes |
|||
for size in [16, 32, 64, 128, 256, 512, 1024, 2048, 4096]: |
|||
assert utils.valid_icon_size(size) |
|||
|
|||
# Some not valid icon sizes |
|||
for size in [-1, 0, 20, 103, 500, 8192]: |
|||
assert not utils.valid_icon_size(size) |
|||
|
|||
|
|||
@pytest.mark.parametrize( |
|||
('url', 'code'), |
|||
[ |
|||
('https://discordapp.com/invite/dpy', 'dpy'), |
|||
('https://discord.com/invite/dpy', 'dpy'), |
|||
('https://discord.gg/dpy', 'dpy'), |
|||
] |
|||
) |
|||
def test_resolve_invite(url, code): |
|||
assert utils.resolve_invite(url) == code |
|||
|
|||
|
|||
@pytest.mark.parametrize( |
|||
('url', 'code'), |
|||
[ |
|||
('https://discordapp.com/template/foobar', 'foobar'), |
|||
('https://discord.com/template/foobar', 'foobar'), |
|||
('https://discord.new/foobar', 'foobar'), |
|||
] |
|||
) |
|||
def test_resolve_template(url, code): |
|||
assert utils.resolve_template(url) == code |
|||
|
|||
|
|||
@pytest.mark.parametrize( |
|||
'mention', |
|||
['@everyone', '@here'] |
|||
) |
|||
def test_escape_mentions(mention): |
|||
assert mention not in utils.escape_mentions(mention) |
|||
assert mention not in utils.escape_mentions(f"one {mention} two") |
|||
|
|||
|
|||
@pytest.mark.asyncio |
|||
@pytest.mark.parametrize( |
|||
('source', 'chunk_size', 'chunked'), |
|||
[ |
|||
([1, 2, 3, 4, 5, 6], 2, [[1, 2], [3, 4], [5, 6]]), |
|||
([1, 2, 3, 4, 5, 6], 3, [[1, 2, 3], [4, 5, 6]]), |
|||
([1, 2, 3, 4, 5, 6], 4, [[1, 2, 3, 4], [5, 6]]), |
|||
([1, 2, 3, 4, 5, 6], 5, [[1, 2, 3, 4, 5], [6]]), |
|||
] |
|||
) |
|||
async def test_as_chunks(source, chunk_size, chunked): |
|||
assert [x for x in utils.as_chunks(source, chunk_size)] == chunked |
|||
assert [x async for x in utils.as_chunks(async_iterate(source), chunk_size)] == chunked |
|||
|
|||
|
|||
@pytest.mark.parametrize( |
|||
('annotation', 'resolved'), |
|||
[ |
|||
(datetime.datetime, datetime.datetime), |
|||
('datetime.datetime', datetime.datetime), |
|||
('typing.Union[typing.Literal["a"], typing.Literal["b"]]', typing.Union[typing.Literal["a"], typing.Literal["b"]]), |
|||
('typing.Union[typing.Union[int, str], typing.Union[bool, dict]]', typing.Union[int, str, bool, dict]), |
|||
] |
|||
) |
|||
def test_resolve_annotation(annotation, resolved): |
|||
assert resolved == utils.resolve_annotation(annotation, globals(), locals(), None) |
|||
|
|||
|
|||
@pytest.mark.skipif(sys.version_info < (3, 10), reason="3.10 union syntax") |
|||
@pytest.mark.parametrize( |
|||
('annotation', 'resolved'), |
|||
[ |
|||
('int | None', typing.Optional[int]), |
|||
('str | int', typing.Union[str, int]), |
|||
('str | int | None', typing.Optional[typing.Union[str, int]]), |
|||
] |
|||
) |
|||
def test_resolve_annotation_310(annotation, resolved): |
|||
assert resolved == utils.resolve_annotation(annotation, globals(), locals(), None) |
|||
|
|||
|
|||
# is_inside_class tests |
|||
|
|||
def not_a_class(): |
|||
def not_a_class_either(): |
|||
pass |
|||
return not_a_class_either |
|||
|
|||
class ThisIsAClass: |
|||
def in_a_class(self): |
|||
def not_directly_in_a_class(): |
|||
pass |
|||
return not_directly_in_a_class |
|||
|
|||
@classmethod |
|||
def a_class_method(cls): |
|||
def not_directly_in_a_class(): |
|||
pass |
|||
return not_directly_in_a_class |
|||
|
|||
@staticmethod |
|||
def a_static_method(): |
|||
def not_directly_in_a_class(): |
|||
pass |
|||
return not_directly_in_a_class |
|||
|
|||
class SubClass: |
|||
pass |
|||
|
|||
|
|||
def test_is_inside_class(): |
|||
assert not utils.is_inside_class(not_a_class) |
|||
assert not utils.is_inside_class(not_a_class()) |
|||
assert not utils.is_inside_class(ThisIsAClass) |
|||
assert utils.is_inside_class(ThisIsAClass.in_a_class) |
|||
assert utils.is_inside_class(ThisIsAClass.a_class_method) |
|||
assert utils.is_inside_class(ThisIsAClass.a_static_method) |
|||
assert not utils.is_inside_class(ThisIsAClass().in_a_class()) |
|||
assert not utils.is_inside_class(ThisIsAClass.a_class_method()) |
|||
assert not utils.is_inside_class(ThisIsAClass().a_static_method()) |
|||
assert not utils.is_inside_class(ThisIsAClass.a_static_method()) |
|||
# Only really designed for callables, although I guess it is callable due to the constructor |
|||
assert utils.is_inside_class(ThisIsAClass.SubClass) |
|||
|
|||
|
|||
@pytest.mark.parametrize( |
|||
('dt', 'style', 'formatted'), |
|||
[ |
|||
(datetime.datetime(1970, 1, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), None, '<t:0>'), |
|||
(datetime.datetime(2020, 1, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), None, '<t:1577836800>'), |
|||
(datetime.datetime(2020, 1, 1, 0, 0, 0, 0, tzinfo=datetime.timezone.utc), 'F', '<t:1577836800:F>'), |
|||
(datetime.datetime(2033, 5, 18, 3, 33, 20, 0, tzinfo=datetime.timezone.utc), 'D', '<t:2000000000:D>'), |
|||
] |
|||
) |
|||
def test_format_dt(dt: datetime.datetime, style: typing.Optional[utils.TimestampStyle], formatted: str): |
|||
assert utils.format_dt(dt, style=style) == formatted |
Loading…
Reference in new issue