[Tests] Refactor component tests for readability

Modified test functions to be async.

Used pytest_twisted_ensuredeferred_for_class decorator to avoid needed
ensureDeferred for each test within the class. There might be a way to
do this with fixtures so likely to be improvements for use in all test
classes.

Used Mock in component subclass for simpler tracking of event method calls
This commit is contained in:
Calum Lind 2023-03-05 16:57:43 +00:00
parent 0745c0eff8
commit c38b4c72d0
No known key found for this signature in database
GPG Key ID: 90597A687B836BA3

View File

@ -3,6 +3,9 @@
# the additional special exception to link portions of this program with the OpenSSL library. # the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details. # See LICENSE for more details.
# #
import inspect
import time
from unittest.mock import Mock
import pytest import pytest
import pytest_twisted import pytest_twisted
@ -13,95 +16,59 @@ import deluge.component as component
class ComponentTester(component.Component): class ComponentTester(component.Component):
def __init__(self, name, depend=None): def __init__(self, name, depend=None):
component.Component.__init__(self, name, depend=depend) super().__init__(name, depend=depend)
self.start_count = 0 event_methods = ('start', 'update', 'stop', 'shutdown')
self.stop_count = 0 for event_method in event_methods:
setattr(self, event_method, Mock())
def start(self):
self.start_count += 1
def stop(self):
self.stop_count += 1
class ComponentTesterDelayStart(ComponentTester): class ComponentTesterDelayStart(ComponentTester):
def start(self): def __init__(self, name, depend=None):
def do_sleep(): super().__init__(name, depend=depend)
import time self.start = Mock(side_effect=self.delay)
time.sleep(1) @pytest_twisted.inlineCallbacks
def delay(self):
d = threads.deferToThread(do_sleep) yield threads.deferToThread(time.sleep, 0.5)
def on_done(result):
self.start_count += 1
return d.addCallback(on_done)
class ComponentTesterUpdate(component.Component): def pytest_twisted_ensuredeferred_for_class(cls):
def __init__(self, name): """Applies ensureDeferred to all async test_ methods in class"""
component.Component.__init__(self, name) for name, method in inspect.getmembers(cls, inspect.iscoroutinefunction):
self.counter = 0 if name.startswith('test'):
self.start_count = 0 setattr(cls, name, pytest_twisted.ensureDeferred(method))
self.stop_count = 0 return cls
def update(self):
self.counter += 1
def stop(self):
self.stop_count += 1
class ComponentTesterShutdown(component.Component):
def __init__(self, name):
component.Component.__init__(self, name)
self.shutdowned = False
self.stop_count = 0
def shutdown(self):
self.shutdowned = True
def stop(self):
self.stop_count += 1
@pytest_twisted_ensuredeferred_for_class
@pytest.mark.usefixtures('component') @pytest.mark.usefixtures('component')
class TestComponent: class TestComponent:
def test_start_component(self): async def test_start_component(self):
def on_start(result, c): c = ComponentTester('test_start')
assert c._component_state == 'Started' await component.start(['test_start'])
assert c.start_count == 1
c = ComponentTester('test_start_c1') assert c._component_state == 'Started'
d = component.start(['test_start_c1']) assert c.start.call_count == 1
d.addCallback(on_start, c)
return d
def test_start_stop_depends(self):
def on_stop(result, c1, c2):
assert c1._component_state == 'Stopped'
assert c2._component_state == 'Stopped'
assert c1.stop_count == 1
assert c2.stop_count == 1
def on_start(result, c1, c2):
assert c1._component_state == 'Started'
assert c2._component_state == 'Started'
assert c1.start_count == 1
assert c2.start_count == 1
return component.stop(['test_start_depends_c1']).addCallback(
on_stop, c1, c2
)
async def test_start_stop_depends(self):
c1 = ComponentTester('test_start_depends_c1') c1 = ComponentTester('test_start_depends_c1')
c2 = ComponentTester('test_start_depends_c2', depend=['test_start_depends_c1']) c2 = ComponentTester('test_start_depends_c2', depend=['test_start_depends_c1'])
d = component.start(['test_start_depends_c2']) await component.start('test_start_depends_c2')
d.addCallback(on_start, c1, c2)
return d
def start_with_depends(self): assert c1._component_state == 'Started'
assert c2._component_state == 'Started'
assert c1.start.call_count == 1
assert c2.start.call_count == 1
await component.stop(['test_start_depends_c1'])
assert c1._component_state == 'Stopped'
assert c2._component_state == 'Stopped'
assert c1.stop.call_count == 1
assert c2.stop.call_count == 1
async def start_with_depends(self):
c1 = ComponentTesterDelayStart('test_start_all_c1') c1 = ComponentTesterDelayStart('test_start_all_c1')
c2 = ComponentTester('test_start_all_c2', depend=['test_start_all_c4']) c2 = ComponentTester('test_start_all_c2', depend=['test_start_all_c4'])
c3 = ComponentTesterDelayStart( c3 = ComponentTesterDelayStart(
@ -110,141 +77,108 @@ class TestComponent:
c4 = ComponentTester('test_start_all_c4', depend=['test_start_all_c3']) c4 = ComponentTester('test_start_all_c4', depend=['test_start_all_c3'])
c5 = ComponentTester('test_start_all_c5') c5 = ComponentTester('test_start_all_c5')
d = component.start() await component.start()
return (d, c1, c2, c3, c4, c5) return c1, c2, c3, c4, c5
def finish_start_with_depends(self, *args): def finish_start_with_depends(self, *args):
for c in args[1:]: for c in args[1:]:
component.deregister(c) component.deregister(c)
def test_start_all(self): async def test_start_all(self):
def on_start(*args): components = await self.start_with_depends()
for c in args[1:]: for c in components:
assert c._component_state == 'Started' assert c._component_state == 'Started'
assert c.start_count == 1 assert c.start.call_count == 1
ret = self.start_with_depends() self.finish_start_with_depends(components)
ret[0].addCallback(on_start, *ret[1:])
ret[0].addCallback(self.finish_start_with_depends, *ret[1:])
return ret[0]
def test_register_exception(self): def test_register_exception(self):
ComponentTester('test_register_exception_c1') ComponentTester('test_register_exception')
with pytest.raises(component.ComponentAlreadyRegistered): with pytest.raises(component.ComponentAlreadyRegistered):
ComponentTester( ComponentTester(
'test_register_exception_c1', 'test_register_exception',
) )
def test_stop_component(self): async def test_stop(self):
def on_stop(result, c): c = ComponentTester('test_stop')
await component.start(['test_stop'])
assert c._component_state == 'Started'
await component.stop(['test_stop'])
assert c._component_state == 'Stopped'
assert not c._component_timer.running
assert c.stop.call_count == 1
async def test_stop_all(self):
components = await self.start_with_depends()
assert all(c._component_state == 'Started' for c in components)
component.stop()
for c in components:
assert c._component_state == 'Stopped' assert c._component_state == 'Stopped'
assert not c._component_timer.running assert c.stop.call_count == 1
assert c.stop_count == 1
def on_start(result, c): self.finish_start_with_depends(components)
assert c._component_state == 'Started'
return component.stop(['test_stop_component_c1']).addCallback(on_stop, c)
c = ComponentTesterUpdate('test_stop_component_c1') async def test_update(self):
d = component.start(['test_stop_component_c1']) c = ComponentTester('test_update')
d.addCallback(on_start, c) init_update_count = int(c.update.call_count)
return d await component.start(['test_update'])
def test_stop_all(self): assert c._component_timer
def on_stop(result, *args): assert c._component_timer.running
for c in args: assert c.update.call_count != init_update_count
assert c._component_state == 'Stopped' await component.stop()
assert c.stop_count == 1
def on_start(result, *args): async def test_pause(self):
for c in args: c = ComponentTester('test_pause')
assert c._component_state == 'Started' init_update_count = int(c.update.call_count)
return component.stop().addCallback(on_stop, *args)
ret = self.start_with_depends() await component.start(['test_pause'])
ret[0].addCallback(on_start, *ret[1:])
ret[0].addCallback(self.finish_start_with_depends, *ret[1:])
return ret[0]
def test_update(self): assert c._component_timer
def on_start(result, c1, counter): assert c.update.call_count != init_update_count
assert c1._component_timer
assert c1._component_timer.running
assert c1.counter != counter
return component.stop()
c1 = ComponentTesterUpdate('test_update_c1') await component.pause(['test_pause'])
cnt = int(c1.counter)
d = component.start(['test_update_c1'])
d.addCallback(on_start, c1, cnt) assert c._component_state == 'Paused'
return d assert c.update.call_count != init_update_count
assert not c._component_timer.running
def test_pause(self): async def test_component_start_error(self):
def on_pause(result, c1, counter): ComponentTester('test_start_error')
assert c1._component_state == 'Paused' await component.start(['test_start_error'])
assert c1.counter != counter await component.pause(['test_start_error'])
assert not c1._component_timer.running test_comp = component.get('test_start_error')
def on_start(result, c1, counter):
assert c1._component_timer
assert c1.counter != counter
d = component.pause(['test_pause_c1'])
d.addCallback(on_pause, c1, counter)
return d
c1 = ComponentTesterUpdate('test_pause_c1')
cnt = int(c1.counter)
d = component.start(['test_pause_c1'])
d.addCallback(on_start, c1, cnt)
return d
@pytest_twisted.inlineCallbacks
def test_component_start_error(self):
ComponentTesterUpdate('test_pause_c1')
yield component.start(['test_pause_c1'])
yield component.pause(['test_pause_c1'])
test_comp = component.get('test_pause_c1')
with pytest.raises(component.ComponentException, match='Current state: Paused'): with pytest.raises(component.ComponentException, match='Current state: Paused'):
yield test_comp._component_start() await test_comp._component_start()
@pytest_twisted.inlineCallbacks async def test_start_paused_error(self):
def test_start_paused_error(self): name = 'test_pause_error'
ComponentTesterUpdate('test_pause_c1') ComponentTester(name)
yield component.start(['test_pause_c1']) await component.start([name])
yield component.pause(['test_pause_c1']) await component.pause([name])
# Deferreds that fail in component have to error handler which results in (failure, error), *_ = await component.start()
# twisted doing a log.err call which causes the test to fail. assert (failure, error.type, error.value.message) == (
# Prevent failure by ignoring the exception defer.FAILURE,
# self._observer._ignoreErrors(component.ComponentException) component.ComponentException,
result = yield component.start()
assert [(result[0][0], result[0][1].value)] == [
( (
defer.FAILURE, f'Trying to start component "{name}" but it is '
component.ComponentException( 'not in a stopped state. Current state: Paused'
'Trying to start component "%s" but it is ' ),
'not in a stopped state. Current state: %s' )
% ('test_pause_c1', 'Paused'),
'',
),
)
]
def test_shutdown(self): async def test_shutdown(self):
def on_shutdown(result, c1): c = ComponentTester('test_shutdown')
assert c1.shutdowned
assert c1._component_state == 'Stopped'
assert c1.stop_count == 1
def on_start(result, c1): await component.start(['test_shutdown'])
d = component.shutdown() await component.shutdown()
d.addCallback(on_shutdown, c1)
return d
c1 = ComponentTesterShutdown('test_shutdown_c1') assert c.shutdown.call_count == 1
d = component.start(['test_shutdown_c1']) assert c._component_state == 'Stopped'
d.addCallback(on_start, c1) assert not c._component_timer.running
return d assert c.stop.call_count == 1