mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-04-19 07:26:17 +00:00
Baseline tests, basically just parts of utils right now
This commit is contained in:
parent
3c6279b947
commit
28c7cdca99
35
.github/workflows/test.yml
vendored
Normal file
35
.github/workflows/test.yml
vendored
Normal file
@ -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
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ docs/crowdin.py
|
|||||||
*.jpg
|
*.jpg
|
||||||
*.flac
|
*.flac
|
||||||
*.mo
|
*.mo
|
||||||
|
/.coverage
|
||||||
|
@ -6,6 +6,19 @@ build-backend = "setuptools.build_meta"
|
|||||||
line-length = 125
|
line-length = 125
|
||||||
skip-string-normalization = true
|
skip-string-normalization = true
|
||||||
|
|
||||||
|
[tool.coverage.run]
|
||||||
|
omit = [
|
||||||
|
"discord/__main__.py",
|
||||||
|
"discord/types/*",
|
||||||
|
"*/_types.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.coverage.report]
|
||||||
|
exclude_lines = [
|
||||||
|
"pragma: no cover",
|
||||||
|
"@overload"
|
||||||
|
]
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
profile = "black"
|
profile = "black"
|
||||||
combine_as_imports = true
|
combine_as_imports = true
|
||||||
@ -29,3 +42,6 @@ exclude = [
|
|||||||
]
|
]
|
||||||
pythonVersion = "3.8"
|
pythonVersion = "3.8"
|
||||||
typeCheckingMode = "basic"
|
typeCheckingMode = "basic"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
asyncio_mode = "strict"
|
||||||
|
7
setup.py
7
setup.py
@ -43,6 +43,13 @@ extras_require = {
|
|||||||
],
|
],
|
||||||
'speed': [
|
'speed': [
|
||||||
'orjson>=3.5.4',
|
'orjson>=3.5.4',
|
||||||
|
],
|
||||||
|
'test': [
|
||||||
|
'coverage[toml]',
|
||||||
|
'pytest',
|
||||||
|
'pytest-asyncio',
|
||||||
|
'pytest-cov',
|
||||||
|
'pytest-mock'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
261
tests/test_utils.py
Normal file
261
tests/test_utils.py
Normal file
@ -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…
x
Reference in New Issue
Block a user