From ec43aab9996023a169df118f686b9e1bb4c149d0 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 7 May 2015 07:58:22 +0200 Subject: [PATCH] Add a new config_stub fixture. This replaces various other constructs: - The default_config fixture - this means the config values used by test_progress.py are set explicitly and the (rather complex) default config is mocked out. - stubs.ConfigStub which was created by the tests manually before. --- tests/browser/test_webelem.py | 40 +++++++-------- tests/conftest.py | 53 ++++++++++++++++++- tests/keyinput/test_basekeyparser.py | 6 ++- tests/keyinput/test_modeparsers.py | 5 +- tests/mainwindow/conftest.py | 52 ------------------- tests/mainwindow/statusbar/test_progress.py | 8 ++- tests/misc/test_editor.py | 57 ++++++++++++--------- tests/stubs.py | 57 +++++++++------------ tests/utils/test_urlutils.py | 31 +++++------ 9 files changed, 159 insertions(+), 150 deletions(-) delete mode 100644 tests/mainwindow/conftest.py diff --git a/tests/browser/test_webelem.py b/tests/browser/test_webelem.py index 0461b0987..ba58820a9 100644 --- a/tests/browser/test_webelem.py +++ b/tests/browser/test_webelem.py @@ -378,11 +378,11 @@ class TestIsEditable: webelem.config = old_config @pytest.fixture - def stub_config(self, stubs, mocker): + def stubbed_config(self, config_stub, mocker): """Fixture to create a config stub with an input section.""" - config = stubs.ConfigStub({'input': {}}) - mocker.patch('qutebrowser.browser.webelem.config', new=config) - return config + config_stub.data = {'input': {}} + mocker.patch('qutebrowser.browser.webelem.config', new=config_stub) + return config_stub def test_input_plain(self): """Test with plain input element.""" @@ -469,27 +469,27 @@ class TestIsEditable: elem = get_webelem(tagname='textarea', attributes={'readonly': None}) assert not elem.is_editable() - def test_embed_true(self, stub_config): + def test_embed_true(self, stubbed_config): """Test embed-element with insert-mode-on-plugins true.""" - stub_config.data['input']['insert-mode-on-plugins'] = True + stubbed_config.data['input']['insert-mode-on-plugins'] = True elem = get_webelem(tagname='embed') assert elem.is_editable() - def test_applet_true(self, stub_config): + def test_applet_true(self, stubbed_config): """Test applet-element with insert-mode-on-plugins true.""" - stub_config.data['input']['insert-mode-on-plugins'] = True + stubbed_config.data['input']['insert-mode-on-plugins'] = True elem = get_webelem(tagname='applet') assert elem.is_editable() - def test_embed_false(self, stub_config): + def test_embed_false(self, stubbed_config): """Test embed-element with insert-mode-on-plugins false.""" - stub_config.data['input']['insert-mode-on-plugins'] = False + stubbed_config.data['input']['insert-mode-on-plugins'] = False elem = get_webelem(tagname='embed') assert not elem.is_editable() - def test_applet_false(self, stub_config): + def test_applet_false(self, stubbed_config): """Test applet-element with insert-mode-on-plugins false.""" - stub_config.data['input']['insert-mode-on-plugins'] = False + stubbed_config.data['input']['insert-mode-on-plugins'] = False elem = get_webelem(tagname='applet') assert not elem.is_editable() @@ -503,30 +503,30 @@ class TestIsEditable: elem = get_webelem(tagname='object', attributes={'type': 'image/gif'}) assert not elem.is_editable() - def test_object_application(self, stub_config): + def test_object_application(self, stubbed_config): """Test object-element with application type.""" - stub_config.data['input']['insert-mode-on-plugins'] = True + stubbed_config.data['input']['insert-mode-on-plugins'] = True elem = get_webelem(tagname='object', attributes={'type': 'application/foo'}) assert elem.is_editable() - def test_object_application_false(self, stub_config): + def test_object_application_false(self, stubbed_config): """Test object-element with application type but not ...-on-plugins.""" - stub_config.data['input']['insert-mode-on-plugins'] = False + stubbed_config.data['input']['insert-mode-on-plugins'] = False elem = get_webelem(tagname='object', attributes={'type': 'application/foo'}) assert not elem.is_editable() - def test_object_classid(self, stub_config): + def test_object_classid(self, stubbed_config): """Test object-element with classid.""" - stub_config.data['input']['insert-mode-on-plugins'] = True + stubbed_config.data['input']['insert-mode-on-plugins'] = True elem = get_webelem(tagname='object', attributes={'type': 'foo', 'classid': 'foo'}) assert elem.is_editable() - def test_object_classid_false(self, stub_config): + def test_object_classid_false(self, stubbed_config): """Test object-element with classid but not insert-mode-on-plugins.""" - stub_config.data['input']['insert-mode-on-plugins'] = False + stubbed_config.data['input']['insert-mode-on-plugins'] = False elem = get_webelem(tagname='object', attributes={'type': 'foo', 'classid': 'foo'}) assert not elem.is_editable() diff --git a/tests/conftest.py b/tests/conftest.py index 892a91912..ca7042232 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,6 +24,10 @@ import itertools import pytest +import stubs as stubsmod +from qutebrowser.config import configexc +from qutebrowser.utils import objreg + @pytest.fixture(scope='session', autouse=True) def app_and_logging(qapp): @@ -38,8 +42,7 @@ def app_and_logging(qapp): @pytest.fixture(scope='session') def stubs(): """Provide access to stub objects useful for testing.""" - import stubs - return stubs + return stubsmod @pytest.fixture(scope='session') @@ -147,3 +150,49 @@ def cmdline_test(request): # Import qutebrowser.app so all cmdutils.register decorators get run. import qutebrowser.app # pylint: disable=unused-variable return request.param + + +class ConfigStub: + + """Stub for the config module. + + Attributes: + data: The config data to return. + """ + + def __init__(self, signal): + """Constructor. + + Args: + signal: The signal to use for self.changed. + """ + self.data = {} + self.changed = signal + + def section(self, name): + """Get a section from the config. + + Args: + name: The section name to get. + + Return: + The section as dict. + """ + return self.data[name] + + def get(self, sect, opt): + """Get a value from the config.""" + data = self.data[sect] + try: + return data[opt] + except KeyError: + raise configexc.NoOptionError(opt, sect) + + +@pytest.yield_fixture +def config_stub(stubs): + """Fixture which provides a fake config object.""" + stub = ConfigStub(stubs.FakeSignal()) + objreg.register('config', stub) + yield stub + objreg.delete('config') diff --git a/tests/keyinput/test_basekeyparser.py b/tests/keyinput/test_basekeyparser.py index 29d556ac5..a68cc685b 100644 --- a/tests/keyinput/test_basekeyparser.py +++ b/tests/keyinput/test_basekeyparser.py @@ -189,10 +189,12 @@ class TestKeyChain: self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None) assert self.kp._keystring == '' - def test_ambiguous_keychain(self, fake_keyevent_factory, mocker, stubs): + def test_ambiguous_keychain(self, fake_keyevent_factory, config_stub, + mocker): """Test ambiguous keychain.""" + config_stub.data = CONFIG mocker.patch('qutebrowser.keyinput.basekeyparser.config', - new=stubs.ConfigStub(CONFIG)) + new=config_stub) timer = self.kp._ambiguous_timer assert not timer.isActive() # We start with 'a' where the keychain gives us an ambiguous result. diff --git a/tests/keyinput/test_modeparsers.py b/tests/keyinput/test_modeparsers.py index e177ad5a3..f61d98f67 100644 --- a/tests/keyinput/test_modeparsers.py +++ b/tests/keyinput/test_modeparsers.py @@ -49,12 +49,13 @@ class TestsNormalKeyParser: # pylint: disable=protected-access @pytest.yield_fixture(autouse=True) - def setup(self, mocker, stubs): + def setup(self, mocker, stubs, config_stub): """Set up mocks and read the test config.""" mocker.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer', new=stubs.FakeTimer) + config_stub.data = CONFIG mocker.patch('qutebrowser.keyinput.modeparsers.config', - new=stubs.ConfigStub(CONFIG)) + new=config_stub) objreg.register('key-config', fake_keyconfig) self.kp = modeparsers.NormalKeyParser(0) diff --git a/tests/mainwindow/conftest.py b/tests/mainwindow/conftest.py deleted file mode 100644 index 47dcc5883..000000000 --- a/tests/mainwindow/conftest.py +++ /dev/null @@ -1,52 +0,0 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2014-2015 Florian Bruhin (The Compiler) -# -# This file is part of qutebrowser. -# -# qutebrowser is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# qutebrowser is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with qutebrowser. If not, see . - -"""pytest fixtures and utilities for testing. - -Fixtures defined here will be visible to all test files in this directory and -below. -""" - -import pytest - -from qutebrowser.config.config import ConfigManager -from qutebrowser.utils import objreg - - -@pytest.yield_fixture -def default_config(): - """ - Fixture that registers an empty config object into the objreg module. - - Should be used by tests which create widgets that obtain their initial - state from the global config object. - - Note: - - If we declare this fixture like this: - - @pytest.yield_fixture(autouse=True) - - Then all tests below this file will have a default config registered - and ready for use. Is that desirable? - """ - config_obj = ConfigManager(configdir=None, fname=None, relaxed=True) - objreg.register('config', config_obj) - yield config_obj - objreg.delete('config') diff --git a/tests/mainwindow/statusbar/test_progress.py b/tests/mainwindow/statusbar/test_progress.py index a3a9f1797..07e93e0e5 100644 --- a/tests/mainwindow/statusbar/test_progress.py +++ b/tests/mainwindow/statusbar/test_progress.py @@ -29,8 +29,14 @@ from qutebrowser.mainwindow.statusbar.progress import Progress @pytest.fixture -def progress_widget(qtbot, default_config): +def progress_widget(qtbot, monkeypatch, config_stub): """Create a Progress widget and checks its initial state.""" + config_stub.data = { + 'colors': {'statusbar.progress.bg': 'black'}, + 'fonts': {}, + } + monkeypatch.setattr( + 'qutebrowser.mainwindow.statusbar.progress.style.config', config_stub) widget = Progress() qtbot.add_widget(widget) assert not widget.isVisible() diff --git a/tests/misc/test_editor.py b/tests/misc/test_editor.py index 417253d21..6cc7ffc6d 100644 --- a/tests/misc/test_editor.py +++ b/tests/misc/test_editor.py @@ -44,39 +44,47 @@ class TestArg: def setup(self, mocker, stubs): mocker.patch('qutebrowser.misc.editor.QProcess', new_callable=stubs.FakeQProcess) - self.config = stubs.ConfigStub() - mocker.patch('qutebrowser.misc.editor.config', new=self.config) self.editor = editor.ExternalEditor(0) yield self.editor._cleanup() # pylint: disable=protected-access - def test_simple_start_args(self): + @pytest.fixture + def stubbed_config(self, config_stub, mocker): + """Fixture to create a config stub with an input section.""" + config_stub.data = {'input': {}} + mocker.patch('qutebrowser.misc.editor.config', new=config_stub) + return config_stub + + def test_simple_start_args(self, stubbed_config): """Test starting editor without arguments.""" - self.config.data = { + stubbed_config.data = { 'general': {'editor': ['bin'], 'editor-encoding': 'utf-8'}} self.editor.edit("") self.editor._proc.start.assert_called_with("bin", []) - def test_start_args(self): + def test_start_args(self, stubbed_config): """Test starting editor with static arguments.""" - self.config.data = {'general': {'editor': ['bin', 'foo', 'bar'], - 'editor-encoding': 'utf-8'}} + stubbed_config.data = { + 'general': {'editor': ['bin', 'foo', 'bar'], + 'editor-encoding': 'utf-8'}} self.editor.edit("") self.editor._proc.start.assert_called_with("bin", ["foo", "bar"]) - def test_placeholder(self): + def test_placeholder(self, stubbed_config): """Test starting editor with placeholder argument.""" - self.config.data = {'general': {'editor': ['bin', 'foo', '{}', 'bar'], - 'editor-encoding': 'utf-8'}} + stubbed_config.data = { + 'general': {'editor': ['bin', 'foo', '{}', 'bar'], + 'editor-encoding': 'utf-8'}} self.editor.edit("") filename = self.editor._filename self.editor._proc.start.assert_called_with( "bin", ["foo", filename, "bar"]) - def test_in_arg_placeholder(self): + def test_in_arg_placeholder(self, stubbed_config): """Test starting editor with placeholder argument inside argument.""" - self.config.data = {'general': {'editor': ['bin', 'foo{}bar'], - 'editor-encoding': 'utf-8'}} + stubbed_config.data = { + 'general': {'editor': ['bin', 'foo{}bar'], + 'editor-encoding': 'utf-8'}} self.editor.edit("") self.editor._proc.start.assert_called_with("bin", ["foo{}bar"]) @@ -90,15 +98,14 @@ class TestFileHandling: """ @pytest.fixture(autouse=True) - def setup(self, mocker, stubs): + def setup(self, mocker, stubs, config_stub): mocker.patch('qutebrowser.misc.editor.message', new=stubs.MessageModule()) mocker.patch('qutebrowser.misc.editor.QProcess', new_callable=stubs.FakeQProcess) - mocker.patch('qutebrowser.misc.editor.config', - new=stubs.ConfigStub( - {'general': {'editor': [''], - 'editor-encoding': 'utf-8'}})) + config_stub.data = {'general': {'editor': [''], + 'editor-encoding': 'utf-8'}} + mocker.patch('qutebrowser.misc.editor.config', config_stub) self.editor = editor.ExternalEditor(0) def test_file_handling_closed_ok(self): @@ -140,11 +147,12 @@ class TestModifyTests: """ @pytest.fixture(autouse=True) - def setup(self, mocker, stubs): + def setup(self, mocker, stubs, config_stub): mocker.patch('qutebrowser.misc.editor.QProcess', new_callable=stubs.FakeQProcess) - mocker.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( - {'general': {'editor': [''], 'editor-encoding': 'utf-8'}})) + config_stub.data = {'general': {'editor': [''], + 'editor-encoding': 'utf-8'}} + mocker.patch('qutebrowser.misc.editor.config', new=config_stub) self.editor = editor.ExternalEditor(0) self.editor.editing_finished = mock.Mock() @@ -211,13 +219,14 @@ class TestErrorMessage: """ @pytest.yield_fixture(autouse=True) - def setup(self, mocker, stubs): + def setup(self, mocker, stubs, config_stub): mocker.patch('qutebrowser.misc.editor.QProcess', new_callable=stubs.FakeQProcess) mocker.patch('qutebrowser.misc.editor.message', new=stubs.MessageModule()) - mocker.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( - {'general': {'editor': [''], 'editor-encoding': 'utf-8'}})) + config_stub.data = {'general': {'editor': [''], + 'editor-encoding': 'utf-8'}} + mocker.patch('qutebrowser.misc.editor.config', new=config_stub) self.editor = editor.ExternalEditor(0) yield self.editor._cleanup() # pylint: disable=protected-access diff --git a/tests/stubs.py b/tests/stubs.py index 95c6833b8..75e33b7eb 100644 --- a/tests/stubs.py +++ b/tests/stubs.py @@ -31,37 +31,6 @@ from PyQt5.QtNetwork import QNetworkRequest from qutebrowser.config import configexc -class ConfigStub: - - """Stub for basekeyparser.config. - - Attributes: - data: The config data to return. - """ - - def __init__(self, data=None): - self.data = data or {} - - def section(self, name): - """Get a section from the config. - - Args: - name: The section name to get. - - Return: - The section as dict. - """ - return self.data[name] - - def get(self, sect, opt): - """Get a value from the config.""" - data = self.data[sect] - try: - return data[opt] - except KeyError: - raise configexc.NoOptionError(opt, sect) - - class FakeKeyEvent: """Fake QKeyPressEvent stub.""" @@ -200,11 +169,35 @@ class FakeQProcess(mock.Mock): class FakeSignal: - """Fake pyqtSignal stub which uses a mock to see if it was called.""" + """Fake pyqtSignal stub which does nothing.""" def __init__(self, name='fake'): self.signal = '2{}(int, int)'.format(name) + def connect(self, slot): + """Connect the signal to a slot. + + Currently does nothing, but could be improved to do some sanity + checking on the slot. + """ + pass + + def disconnect(self, slot=None): + """Disconnect the signal from a slot. + + Currently does nothing, but could be improved to do some sanity + checking on the slot and see if it actually got connected. + """ + pass + + def emit(self, *args): + """Emit the signal. + + Currently does nothing, but could be improved to do type checking based + on a signature given to __init__. + """ + pass + class FakeCmdUtils: diff --git a/tests/utils/test_urlutils.py b/tests/utils/test_urlutils.py index 501ce2dd1..52a3323ff 100644 --- a/tests/utils/test_urlutils.py +++ b/tests/utils/test_urlutils.py @@ -27,13 +27,14 @@ import pytest from qutebrowser.utils import urlutils -def get_config_stub(auto_search=True): - """Get a config stub. +def init_config_stub(stub, auto_search=True): + """Initialize the given config_stub. Args: + stub: The ConfigStub provided by the config_stub fixture. auto_search: The value auto-search should have. """ - return { + stub.data = { 'general': {'auto-search': auto_search}, 'searchengines': { 'test': 'http://www.qutebrowser.org/?q={}', @@ -80,10 +81,10 @@ class TestSearchUrl: """Test _get_search_url.""" @pytest.fixture(autouse=True) - def mock_config(self, stubs, mocker): + def mock_config(self, config_stub, mocker): """Fixture to patch urlutils.config with a stub.""" - mocker.patch('qutebrowser.utils.urlutils.config', - new=stubs.ConfigStub(get_config_stub())) + init_config_stub(config_stub) + mocker.patch('qutebrowser.utils.urlutils.config', config_stub) def test_default_engine(self): """Test default search engine.""" @@ -158,24 +159,24 @@ class TestIsUrl: ) @pytest.mark.parametrize('url', URLS) - def test_urls(self, mocker, stubs, url): + def test_urls(self, mocker, config_stub, url): """Test things which are URLs.""" - mocker.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( - get_config_stub('naive'))) + init_config_stub(config_stub, 'naive') + mocker.patch('qutebrowser.utils.urlutils.config', config_stub) assert urlutils.is_url(url), url @pytest.mark.parametrize('url', NOT_URLS) - def test_not_urls(self, mocker, stubs, url): + def test_not_urls(self, mocker, config_stub, url): """Test things which are not URLs.""" - mocker.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( - get_config_stub('naive'))) + init_config_stub(config_stub, 'naive') + mocker.patch('qutebrowser.utils.urlutils.config', config_stub) assert not urlutils.is_url(url), url @pytest.mark.parametrize('autosearch', [True, False]) - def test_search_autosearch(self, mocker, stubs, autosearch): + def test_search_autosearch(self, mocker, config_stub, autosearch): """Test explicit search with auto-search=True.""" - mocker.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( - get_config_stub(autosearch))) + init_config_stub(config_stub, autosearch) + mocker.patch('qutebrowser.utils.urlutils.config', config_stub) assert not urlutils.is_url('test foo')