[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
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')
await component.start(['test_start'])
assert c._component_state == 'Started' assert c._component_state == 'Started'
assert c.start_count == 1 assert c.start.call_count == 1
c = ComponentTester('test_start_c1')
d = component.start(['test_start_c1'])
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 c._component_state == 'Stopped'
assert not c._component_timer.running assert not c._component_timer.running
assert c.stop_count == 1 assert c.stop.call_count == 1
def on_start(result, c): async def test_stop_all(self):
assert c._component_state == 'Started' components = await self.start_with_depends()
return component.stop(['test_stop_component_c1']).addCallback(on_stop, c) assert all(c._component_state == 'Started' for c in components)
c = ComponentTesterUpdate('test_stop_component_c1') component.stop()
d = component.start(['test_stop_component_c1']) for c in components:
d.addCallback(on_start, c)
return d
def test_stop_all(self):
def on_stop(result, *args):
for c in args:
assert c._component_state == 'Stopped' assert c._component_state == 'Stopped'
assert c.stop_count == 1 assert c.stop.call_count == 1
def on_start(result, *args): self.finish_start_with_depends(components)
for c in args:
assert c._component_state == 'Started'
return component.stop().addCallback(on_stop, *args)
ret = self.start_with_depends() async def test_update(self):
ret[0].addCallback(on_start, *ret[1:]) c = ComponentTester('test_update')
ret[0].addCallback(self.finish_start_with_depends, *ret[1:]) init_update_count = int(c.update.call_count)
return ret[0] await component.start(['test_update'])
def test_update(self): assert c._component_timer
def on_start(result, c1, counter): assert c._component_timer.running
assert c1._component_timer assert c.update.call_count != init_update_count
assert c1._component_timer.running await component.stop()
assert c1.counter != counter
return component.stop()
c1 = ComponentTesterUpdate('test_update_c1') async def test_pause(self):
cnt = int(c1.counter) c = ComponentTester('test_pause')
d = component.start(['test_update_c1']) init_update_count = int(c.update.call_count)
d.addCallback(on_start, c1, cnt) await component.start(['test_pause'])
return d
def test_pause(self): assert c._component_timer
def on_pause(result, c1, counter): assert c.update.call_count != init_update_count
assert c1._component_state == 'Paused'
assert c1.counter != counter
assert not c1._component_timer.running
def on_start(result, c1, counter): await component.pause(['test_pause'])
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') assert c._component_state == 'Paused'
cnt = int(c1.counter) assert c.update.call_count != init_update_count
d = component.start(['test_pause_c1']) assert not c._component_timer.running
d.addCallback(on_start, c1, cnt) async def test_component_start_error(self):
return d ComponentTester('test_start_error')
await component.start(['test_start_error'])
@pytest_twisted.inlineCallbacks await component.pause(['test_start_error'])
def test_component_start_error(self): test_comp = component.get('test_start_error')
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
# self._observer._ignoreErrors(component.ComponentException)
result = yield component.start()
assert [(result[0][0], result[0][1].value)] == [
(
defer.FAILURE, defer.FAILURE,
component.ComponentException( component.ComponentException,
'Trying to start component "%s" but it is ' (
'not in a stopped state. Current state: %s' f'Trying to start component "{name}" but it is '
% ('test_pause_c1', 'Paused'), 'not in a stopped state. Current state: 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