diff --git a/deluge/config.py b/deluge/config.py index 8671bc330..abf46d71b 100644 --- a/deluge/config.py +++ b/deluge/config.py @@ -144,11 +144,11 @@ class Config: self.load() - def callLater(self, period, func): # noqa: N802 ignore camelCase + def callLater(self, period, func, *args, **kwargs): # noqa: N802 ignore camelCase """Wrapper around reactor.callLater for test purpose.""" from twisted.internet import reactor - return reactor.callLater(period, func) + return reactor.callLater(period, func, *args, **kwargs) def __contains__(self, item): return item in self.__config diff --git a/deluge/conftest.py b/deluge/conftest.py index ad7c8b30f..4266938fa 100644 --- a/deluge/conftest.py +++ b/deluge/conftest.py @@ -4,11 +4,13 @@ # See LICENSE for more details. # +import unittest.mock import warnings import pytest import pytest_twisted -from twisted.internet.defer import maybeDeferred +from twisted.internet import reactor +from twisted.internet.defer import Deferred, maybeDeferred from twisted.internet.error import CannotListenError from twisted.python.failure import Failure @@ -31,6 +33,29 @@ def listen_port(request): return DEFAULT_LISTEN_PORT +@pytest.fixture +def mock_callback(): + """Returns a `Mock` object which can be registered as a callback to test against. + + If callback was not called within `timeout` seconds, it will raise a TimeoutError. + The returned Mock instance will have a `deferred` attribute which will complete when the callback has been called. + """ + + def reset(): + if mock.called: + original_reset_mock() + deferred = Deferred() + deferred.addTimeout(0.5, reactor) + mock.side_effect = lambda *args, **kw: deferred.callback((args, kw)) + mock.deferred = deferred + + mock = unittest.mock.Mock() + original_reset_mock = mock.reset_mock + mock.reset_mock = reset + mock.reset_mock() + return mock + + @pytest.fixture def config_dir(tmp_path): deluge.configmanager.set_config_dir(tmp_path) diff --git a/deluge/tests/test_config.py b/deluge/tests/test_config.py index 8bf470dbc..2840dbf5b 100644 --- a/deluge/tests/test_config.py +++ b/deluge/tests/test_config.py @@ -10,6 +10,7 @@ import os from codecs import getwriter import pytest +import pytest_twisted from twisted.internet import task from deluge.common import JSON_FORMAT @@ -83,6 +84,33 @@ class TestConfig: config._save_timer.cancel() + @pytest_twisted.ensureDeferred + async def test_on_changed_callback(self, mock_callback): + config = Config('test.conf', config_dir=self.config_dir) + config.register_change_callback(mock_callback) + config['foo'] = 1 + assert config['foo'] == 1 + await mock_callback.deferred + mock_callback.assert_called_once_with('foo', 1) + + @pytest_twisted.ensureDeferred + async def test_key_function_callback(self, mock_callback): + config = Config( + 'test.conf', defaults={'foo': 1, 'bar': 1}, config_dir=self.config_dir + ) + + assert config['foo'] == 1 + config.register_set_function('foo', mock_callback) + await mock_callback.deferred + mock_callback.assert_called_once_with('foo', 1) + + mock_callback.reset_mock() + config.register_set_function('bar', mock_callback, apply_now=False) + mock_callback.assert_not_called() + config['bar'] = 2 + await mock_callback.deferred + mock_callback.assert_called_once_with('bar', 2) + def test_get(self): config = Config('test.conf', config_dir=self.config_dir) config['foo'] = 1