diff options
author | Calum Lind <calumlind+deluge@gmail.com> | 2023-03-05 16:57:43 +0000 |
---|---|---|
committer | Calum Lind <calumlind+deluge@gmail.com> | 2023-03-06 13:25:00 +0000 |
commit | c38b4c72d03331ddda990b49cfda27c27c79bad5 (patch) | |
tree | 51935e947b4d8397911969bfae097829ddebe539 | |
parent | 0745c0eff85ea4e4a3646c454098503ed08c56ff (diff) | |
download | deluge-c38b4c72d03331ddda990b49cfda27c27c79bad5.tar.gz deluge-c38b4c72d03331ddda990b49cfda27c27c79bad5.tar.bz2 deluge-c38b4c72d03331ddda990b49cfda27c27c79bad5.zip |
[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
-rw-r--r-- | deluge/tests/test_component.py | 324 |
1 files changed, 129 insertions, 195 deletions
diff --git a/deluge/tests/test_component.py b/deluge/tests/test_component.py index aee113668..7655eef11 100644 --- a/deluge/tests/test_component.py +++ b/deluge/tests/test_component.py @@ -3,6 +3,9 @@ # the additional special exception to link portions of this program with the OpenSSL library. # See LICENSE for more details. # +import inspect +import time +from unittest.mock import Mock import pytest import pytest_twisted @@ -13,95 +16,59 @@ import deluge.component as component class ComponentTester(component.Component): def __init__(self, name, depend=None): - component.Component.__init__(self, name, depend=depend) - self.start_count = 0 - self.stop_count = 0 - - def start(self): - self.start_count += 1 - - def stop(self): - self.stop_count += 1 + super().__init__(name, depend=depend) + event_methods = ('start', 'update', 'stop', 'shutdown') + for event_method in event_methods: + setattr(self, event_method, Mock()) class ComponentTesterDelayStart(ComponentTester): - def start(self): - def do_sleep(): - import time - - time.sleep(1) - - d = threads.deferToThread(do_sleep) - - def on_done(result): - self.start_count += 1 - - return d.addCallback(on_done) - - -class ComponentTesterUpdate(component.Component): - def __init__(self, name): - component.Component.__init__(self, name) - self.counter = 0 - self.start_count = 0 - self.stop_count = 0 - - def update(self): - self.counter += 1 - - def stop(self): - self.stop_count += 1 - + def __init__(self, name, depend=None): + super().__init__(name, depend=depend) + self.start = Mock(side_effect=self.delay) -class ComponentTesterShutdown(component.Component): - def __init__(self, name): - component.Component.__init__(self, name) - self.shutdowned = False - self.stop_count = 0 + @pytest_twisted.inlineCallbacks + def delay(self): + yield threads.deferToThread(time.sleep, 0.5) - def shutdown(self): - self.shutdowned = True - def stop(self): - self.stop_count += 1 +def pytest_twisted_ensuredeferred_for_class(cls): + """Applies ensureDeferred to all async test_ methods in class""" + for name, method in inspect.getmembers(cls, inspect.iscoroutinefunction): + if name.startswith('test'): + setattr(cls, name, pytest_twisted.ensureDeferred(method)) + return cls +@pytest_twisted_ensuredeferred_for_class @pytest.mark.usefixtures('component') class TestComponent: - def test_start_component(self): - def on_start(result, c): - assert c._component_state == 'Started' - assert c.start_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_component(self): + c = ComponentTester('test_start') + await component.start(['test_start']) + + assert c._component_state == 'Started' + assert c.start.call_count == 1 + async def test_start_stop_depends(self): c1 = ComponentTester('test_start_depends_c1') c2 = ComponentTester('test_start_depends_c2', depend=['test_start_depends_c1']) - d = component.start(['test_start_depends_c2']) - d.addCallback(on_start, c1, c2) - return d + await component.start('test_start_depends_c2') - 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') c2 = ComponentTester('test_start_all_c2', depend=['test_start_all_c4']) c3 = ComponentTesterDelayStart( @@ -110,141 +77,108 @@ class TestComponent: c4 = ComponentTester('test_start_all_c4', depend=['test_start_all_c3']) c5 = ComponentTester('test_start_all_c5') - d = component.start() - return (d, c1, c2, c3, c4, c5) + await component.start() + return c1, c2, c3, c4, c5 def finish_start_with_depends(self, *args): for c in args[1:]: component.deregister(c) - def test_start_all(self): - def on_start(*args): - for c in args[1:]: - assert c._component_state == 'Started' - assert c.start_count == 1 + async def test_start_all(self): + components = await self.start_with_depends() + for c in components: + assert c._component_state == 'Started' + assert c.start.call_count == 1 - ret = self.start_with_depends() - ret[0].addCallback(on_start, *ret[1:]) - ret[0].addCallback(self.finish_start_with_depends, *ret[1:]) - return ret[0] + self.finish_start_with_depends(components) def test_register_exception(self): - ComponentTester('test_register_exception_c1') + ComponentTester('test_register_exception') with pytest.raises(component.ComponentAlreadyRegistered): ComponentTester( - 'test_register_exception_c1', + 'test_register_exception', ) - def test_stop_component(self): - def on_stop(result, c): + async def test_stop(self): + 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 not c._component_timer.running - assert c.stop_count == 1 + assert c.stop.call_count == 1 - def on_start(result, c): - assert c._component_state == 'Started' - return component.stop(['test_stop_component_c1']).addCallback(on_stop, c) - - c = ComponentTesterUpdate('test_stop_component_c1') - d = component.start(['test_stop_component_c1']) - 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.stop_count == 1 - - def on_start(result, *args): - for c in args: - assert c._component_state == 'Started' - return component.stop().addCallback(on_stop, *args) - - ret = self.start_with_depends() - ret[0].addCallback(on_start, *ret[1:]) - ret[0].addCallback(self.finish_start_with_depends, *ret[1:]) - return ret[0] - - def test_update(self): - def on_start(result, c1, counter): - assert c1._component_timer - assert c1._component_timer.running - assert c1.counter != counter - return component.stop() - - c1 = ComponentTesterUpdate('test_update_c1') - cnt = int(c1.counter) - d = component.start(['test_update_c1']) - - d.addCallback(on_start, c1, cnt) - return d - - def test_pause(self): - def on_pause(result, c1, counter): - assert c1._component_state == 'Paused' - assert c1.counter != counter - assert not c1._component_timer.running - - 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 + self.finish_start_with_depends(components) - @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'): - yield test_comp._component_start() + async def test_update(self): + c = ComponentTester('test_update') + init_update_count = int(c.update.call_count) + await component.start(['test_update']) - @pytest_twisted.inlineCallbacks - def test_start_paused_error(self): - ComponentTesterUpdate('test_pause_c1') - yield component.start(['test_pause_c1']) - yield component.pause(['test_pause_c1']) - - # Deferreds that fail in component have to error handler which results in - # twisted doing a log.err call which causes the test to fail. - # Prevent failure by ignoring the exception - # self._observer._ignoreErrors(component.ComponentException) - - result = yield component.start() - assert [(result[0][0], result[0][1].value)] == [ + assert c._component_timer + assert c._component_timer.running + assert c.update.call_count != init_update_count + await component.stop() + + async def test_pause(self): + c = ComponentTester('test_pause') + init_update_count = int(c.update.call_count) + + await component.start(['test_pause']) + + assert c._component_timer + assert c.update.call_count != init_update_count + + await component.pause(['test_pause']) + + assert c._component_state == 'Paused' + assert c.update.call_count != init_update_count + assert not c._component_timer.running + + async def test_component_start_error(self): + ComponentTester('test_start_error') + await component.start(['test_start_error']) + await component.pause(['test_start_error']) + test_comp = component.get('test_start_error') + with pytest.raises(component.ComponentException, match='Current state: Paused'): + await test_comp._component_start() + + async def test_start_paused_error(self): + name = 'test_pause_error' + ComponentTester(name) + await component.start([name]) + await component.pause([name]) + + (failure, error), *_ = await component.start() + assert (failure, error.type, error.value.message) == ( + defer.FAILURE, + component.ComponentException, ( - defer.FAILURE, - component.ComponentException( - 'Trying to start component "%s" but it is ' - 'not in a stopped state. Current state: %s' - % ('test_pause_c1', 'Paused'), - '', - ), - ) - ] - - def test_shutdown(self): - def on_shutdown(result, c1): - assert c1.shutdowned - assert c1._component_state == 'Stopped' - assert c1.stop_count == 1 - - def on_start(result, c1): - d = component.shutdown() - d.addCallback(on_shutdown, c1) - return d - - c1 = ComponentTesterShutdown('test_shutdown_c1') - d = component.start(['test_shutdown_c1']) - d.addCallback(on_start, c1) - return d + f'Trying to start component "{name}" but it is ' + 'not in a stopped state. Current state: Paused' + ), + ) + + async def test_shutdown(self): + c = ComponentTester('test_shutdown') + + await component.start(['test_shutdown']) + await component.shutdown() + + assert c.shutdown.call_count == 1 + assert c._component_state == 'Stopped' + assert not c._component_timer.running + assert c.stop.call_count == 1 |