Skip to content
This repository has been archived by the owner on Feb 21, 2023. It is now read-only.

Commit

Permalink
Merge pull request #643 from aio-libs/tests_speedups
Browse files Browse the repository at this point in the history
Slightly improve tests execution time
  • Loading branch information
popravich committed Sep 30, 2019
2 parents bac9b42 + b98de66 commit ecc9dc6
Show file tree
Hide file tree
Showing 14 changed files with 127 additions and 198 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ env:
INSTALL_DIR: >-
$HOME/redis
PYTEST_ADDOPTS: >-
'-n auto'
"-n $(( $(nproc) * 2 ))"
python:
- "3.5"
Expand Down Expand Up @@ -99,4 +99,4 @@ cache:
- $HOME/redis/

after_script:
- codecov
- codecov -f coverage.xml
1 change: 1 addition & 0 deletions CHANGES/643.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Slightly improve tests execution time
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@ certificate:
$(MAKE) -C tests/ssl

ci-test: $(REDIS_TARGETS)
$(PYTEST) --cov \
$(PYTEST) \
--cov --cov-report=xml \
$(foreach T,$(REDIS_TARGETS),--redis-server=$T)

ci-test-%: $(INSTALL_DIR)/%/redis-server
Expand Down
32 changes: 7 additions & 25 deletions docs/devel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,12 @@ There is a number of fixtures that can be used to write tests:
:rtype: tuple


Helpers
~~~~~~~
``redis_version`` tests helper
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

:mod:`aioredis` have `_testutils` module with several helpers.
In ``tests`` directory there is a :mod:`_testutils` module with a simple
helper --- :func:`redis_version` --- a function that add a pytest mark to a test
allowing to run it with requested Redis server versions.

.. function:: _testutils.redis_version(\*version, reason)

Expand All @@ -219,28 +221,8 @@ Helpers

.. code-block:: python
from _testutil import redis_version
@redis_version(3, 2, 0, reason="HSTRLEN new in redis 3.2.0")
def test_hstrlen(redis):
pass
.. function:: _testutils.logs(logger, level=None)

Example:

.. code-block:: python
import pytest
@pytest.mark.run_loop
async def test_logs(create_connection, server):
with _testutils.logs('aioredis', 'DEBUG') as cm:
conn = await create_connection(server.tcp_address)
assert cm.output[0].startswith(
'DEBUG:aioredis:Creating tcp connection')
.. function:: _testutils.assert_almost_equal(first, second, places=None, \
msg=None, delta=None)

Adopted version of :meth:`unittest.TestCase.assertAlmostEqual`.
87 changes: 0 additions & 87 deletions tests/_testutils.py
Original file line number Diff line number Diff line change
@@ -1,98 +1,11 @@
import pytest
import logging

from collections import namedtuple

__all__ = [
'assert_almost_equal',
'redis_version',
'logs',
]


def assert_almost_equal(first, second, places=None, msg=None, delta=None):
assert not (places is None and delta is None), \
"Both places and delta are not set, please set one"
if delta is not None:
assert abs(first - second) <= delta
else:
assert round(abs(first - second), places) == 0


def redis_version(*version, reason):
assert 1 < len(version) <= 3, version
assert all(isinstance(v, int) for v in version), version
return pytest.mark.redis_version(version=version, reason=reason)


def logs(logger, level=None):
"""Catches logs for given logger and level.
See unittest.TestCase.assertLogs for details.
"""
return _AssertLogsContext(logger, level)


_LoggingWatcher = namedtuple("_LoggingWatcher", ["records", "output"])


class _CapturingHandler(logging.Handler):
"""
A logging handler capturing all (raw and formatted) logging output.
"""

def __init__(self):
logging.Handler.__init__(self)
self.watcher = _LoggingWatcher([], [])

def flush(self):
pass

def emit(self, record):
self.watcher.records.append(record)
msg = self.format(record)
self.watcher.output.append(msg)


class _AssertLogsContext:
"""Standard unittest's _AssertLogsContext context manager
adopted to raise pytest failure.
"""
LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s"

def __init__(self, logger_name, level):
self.logger_name = logger_name
if level:
self.level = level
else:
self.level = logging.INFO
self.msg = None

def __enter__(self):
if isinstance(self.logger_name, logging.Logger):
logger = self.logger = self.logger_name
else:
logger = self.logger = logging.getLogger(self.logger_name)
formatter = logging.Formatter(self.LOGGING_FORMAT)
handler = _CapturingHandler()
handler.setFormatter(formatter)
self.watcher = handler.watcher
self.old_handlers = logger.handlers[:]
self.old_level = logger.level
self.old_propagate = logger.propagate
logger.handlers = [handler]
logger.setLevel(self.level)
logger.propagate = False
return handler.watcher

def __exit__(self, exc_type, exc_value, tb):
self.logger.handlers = self.old_handlers
self.logger.propagate = self.old_propagate
self.logger.setLevel(self.old_level)
if exc_type is not None:
# let unexpected exceptions pass through
return False
if len(self.watcher.records) == 0:
pytest.fail(
"no logs of level {} or higher triggered on {}"
.format(logging.getLevelName(self.level), self.logger.name))
10 changes: 7 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,9 @@ def timeout(t):
yield True
raise RuntimeError("Redis startup timeout expired")

def maker(name, *masters, quorum=1, noslaves=False):
def maker(name, *masters, quorum=1, noslaves=False,
down_after_milliseconds=3000,
failover_timeout=1000):
key = (name,) + masters
if key in sentinels:
return sentinels[key]
Expand Down Expand Up @@ -409,8 +411,10 @@ def maker(name, *masters, quorum=1, noslaves=False):
for master in masters:
write('sentinel monitor', master.name,
'127.0.0.1', master.tcp_address.port, quorum)
write('sentinel down-after-milliseconds', master.name, '3000')
write('sentinel failover-timeout', master.name, '3000')
write('sentinel down-after-milliseconds', master.name,
down_after_milliseconds)
write('sentinel failover-timeout', master.name,
failover_timeout)
write('sentinel auth-pass', master.name, master.password)

f = open(stdout_file, 'w')
Expand Down
14 changes: 8 additions & 6 deletions tests/generic_commands_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from unittest import mock

from aioredis import ReplyError
from _testutils import redis_version, assert_almost_equal
from _testutils import redis_version


async def add(redis, key, value):
Expand Down Expand Up @@ -383,6 +383,8 @@ async def test_object_encoding(redis, server):
await redis.object_encoding(None)


@redis_version(
3, 0, 0, reason="Older Redis version has lower idle time resolution")
@pytest.mark.run_loop(timeout=20)
async def test_object_idletime(redis, loop, server):
await add(redis, 'foo', 'bar')
Expand Down Expand Up @@ -450,13 +452,13 @@ async def test_pexpire(redis, loop):
@pytest.mark.run_loop
async def test_pexpireat(redis):
await add(redis, 'my-key', 123)
now = math.ceil((await redis.time()) * 1000)
now = int((await redis.time()) * 1000)
fut1 = redis.pexpireat('my-key', now + 2000)
fut2 = redis.ttl('my-key')
fut3 = redis.pttl('my-key')
assert (await fut1) is True
assert (await fut2) == 2
assert_almost_equal((await fut3), 2000, -3)
assert await fut1 is True
assert await fut2 == 2
assert 1000 < await fut3 <= 2000

with pytest.raises(TypeError):
await redis.pexpireat(None, 1234)
Expand All @@ -479,7 +481,7 @@ async def test_pttl(redis, server):

await redis.pexpire('key', 500)
res = await redis.pttl('key')
assert_almost_equal(res, 500, -2)
assert 400 < res <= 500

with pytest.raises(TypeError):
await redis.pttl(None)
Expand Down
36 changes: 21 additions & 15 deletions tests/pool_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
ConnectionsPool,
MaxClientsError,
)
from _testutils import redis_version, logs
from _testutils import redis_version


def _assert_defaults(pool):
Expand Down Expand Up @@ -195,30 +195,31 @@ async def test_release_closed(create_pool, loop, server):


@pytest.mark.run_loop
async def test_release_pending(create_pool, loop, server):
async def test_release_pending(create_pool, loop, server, caplog):
pool = await create_pool(
server.tcp_address,
minsize=1, loop=loop)
assert pool.size == 1
assert pool.freesize == 1

with logs('aioredis', 'WARNING') as cm:
caplog.clear()
with caplog.at_level('WARNING', 'aioredis'):
with (await pool) as conn:
try:
await asyncio.wait_for(
conn.execute(
b'blpop',
b'somekey:not:exists',
b'0'),
0.1,
0.05,
loop=loop)
except asyncio.TimeoutError:
pass
assert pool.size == 0
assert pool.freesize == 0
assert cm.output == [
'WARNING:aioredis:Connection <RedisConnection [db:0]>'
' has pending commands, closing it.'
assert caplog.record_tuples == [
('aioredis', logging.WARNING, 'Connection <RedisConnection [db:0]>'
' has pending commands, closing it.'),
]


Expand Down Expand Up @@ -463,23 +464,28 @@ async def test_pool_close__used(create_pool, server, loop):
@pytest.mark.run_loop
@redis_version(2, 8, 0, reason="maxclients config setting")
async def test_pool_check_closed_when_exception(
create_pool, create_redis, start_server, loop):
create_pool, create_redis, start_server, loop, caplog):
server = start_server('server-small')
redis = await create_redis(server.tcp_address, loop=loop)
await redis.config_set('maxclients', 2)

errors = (MaxClientsError, ConnectionClosedError, ConnectionError)
with logs('aioredis', 'DEBUG') as cm:
caplog.clear()
with caplog.at_level('DEBUG', 'aioredis'):
with pytest.raises(errors):
await create_pool(address=tuple(server.tcp_address),
minsize=3, loop=loop)

assert len(cm.output) >= 3
connect_msg = (
"DEBUG:aioredis:Creating tcp connection"
" to ('localhost', {})".format(server.tcp_address.port))
assert cm.output[:2] == [connect_msg, connect_msg]
assert cm.output[-1] == "DEBUG:aioredis:Closed 1 connection(s)"
assert len(caplog.record_tuples) >= 3
connect_msg = "Creating tcp connection to ('localhost', {})".format(
server.tcp_address.port)
assert caplog.record_tuples[:2] == [
('aioredis', logging.DEBUG, connect_msg),
('aioredis', logging.DEBUG, connect_msg),
]
assert caplog.record_tuples[-1] == (
'aioredis', logging.DEBUG, 'Closed 1 connection(s)'
)


@pytest.mark.run_loop
Expand Down
2 changes: 1 addition & 1 deletion tests/pubsub_commands_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ async def coro(ch):
tsk = asyncio.ensure_future(coro(ch), loop=loop)
await pub.publish_json('chan:1', {'Hello': 'World'})
await pub.publish_json('chan:1', ['message'])
await asyncio.sleep(0, loop=loop)
await asyncio.sleep(0.1, loop=loop)
ch.close()
assert await tsk == [b'{"Hello": "World"}', b'["message"]']

Expand Down
19 changes: 11 additions & 8 deletions tests/pubsub_receiver_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
import asyncio
import json
import sys
import logging

from unittest import mock

from aioredis import ChannelClosedError
from aioredis.abc import AbcChannel
from aioredis.pubsub import Receiver, _Sender
from _testutils import logs


def test_listener_channel(loop):
Expand Down Expand Up @@ -167,7 +167,7 @@ async def test_unsubscribe(create_connection, server, loop):


@pytest.mark.run_loop
async def test_stopped(create_connection, server, loop):
async def test_stopped(create_connection, server, loop, caplog):
sub = await create_connection(server.tcp_address, loop=loop)
pub = await create_connection(server.tcp_address, loop=loop)

Expand All @@ -176,18 +176,21 @@ async def test_stopped(create_connection, server, loop):
assert mpsc.is_active
mpsc.stop()

with logs('aioredis', 'DEBUG') as cm:
caplog.clear()
with caplog.at_level('DEBUG', 'aioredis'):
await pub.execute('publish', 'channel:1', b'Hello')
await asyncio.sleep(0, loop=loop)

assert len(cm.output) == 1
assert len(caplog.record_tuples) == 1
# Receiver must have 1 EndOfStream message
warn_messaege = (
"WARNING:aioredis:Pub/Sub listener message after stop: "
message = (
"Pub/Sub listener message after stop: "
"sender: <_Sender name:b'channel:1', is_pattern:False, receiver:"
"<Receiver is_active:False, senders:1, qsize:0>>, data: b'Hello'"
)
assert cm.output == [warn_messaege]
assert caplog.record_tuples == [
('aioredis', logging.WARNING, message),
]

# assert (await mpsc.get()) is None
with pytest.raises(ChannelClosedError):
Expand Down Expand Up @@ -300,7 +303,7 @@ async def coro(mpsc):
subscribers = await pub.publish_json('chan:2', ['message'])
assert subscribers > 1
loop.call_later(0, mpsc.stop)
# await asyncio.sleep(0, loop=loop)
await asyncio.sleep(0.01, loop=loop)
assert await tsk == [
(snd1, b'{"Hello": "World"}'),
(snd3, (b'chan:1', b'{"Hello": "World"}')),
Expand Down

0 comments on commit ecc9dc6

Please sign in to comment.