diff --git a/qutebrowser/misc/split.py b/qutebrowser/misc/split.py index bd1904763..b763d8246 100644 --- a/qutebrowser/misc/split.py +++ b/qutebrowser/misc/split.py @@ -127,7 +127,7 @@ def split(s, keep=False): """Split a string via ShellLexer. Args: - keep: Whether to keep are special chars in the split output. + keep: Whether to keep special chars in the split output. """ lexer = ShellLexer(s) lexer.keep = keep diff --git a/tests/commands/test_runners.py b/tests/commands/test_runners.py new file mode 100644 index 000000000..a03eab9d8 --- /dev/null +++ b/tests/commands/test_runners.py @@ -0,0 +1,44 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 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 . + +"""Tests for qutebrowser.commands.runners.""" + +import pytest + +from qutebrowser.commands import runners, cmdexc + + +class TestCommandRunner: + + """Tests for CommandRunner.""" + + def test_parse_all(self, cmdline_test): + """Test parsing of commands. + + See https://github.com/The-Compiler/qutebrowser/issues/615 + + Args: + cmdline_test: A pytest fixture which provides testcases. + """ + cr = runners.CommandRunner(0) + if cmdline_test.valid: + list(cr.parse_all(cmdline_test.cmd, aliases=False)) + else: + with pytest.raises(cmdexc.NoSuchCommandError): + list(cr.parse_all(cmdline_test.cmd, aliases=False)) diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 6bee98ee4..383fed232 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -31,7 +31,9 @@ from PyQt5.QtCore import QObject from PyQt5.QtGui import QColor import pytest -from qutebrowser.config import config, configexc +from qutebrowser.config import config, configexc, configdata +from qutebrowser.config.parsers import keyconf +from qutebrowser.commands import runners from qutebrowser.utils import objreg, standarddir @@ -155,6 +157,27 @@ class TestConfigParser: self.cfg.get('general', 'bar') # pylint: disable=bad-config-call +class TestKeyConfigParser: + + """Test config.parsers.keyconf.KeyConfigParser.""" + + def test_cmd_binding(self, cmdline_test): + """Test various command bindings. + + See https://github.com/The-Compiler/qutebrowser/issues/615 + + Args: + cmdline_test: A pytest fixture which provides testcases. + """ + kcp = keyconf.KeyConfigParser(None, None) + kcp._cur_section = 'normal' + if cmdline_test.valid: + kcp._read_command(cmdline_test.cmd) + else: + with pytest.raises(keyconf.KeyConfigError): + kcp._read_command(cmdline_test.cmd) + + class TestDefaultConfig: """Test validating of the default config.""" @@ -164,6 +187,16 @@ class TestDefaultConfig: conf = config.ConfigManager(None, None) conf._validate_all() + def test_default_key_config(self): + """Test validating of the default key config.""" + # We import qutebrowser.app so the cmdutils.register decorators run. + import qutebrowser.app # pylint: disable=unused-variable + conf = keyconf.KeyConfigParser(None, None) + runner = runners.CommandRunner(win_id=0) + for sectname in configdata.KEY_DATA: + for cmd in conf.get_bindings_for(sectname).values(): + runner.parse(cmd, aliases=False) + class TestConfigInit: diff --git a/tests/conftest.py b/tests/conftest.py index d3411694c..892a91912 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -19,6 +19,9 @@ """The qutebrowser test suite contest file.""" +import collections +import itertools + import pytest @@ -49,15 +52,23 @@ def unicode_encode_err(): 'fake exception') # reason +@pytest.fixture(scope='session') +def qnam(): + """Session-wide QNetworkAccessManager.""" + from PyQt5.QtNetwork import QNetworkAccessManager + nam = QNetworkAccessManager() + nam.setNetworkAccessible(QNetworkAccessManager.NotAccessible) + return nam + + @pytest.fixture -def webpage(): +def webpage(qnam): """Get a new QWebPage object.""" from PyQt5.QtWebKitWidgets import QWebPage - from PyQt5.QtNetwork import QNetworkAccessManager page = QWebPage() - nam = page.networkAccessManager() - nam.setNetworkAccessible(QNetworkAccessManager.NotAccessible) + page.networkAccessManager().deleteLater() + page.setNetworkAccessManager(qnam) return page @@ -76,3 +87,63 @@ def fake_keyevent_factory(): return evtmock return fake_keyevent + + +def pytest_collection_modifyitems(items): + """Automatically add a 'gui' marker to all gui-related tests. + + pytest hook called after collection has been performed, adds a marker + named "gui" which can be used to filter gui tests from the command line. + For example: + + py.test -m "not gui" # run all tests except gui tests + py.test -m "gui" # run only gui tests + + Args: + items: list of _pytest.main.Node items, where each item represents + a python test that will be executed. + + Reference: + http://pytest.org/latest/plugins.html + """ + for item in items: + if 'qtbot' in getattr(item, 'fixturenames', ()): + item.add_marker('gui') + + +def _generate_cmdline_tests(): + """Generate testcases for test_split_binding.""" + # pylint: disable=invalid-name + TestCase = collections.namedtuple('TestCase', 'cmd, valid') + separators = [';;', ' ;; ', ';; ', ' ;;'] + invalid = ['foo', ''] + valid = ['leave-mode', 'hint all'] + # Valid command only -> valid + for item in valid: + yield TestCase(''.join(item), True) + # Invalid command only -> invalid + for item in valid: + yield TestCase(''.join(item), True) + # Invalid command combined with invalid command -> invalid + for item in itertools.product(invalid, separators, invalid): + yield TestCase(''.join(item), False) + # Valid command combined with valid command -> valid + for item in itertools.product(valid, separators, valid): + yield TestCase(''.join(item), True) + # Valid command combined with invalid command -> invalid + for item in itertools.product(valid, separators, invalid): + yield TestCase(''.join(item), False) + # Invalid command combined with valid command -> invalid + for item in itertools.product(invalid, separators, valid): + yield TestCase(''.join(item), False) + # Command with no_cmd_split combined with an "invalid" command -> valid + for item in itertools.product(['bind x open'], separators, invalid): + yield TestCase(''.join(item), True) + + +@pytest.fixture(params=_generate_cmdline_tests()) +def cmdline_test(request): + """Fixture which generates tests for things validating commandlines.""" + # Import qutebrowser.app so all cmdutils.register decorators get run. + import qutebrowser.app # pylint: disable=unused-variable + return request.param diff --git a/tests/keyinput/test_basekeyparser.py b/tests/keyinput/test_basekeyparser.py index 7164bffbd..29d556ac5 100644 --- a/tests/keyinput/test_basekeyparser.py +++ b/tests/keyinput/test_basekeyparser.py @@ -63,44 +63,27 @@ class TestSplitCount: """Test the _split_count method. - Attributes: - kp: The BaseKeyParser we're testing. + Class Attributes: + TESTS: list of parameters for the tests, as tuples of + (input_key, supports_count, expected) """ - @pytest.fixture(autouse=True) - def setup(self): - self.kp = basekeyparser.BaseKeyParser(0, supports_count=True) + TESTS = [ + # (input_key, supports_count, expected) + ('10', True, (10, '')), + ('10foo', True, (10, 'foo')), + ('-1foo', True, (None, '-1foo')), + ('10e4foo', True, (10, 'e4foo')), + ('foo', True, (None, 'foo')), + ('10foo', False, (None, '10foo')), + ] - def test_onlycount(self): + @pytest.mark.parametrize('input_key, supports_count, expected', TESTS) + def test_splitcount(self, input_key, supports_count, expected): """Test split_count with only a count.""" - self.kp._keystring = '10' - assert self.kp._split_count() == (10, '') - - def test_normalcount(self): - """Test split_count with count and text.""" - self.kp._keystring = '10foo' - assert self.kp._split_count() == (10, 'foo') - - def test_minuscount(self): - """Test split_count with a negative count.""" - self.kp._keystring = '-1foo' - assert self.kp._split_count() == (None, '-1foo') - - def test_expcount(self): - """Test split_count with an exponential count.""" - self.kp._keystring = '10e4foo' - assert self.kp._split_count() == (10, 'e4foo') - - def test_nocount(self): - """Test split_count with only a command.""" - self.kp._keystring = 'foo' - assert self.kp._split_count() == (None, 'foo') - - def test_nosupport(self): - """Test split_count with a count when counts aren't supported.""" - self.kp._supports_count = False - self.kp._keystring = '10foo' - assert self.kp._split_count() == (None, '10foo') + kp = basekeyparser.BaseKeyParser(0, supports_count=supports_count) + kp._keystring = input_key + assert kp._split_count() == expected @pytest.mark.usefixtures('fake_keyconfig', 'mock_timer') diff --git a/tests/misc/test_miscwidgets.py b/tests/misc/test_miscwidgets.py new file mode 100644 index 000000000..bd316935f --- /dev/null +++ b/tests/misc/test_miscwidgets.py @@ -0,0 +1,90 @@ +# 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 . + +"""Test widgets in miscwidgets module.""" + +from unittest import mock +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QApplication +import pytest + +from qutebrowser.misc.miscwidgets import CommandLineEdit + + +class TestCommandLineEdit: + + """Tests for CommandLineEdit widget.""" + + @pytest.yield_fixture + def cmd_edit(self, qtbot): + """Fixture to initialize a CommandLineEdit.""" + cmd_edit = CommandLineEdit(None) + cmd_edit.set_prompt(':') + qtbot.add_widget(cmd_edit) + assert cmd_edit.text() == '' + yield cmd_edit + + @pytest.fixture + def mock_clipboard(self, mocker): + """Fixture to mock QApplication.clipboard. + + Return: + The mocked QClipboard object. + """ + mocker.patch.object(QApplication, 'clipboard') + clipboard = mock.MagicMock() + clipboard.supportsSelection.return_value = True + QApplication.clipboard.return_value = clipboard + return clipboard + + def test_position(self, qtbot, cmd_edit): + """Test cursor position based on the prompt.""" + qtbot.keyClicks(cmd_edit, ':hello') + assert cmd_edit.text() == ':hello' + assert cmd_edit.cursorPosition() == len(':hello') + + cmd_edit.home(mark=True) + assert cmd_edit.cursorPosition() == len(':hello') + qtbot.keyClick(cmd_edit, Qt.Key_Delete) + assert cmd_edit.text() == ':' + qtbot.keyClick(cmd_edit, Qt.Key_Backspace) + assert cmd_edit.text() == ':' + + qtbot.keyClicks(cmd_edit, 'hey again') + assert cmd_edit.text() == ':hey again' + + def test_invalid_prompt(self, qtbot, cmd_edit): + """Test preventing of an invalid prompt being entered.""" + qtbot.keyClicks(cmd_edit, '$hello') + assert cmd_edit.text() == '' + + def test_clipboard_paste(self, qtbot, cmd_edit, mock_clipboard): + """Test pasting commands from clipboard.""" + mock_clipboard.text.return_value = ':command' + qtbot.keyClick(cmd_edit, Qt.Key_Insert, Qt.ShiftModifier) + assert cmd_edit.text() == ':command' + + mock_clipboard.text.return_value = ' param1' + qtbot.keyClick(cmd_edit, Qt.Key_Insert, Qt.ShiftModifier) + assert cmd_edit.text() == ':command param1' + + cmd_edit.clear() + mock_clipboard.text.return_value = '$ command' + qtbot.keyClick(cmd_edit, Qt.Key_Insert, Qt.ShiftModifier) + assert cmd_edit.text() == ':command param1' diff --git a/tests/misc/test_split.py b/tests/misc/test_split.py index c2c930b50..65fa218b1 100644 --- a/tests/misc/test_split.py +++ b/tests/misc/test_split.py @@ -18,8 +18,9 @@ # along with qutebrowser. If not, see . """Tests for qutebrowser.misc.split.""" +import collections -import unittest +import pytest from qutebrowser.misc import split @@ -29,7 +30,7 @@ from qutebrowser.misc import split # Format: input/split|output|without|keep/split|output|with|keep/ -test_data = r""" +test_data_str = r""" one two/one|two/one| two/ one "two three" four/one|two three|four/one| "two three"| four/ one 'two three' four/one|two three|four/one| 'two three'| four/ @@ -104,36 +105,56 @@ foo\ bar/foo bar/foo\ bar/ """ -class SplitTests(unittest.TestCase): +def _parse_split_test_data_str(): + """ + Parse the test data set into a namedtuple to use in tests. + + Returns: + A list of namedtuples with str attributes: input, keep, no_keep + """ + tuple_class = collections.namedtuple('TestCase', 'input, keep, no_keep') + + result = [] + for line in test_data_str.splitlines(): + if not line: + continue + data = line.split('/') + item = tuple_class(input=data[0], keep=data[1].split('|'), + no_keep=data[2].split('|')) + result.append(item) + return result + + +class TestSplit: """Test split.""" - def test_split(self): + @pytest.fixture(params=_parse_split_test_data_str()) + def split_test_case(self, request): + """Fixture to automatically parametrize all depending tests. + + It will use the test data from test_data_str, parsed using + _parse_split_test_data_str(). + """ + return request.param + + def test_split(self, split_test_case): """Test splitting.""" - for case in test_data.strip().splitlines(): - cmd, out = case.split('/')[:-2] - with self.subTest(cmd=cmd): - items = split.split(cmd) - self.assertEqual(items, out.split('|')) + items = split.split(split_test_case.input) + assert items == split_test_case.keep - def test_split_keep_original(self): + def test_split_keep_original(self, split_test_case): """Test if splitting with keep=True yields the original string.""" - for case in test_data.strip().splitlines(): - cmd = case.split('/')[0] - with self.subTest(cmd=cmd): - items = split.split(cmd, keep=True) - self.assertEqual(''.join(items), cmd) + items = split.split(split_test_case.input, keep=True) + assert ''.join(items) == split_test_case.input - def test_split_keep(self): + def test_split_keep(self, split_test_case): """Test splitting with keep=True.""" - for case in test_data.strip().splitlines(): - cmd, _mid, out = case.split('/')[:-1] - with self.subTest(cmd=cmd): - items = split.split(cmd, keep=True) - self.assertEqual(items, out.split('|')) + items = split.split(split_test_case.input, keep=True) + assert items == split_test_case.no_keep -class SimpleSplitTests(unittest.TestCase): +class TestSimpleSplit: """Test simple_split.""" @@ -145,27 +166,20 @@ class SimpleSplitTests(unittest.TestCase): 'foo\nbar': ['foo', '\nbar'], } - def test_str_split(self): + @pytest.mark.parametrize('test', TESTS) + def test_str_split(self, test): """Test if the behavior matches str.split.""" - for test in self.TESTS: - with self.subTest(string=test): - self.assertEqual(split.simple_split(test), - test.rstrip().split()) + assert split.simple_split(test) == test.rstrip().split() - def test_str_split_maxsplit_1(self): - """Test if the behavior matches str.split with maxsplit=1.""" - string = "foo bar baz" - self.assertEqual(split.simple_split(string, maxsplit=1), - string.rstrip().split(maxsplit=1)) + @pytest.mark.parametrize('s, maxsplit', + [("foo bar baz", 1), (" foo bar baz ", 0)]) + def test_str_split_maxsplit(self, s, maxsplit): + """Test if the behavior matches str.split with given maxsplit.""" + actual = split.simple_split(s, maxsplit=maxsplit) + expected = s.rstrip().split(maxsplit=maxsplit) + assert actual == expected - def test_str_split_maxsplit_0(self): - """Test if the behavior matches str.split with maxsplit=0.""" - string = " foo bar baz " - self.assertEqual(split.simple_split(string, maxsplit=0), - string.rstrip().split(maxsplit=0)) - - def test_split_keep(self): + @pytest.mark.parametrize('test, expected', TESTS.items()) + def test_split_keep(self, test, expected): """Test splitting with keep=True.""" - for test, expected in self.TESTS.items(): - with self.subTest(string=test): - self.assertEqual(split.simple_split(test, keep=True), expected) + assert split.simple_split(test, keep=True) == expected diff --git a/tests/utils/overflow_test_cases.py b/tests/utils/overflow_test_cases.py new file mode 100644 index 000000000..08e6ae7a8 --- /dev/null +++ b/tests/utils/overflow_test_cases.py @@ -0,0 +1,70 @@ +# 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 . + +""" +Provides test data for overflow checking. + +Module attributes: + INT32_MIN: Minimum valid value for a signed int32. + INT32_MAX: Maximum valid value for a signed int32. + INT64_MIN: Minimum valid value for a signed int64. + INT64_MAX: Maximum valid value for a signed int64. + GOOD_VALUES: A dict of types mapped to a list of good values. + BAD_VALUES: A dict of types mapped to a list of bad values. +""" + +INT32_MIN = -(2 ** 31) +INT32_MAX = 2 ** 31 - 1 +INT64_MIN = -(2 ** 63) +INT64_MAX = 2 ** 63 - 1 + +GOOD_VALUES = { + 'int': [-1, 0, 1, 23.42, INT32_MIN, INT32_MAX], + 'int64': [-1, 0, 1, 23.42, INT64_MIN, INT64_MAX], +} + +BAD_VALUES = { + 'int': [(INT32_MIN - 1, INT32_MIN), + (INT32_MAX + 1, INT32_MAX), + (float(INT32_MAX + 1), INT32_MAX)], + 'int64': [(INT64_MIN - 1, INT64_MIN), + (INT64_MAX + 1, INT64_MAX), + (float(INT64_MAX + 1), INT64_MAX)], +} + + +def iter_good_values(): + """Yield "good" (C data type, value) tuples. + + Those should pass overflow checking. + """ + for ctype, values in GOOD_VALUES.items(): + for value in values: + yield ctype, value + + +def iter_bad_values(): + """Yield pairs of "bad" (C type, value, repl) tuples. + + Theose should not pass overflow checking. The third value is the value they + should be replaced with if overflow checking should not be fatal. + """ + for ctype, values in BAD_VALUES.items(): + for value, repl in values: + yield ctype, value, repl diff --git a/tests/utils/test_jinja.py b/tests/utils/test_jinja.py index eee703cdf..174a44df2 100644 --- a/tests/utils/test_jinja.py +++ b/tests/utils/test_jinja.py @@ -20,47 +20,42 @@ """Tests for qutebrowser.utils.jinja.""" import os.path -import unittest -import unittest.mock + +import pytest from qutebrowser.utils import jinja -def _read_file(path): - """Mocked utils.read_file.""" - if path == os.path.join('html', 'test.html'): - return """Hello {{var}}""" - else: - raise ValueError("Invalid path {}!".format(path)) +@pytest.fixture(autouse=True) +def patch_read_file(monkeypatch): + """pytest fixture to patch utils.read_file.""" + def _read_file(path): + """A read_file which returns a simple template if the path is right.""" + if path == os.path.join('html', 'test.html'): + return """Hello {{var}}""" + else: + raise ValueError("Invalid path {}!".format(path)) + + monkeypatch.setattr('qutebrowser.utils.jinja.utils.read_file', _read_file) -@unittest.mock.patch('qutebrowser.utils.jinja.utils.read_file') -class JinjaTests(unittest.TestCase): - - """Tests for getting template via jinja.""" - - def test_simple_template(self, readfile_mock): - """Test with a simple template.""" - readfile_mock.side_effect = _read_file - template = jinja.env.get_template('test.html') - # https://bitbucket.org/logilab/pylint/issue/490/ - data = template.render(var='World') # pylint: disable=no-member - self.assertEqual(data, "Hello World") - - def test_utf8(self, readfile_mock): - """Test rendering with an UTF8 template. - - This was an attempt to get a failing test case for #127 but it seems - the issue is elsewhere. - - https://github.com/The-Compiler/qutebrowser/issues/127 - """ - readfile_mock.side_effect = _read_file - template = jinja.env.get_template('test.html') - # https://bitbucket.org/logilab/pylint/issue/490/ - data = template.render(var='\u2603') # pylint: disable=no-member - self.assertEqual(data, "Hello \u2603") +def test_simple_template(): + """Test with a simple template.""" + template = jinja.env.get_template('test.html') + # https://bitbucket.org/logilab/pylint/issue/490/ + data = template.render(var='World') # pylint: disable=no-member + assert data == "Hello World" -if __name__ == '__main__': - unittest.main() +def test_utf8(): + """Test rendering with an UTF8 template. + + This was an attempt to get a failing test case for #127 but it seems + the issue is elsewhere. + + https://github.com/The-Compiler/qutebrowser/issues/127 + """ + template = jinja.env.get_template('test.html') + # https://bitbucket.org/logilab/pylint/issue/490/ + data = template.render(var='\u2603') # pylint: disable=no-member + assert data == "Hello \u2603" diff --git a/tests/utils/test_qtutils.py b/tests/utils/test_qtutils.py index 582abbf07..85a99c2f4 100644 --- a/tests/utils/test_qtutils.py +++ b/tests/utils/test_qtutils.py @@ -20,107 +20,74 @@ """Tests for qutebrowser.utils.qtutils.""" import sys -import unittest + +import pytest from qutebrowser import qutebrowser from qutebrowser.utils import qtutils +import overflow_test_cases -class CheckOverflowTests(unittest.TestCase): +class TestCheckOverflow: - """Test check_overflow. + """Test check_overflow.""" - Class attributes: - INT32_MIN: Minimum valid value for a signed int32. - INT32_MAX: Maximum valid value for a signed int32. - INT64_MIN: Minimum valid value for a signed int64. - INT64_MAX: Maximum valid value for a signed int64. - GOOD_VALUES: A dict of types mapped to a list of good values. - BAD_VALUES: A dict of types mapped to a list of bad values. - """ - - INT32_MIN = -(2 ** 31) - INT32_MAX = 2 ** 31 - 1 - INT64_MIN = -(2 ** 63) - INT64_MAX = 2 ** 63 - 1 - - GOOD_VALUES = { - 'int': [-1, 0, 1, 23.42, INT32_MIN, INT32_MAX], - 'int64': [-1, 0, 1, 23.42, INT64_MIN, INT64_MAX], - } - - BAD_VALUES = { - 'int': [(INT32_MIN - 1, INT32_MIN), - (INT32_MAX + 1, INT32_MAX), - (float(INT32_MAX + 1), INT32_MAX)], - 'int64': [(INT64_MIN - 1, INT64_MIN), - (INT64_MAX + 1, INT64_MAX), - (float(INT64_MAX + 1), INT64_MAX)], - } - - def test_good_values(self): + @pytest.mark.parametrize('ctype, val', + overflow_test_cases.iter_good_values()) + def test_good_values(self, ctype, val): """Test values which are inside bounds.""" - for ctype, vals in self.GOOD_VALUES.items(): - for val in vals: - with self.subTest(ctype=ctype, val=val): - qtutils.check_overflow(val, ctype) + qtutils.check_overflow(val, ctype) - def test_bad_values_fatal(self): + @pytest.mark.parametrize('ctype, val', + [(ctype, val) for (ctype, val, _) in + overflow_test_cases.iter_bad_values()]) + def test_bad_values_fatal(self, ctype, val): """Test values which are outside bounds with fatal=True.""" - for ctype, vals in self.BAD_VALUES.items(): - for (val, _) in vals: - with self.subTest(ctype=ctype, val=val): - with self.assertRaises(OverflowError): - qtutils.check_overflow(val, ctype) + with pytest.raises(OverflowError): + qtutils.check_overflow(val, ctype) - def test_bad_values_nonfatal(self): + @pytest.mark.parametrize('ctype, val, repl', + overflow_test_cases.iter_bad_values()) + def test_bad_values_nonfatal(self, ctype, val, repl): """Test values which are outside bounds with fatal=False.""" - for ctype, vals in self.BAD_VALUES.items(): - for (val, replacement) in vals: - with self.subTest(ctype=ctype, val=val): - newval = qtutils.check_overflow(val, ctype, fatal=False) - self.assertEqual(newval, replacement) + newval = qtutils.check_overflow(val, ctype, fatal=False) + assert newval == repl -def argparser_exit(status=0, message=None): # pylint: disable=unused-argument - """Function to monkey-patch .exit() of the argparser so it doesn't exit.""" - raise Exception - - -class GetQtArgsTests(unittest.TestCase): +class TestGetQtArgs: """Tests for get_args.""" - def setUp(self): - self.parser = qutebrowser.get_argparser() - self.parser.exit = argparser_exit + @pytest.fixture + def parser(self, mocker): + """Fixture to provide an argparser. - def test_no_qt_args(self): + Monkey-patches .exit() of the argparser so it doesn't exit on errors. + """ + parser = qutebrowser.get_argparser() + mocker.patch.object(parser, 'exit', side_effect=Exception) + return parser + + def test_no_qt_args(self, parser): """Test commandline with no Qt arguments given.""" - args = self.parser.parse_args(['--debug']) - self.assertEqual(qtutils.get_args(args), [sys.argv[0]]) + args = parser.parse_args(['--debug']) + assert qtutils.get_args(args) == [sys.argv[0]] - def test_qt_flag(self): + def test_qt_flag(self, parser): """Test commandline with a Qt flag.""" - args = self.parser.parse_args(['--debug', '--qt-reverse', '--nocolor']) - self.assertEqual(qtutils.get_args(args), [sys.argv[0], '-reverse']) + args = parser.parse_args(['--debug', '--qt-reverse', '--nocolor']) + assert qtutils.get_args(args) == [sys.argv[0], '-reverse'] - def test_qt_arg(self): + def test_qt_arg(self, parser): """Test commandline with a Qt argument.""" - args = self.parser.parse_args(['--qt-stylesheet', 'foobar']) - self.assertEqual(qtutils.get_args(args), [sys.argv[0], '-stylesheet', - 'foobar']) + args = parser.parse_args(['--qt-stylesheet', 'foobar']) + assert qtutils.get_args(args) == [sys.argv[0], '-stylesheet', 'foobar'] - def test_qt_both(self): + def test_qt_both(self, parser): """Test commandline with a Qt argument and flag.""" - args = self.parser.parse_args(['--qt-stylesheet', 'foobar', - '--qt-reverse']) + args = parser.parse_args(['--qt-stylesheet', 'foobar', '--qt-reverse']) qt_args = qtutils.get_args(args) - self.assertEqual(qt_args[0], sys.argv[0]) - self.assertIn('-reverse', qt_args) - self.assertIn('-stylesheet', qt_args) - self.assertIn('foobar', qt_args) - - -if __name__ == '__main__': - unittest.main() + assert qt_args[0] == sys.argv[0] + assert '-reverse' in qt_args + assert '-stylesheet' in qt_args + assert 'foobar' in qt_args diff --git a/tox.ini b/tox.ini index 101d2b02c..d1e337271 100644 --- a/tox.ini +++ b/tox.ini @@ -26,7 +26,7 @@ deps = # on Ubuntu Trusty. commands = {envpython} scripts/link_pyqt.py --tox {envdir} - {envpython} -m py.test {posargs} + {envpython} -m py.test --strict {posargs} [testenv:coverage] deps = @@ -36,7 +36,7 @@ deps = cov-core==1.15.0 commands = {[testenv:mkvenv]commands} - {envpython} -m py.test --cov qutebrowser --cov-report term --cov-report html {posargs} + {envpython} -m py.test --strict --cov qutebrowser --cov-report term --cov-report html {posargs} [testenv:misc] commands = @@ -109,3 +109,5 @@ commands = [pytest] norecursedirs = .tox .venv +markers = + gui: Tests using the GUI (e.g. spawning widgets)