diff --git a/.pylintrc b/.pylintrc index daa68e14a..2cc56909d 100644 --- a/.pylintrc +++ b/.pylintrc @@ -40,6 +40,7 @@ argument-rgx=[a-z_][a-z0-9_]{0,30}$ variable-rgx=[a-z_][a-z0-9_]{0,30}$ class-attribute-rgx=[A-Za-z_][A-Za-z0-9_]{1,30}$ inlinevar-rgx=[a-z_][a-z0-9_]*$ +docstring-min-length=2 [FORMAT] max-line-length=79 diff --git a/MANIFEST.in b/MANIFEST.in index 681ca170b..988d530b6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -6,7 +6,7 @@ graft icons graft scripts/pylint_checkers graft doc/img graft misc -include qutebrowser/test/testfile +include qutebrowser/utils/testfile include qutebrowser/git-commit-id include COPYING doc/* README.asciidoc CONTRIBUTING.asciidoc FAQ.asciidoc INSTALL.asciidoc CHANGELOG.asciidoc include qutebrowser.desktop @@ -23,7 +23,7 @@ exclude scripts/quit_segfault_test.sh exclude scripts/segfault_test.sh exclude doc/notes recursive-exclude doc *.asciidoc -prune test +prune tests exclude qutebrowser.rcc exclude .coveragerc exclude .flake8 diff --git a/README.asciidoc b/README.asciidoc index e686ea522..0c0bdad0e 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -134,6 +134,7 @@ Contributors, sorted by the number of commits in descending order: // QUTE_AUTHORS_START * Florian Bruhin +* Bruno Oliveira * Joel Torstensson * Raphael Pierzina * Claude diff --git a/qutebrowser/test/browser/__init__.py b/qutebrowser/test/browser/__init__.py deleted file mode 100644 index 801293531..000000000 --- a/qutebrowser/test/browser/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# 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 the qutebrowser.browser package.""" diff --git a/qutebrowser/test/browser/http/__init__.py b/qutebrowser/test/browser/http/__init__.py deleted file mode 100644 index e1643dbb8..000000000 --- a/qutebrowser/test/browser/http/__init__.py +++ /dev/null @@ -1,20 +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 . - -"""Tests for the qutebrowser.browser.http module.""" diff --git a/qutebrowser/test/browser/test_tabhistory.py b/qutebrowser/test/browser/test_tabhistory.py deleted file mode 100644 index 1714894b5..000000000 --- a/qutebrowser/test/browser/test_tabhistory.py +++ /dev/null @@ -1,140 +0,0 @@ -# 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 webelement.tabhistory.""" - -import unittest - -from PyQt5.QtCore import QUrl, QPoint - -from qutebrowser.browser import tabhistory -from qutebrowser.browser.tabhistory import TabHistoryItem as Item -from qutebrowser.utils import qtutils -from qutebrowser.test import helpers - - -class SerializeHistoryTests(unittest.TestCase): - - """Tests for serialize().""" - - def setUp(self): - self.page = helpers.get_webpage() - self.history = self.page.history() - self.assertEqual(self.history.count(), 0) - - self.items = [ - Item(QUrl('https://www.heise.de/'), QUrl('http://www.heise.de/'), - 'heise'), - Item(QUrl('http://example.com/%E2%80%A6'), - QUrl('http://example.com/%E2%80%A6'), 'percent', active=True), - Item(QUrl('http://example.com/?foo=bar'), - QUrl('http://original.url.example.com/'), 'arg', - user_data={'foo': 23, 'bar': 42}), - # From https://github.com/OtterBrowser/otter-browser/issues/709#issuecomment-74749471 - Item(QUrl('http://github.com/OtterBrowser/24/134/2344/otter-browser/issues/709/'), - QUrl('http://github.com/OtterBrowser/24/134/2344/otter-browser/issues/709/'), - 'Page not found | github', - user_data={'zoom': 149, 'scroll-pos': QPoint(0, 0)}), - Item(QUrl('https://mail.google.com/mail/u/0/#label/some+label/234lkjsd0932lkjf884jqwerdf4'), - QUrl('https://mail.google.com/mail/u/0/#label/some+label/234lkjsd0932lkjf884jqwerdf4'), - '"some label" - email@gmail.com - Gmail"', - user_data={'zoom': 120, 'scroll-pos': QPoint(0, 0)}), - ] - stream, _data, self.user_data = tabhistory.serialize(self.items) - qtutils.deserialize_stream(stream, self.history) - - def test_count(self): - """Check if the history's count was loaded correctly.""" - with helpers.disable_logger('qt'): - self.assertEqual(self.history.count(), len(self.items)) - - def test_valid(self): - """Check if all items are valid.""" - for i, _item in enumerate(self.items): - self.assertTrue(self.history.itemAt(i).isValid()) - - def test_no_userdata(self): - """Check if all items have no user data.""" - for i, _item in enumerate(self.items): - self.assertIsNone(self.history.itemAt(i).userData()) - - def test_userdata(self): - """Check if all user data has been restored to self.user_data.""" - for item, user_data in zip(self.items, self.user_data): - self.assertEqual(user_data, item.user_data) - - def test_currentitem(self): - """Check if the current item index was loaded correctly.""" - self.assertEqual(self.history.currentItemIndex(), 1) - - def test_urls(self): - """Check if the URLs were loaded correctly.""" - for i, item in enumerate(self.items): - with self.subTest(i=i, item=item): - self.assertEqual(self.history.itemAt(i).url(), item.url) - - def test_original_urls(self): - """Check if the original URLs were loaded correctly.""" - for i, item in enumerate(self.items): - with self.subTest(i=i, item=item): - self.assertEqual(self.history.itemAt(i).originalUrl(), - item.original_url) - - def test_titles(self): - """Check if the titles were loaded correctly.""" - for i, item in enumerate(self.items): - with self.subTest(i=i, item=item): - self.assertEqual(self.history.itemAt(i).title(), item.title) - - -class SerializeHistorySpecialTests(unittest.TestCase): - - """Tests for serialize() without items set up in setUp.""" - - def setUp(self): - self.page = helpers.get_webpage() - self.history = self.page.history() - self.assertEqual(self.history.count(), 0) - - def test_no_active_item(self): - """Check tabhistory.serialize with no active item.""" - items = [Item(QUrl(), QUrl(), '')] - with self.assertRaises(ValueError): - tabhistory.serialize(items) - - def test_two_active_items(self): - """Check tabhistory.serialize with two active items.""" - items = [Item(QUrl(), QUrl(), '', active=True), - Item(QUrl(), QUrl(), ''), - Item(QUrl(), QUrl(), '', active=True)] - with self.assertRaises(ValueError): - tabhistory.serialize(items) - - def test_empty(self): - """Check tabhistory.serialize with no items.""" - items = [] - stream, _data, user_data = tabhistory.serialize(items) - qtutils.deserialize_stream(stream, self.history) - self.assertEqual(self.history.count(), 0) - self.assertEqual(self.history.currentItemIndex(), 0) - self.assertFalse(user_data) - - -if __name__ == '__main__': - unittest.main() diff --git a/qutebrowser/test/config/__init__.py b/qutebrowser/test/config/__init__.py deleted file mode 100644 index e55e9df60..000000000 --- a/qutebrowser/test/config/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for qutebrowser.config.""" diff --git a/qutebrowser/test/helpers.py b/qutebrowser/test/helpers.py deleted file mode 100644 index 23ef2b25e..000000000 --- a/qutebrowser/test/helpers.py +++ /dev/null @@ -1,109 +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 . - -"""Helpers needed by tests.""" - -import os -import logging -import contextlib -from unittest import mock - -from PyQt5.QtGui import QKeyEvent -from PyQt5.QtWebKitWidgets import QWebPage -from PyQt5.QtNetwork import QNetworkAccessManager - - -unicode_encode_err = UnicodeEncodeError('ascii', # codec - '', # object - 0, # start - 2, # end - 'fake exception') # reason - - -@contextlib.contextmanager -def environ_set_temp(env): - """Set temporary environment variables. - - Args: - env: A dictionary with name: value pairs. - If value is None, the variable is temporarily deleted. - """ - old_env = {} - - for name, value in env.items(): - try: - old_env[name] = os.environ[name] - except KeyError: - pass - if value is None: - os.environ.pop(name, None) - else: - os.environ[name] = value - - yield - - for name, value in env.items(): - if name in old_env: - os.environ[name] = old_env[name] - elif value is not None: - del os.environ[name] - - -@contextlib.contextmanager -def disable_logger(name): - """Temporarily disable a logger.""" - logging.getLogger(name).propagate = False - try: - yield - finally: - logging.getLogger(name).propagate = True - - -def fake_keyevent(key, modifiers=0, text=''): - """Generate a new fake QKeyPressEvent.""" - evtmock = mock.create_autospec(QKeyEvent, instance=True) - evtmock.key.return_value = key - evtmock.modifiers.return_value = modifiers - evtmock.text.return_value = text - return evtmock - - -def get_webpage(): - """Get a new QWebPage object.""" - page = QWebPage() - nam = page.networkAccessManager() - nam.setNetworkAccessible(QNetworkAccessManager.NotAccessible) - return page - - -class MessageModule: - - """A drop-in replacement for qutebrowser.utils.message.""" - - def error(self, _win_id, message, _immediately=False): - """Log an error to the message logger.""" - logging.getLogger('message').error(message) - - def warning(self, _win_id, message, _immediately=False): - """Log a warning to the message logger.""" - logging.getLogger('message').warning(message) - - def info(self, _win_id, message, _immediately=True): - """Log an info message to the message logger.""" - logging.getLogger('message').info(message) diff --git a/qutebrowser/test/keyinput/__init__.py b/qutebrowser/test/keyinput/__init__.py deleted file mode 100644 index 793f3d99b..000000000 --- a/qutebrowser/test/keyinput/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Tests for qutebrowser.keyinput.""" diff --git a/qutebrowser/test/keyinput/test_basekeyparser.py b/qutebrowser/test/keyinput/test_basekeyparser.py deleted file mode 100644 index eb80aa07c..000000000 --- a/qutebrowser/test/keyinput/test_basekeyparser.py +++ /dev/null @@ -1,299 +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 . - -# pylint: disable=protected-access - -"""Tests for BaseKeyParser.""" - -import logging -import unittest -from unittest import mock - -from PyQt5.QtCore import Qt - -from qutebrowser.keyinput import basekeyparser -from qutebrowser.test import stubs, helpers -from qutebrowser.utils import objreg, log - - -CONFIG = {'input': {'timeout': 100}} - - -BINDINGS = {'test': {'': 'ctrla', - 'a': 'a', - 'ba': 'ba', - 'ax': 'ax', - 'ccc': 'ccc'}, - 'test2': {'foo': 'bar', '': 'ctrlx'}} - - -fake_keyconfig = mock.Mock(spec=['get_bindings_for']) -fake_keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s] - - -class SplitCountTests(unittest.TestCase): - - """Test the _split_count method. - - Attributes: - kp: The BaseKeyParser we're testing. - """ - - def setUp(self): - self.kp = basekeyparser.BaseKeyParser(0, supports_count=True) - - def test_onlycount(self): - """Test split_count with only a count.""" - self.kp._keystring = '10' - self.assertEqual(self.kp._split_count(), (10, '')) - - def test_normalcount(self): - """Test split_count with count and text.""" - self.kp._keystring = '10foo' - self.assertEqual(self.kp._split_count(), (10, 'foo')) - - def test_minuscount(self): - """Test split_count with a negative count.""" - self.kp._keystring = '-1foo' - self.assertEqual(self.kp._split_count(), (None, '-1foo')) - - def test_expcount(self): - """Test split_count with an exponential count.""" - self.kp._keystring = '10e4foo' - self.assertEqual(self.kp._split_count(), (10, 'e4foo')) - - def test_nocount(self): - """Test split_count with only a command.""" - self.kp._keystring = 'foo' - self.assertEqual(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' - self.assertEqual(self.kp._split_count(), (None, '10foo')) - - -@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer', - new=stubs.FakeTimer) -class ReadConfigTests(unittest.TestCase): - - """Test reading the config.""" - - def setUp(self): - objreg.register('key-config', fake_keyconfig) - - def tearDown(self): - objreg.delete('key-config') - - def test_read_config_invalid(self): - """Test reading config without setting it before.""" - kp = basekeyparser.BaseKeyParser(0) - with self.assertRaises(ValueError): - kp.read_config() - - def test_read_config_valid(self): - """Test reading config.""" - kp = basekeyparser.BaseKeyParser(0, supports_count=True, - supports_chains=True) - kp.read_config('test') - self.assertIn('ccc', kp.bindings) - self.assertIn('ctrl+a', kp.special_bindings) - kp.read_config('test2') - self.assertNotIn('ccc', kp.bindings) - self.assertNotIn('ctrl+a', kp.special_bindings) - self.assertIn('foo', kp.bindings) - self.assertIn('ctrl+x', kp.special_bindings) - - -@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer', - new=stubs.FakeTimer) -class SpecialKeysTests(unittest.TestCase): - - """Check execute() with special keys. - - Attributes: - kp: The BaseKeyParser to be tested. - """ - - def setUp(self): - objreg.register('key-config', fake_keyconfig) - self.kp = basekeyparser.BaseKeyParser(0) - self.kp.execute = mock.Mock() - with self.assertLogs(log.keyboard, logging.WARNING): - # Ignoring keychain 'ccc' in mode 'test' because keychains are not - # supported there. - self.kp.read_config('test') - - def tearDown(self): - objreg.delete('key-config') - - def test_valid_key(self): - """Test a valid special keyevent.""" - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, Qt.ControlModifier)) - self.kp.handle(helpers.fake_keyevent(Qt.Key_X, Qt.ControlModifier)) - self.kp.execute.assert_called_once_with('ctrla', self.kp.Type.special) - - def test_invalid_key(self): - """Test an invalid special keyevent.""" - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, (Qt.ControlModifier | - Qt.AltModifier))) - self.assertFalse(self.kp.execute.called) - - def test_keychain(self): - """Test a keychain.""" - self.kp.handle(helpers.fake_keyevent(Qt.Key_B)) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A)) - self.assertFalse(self.kp.execute.called) - - -@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer', - new=stubs.FakeTimer) -class KeyChainTests(unittest.TestCase): - - """Test execute() with keychain support. - - Attributes: - kp: The BaseKeyParser to be tested. - """ - - def setUp(self): - """Set up mocks and read the test config.""" - objreg.register('key-config', fake_keyconfig) - self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True, - supports_count=False) - self.kp.execute = mock.Mock() - self.kp.read_config('test') - - def tearDown(self): - objreg.delete('key-config') - - def test_valid_special_key(self): - """Test valid special key.""" - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, Qt.ControlModifier)) - self.kp.handle(helpers.fake_keyevent(Qt.Key_X, Qt.ControlModifier)) - self.kp.execute.assert_called_once_with('ctrla', self.kp.Type.special) - self.assertEqual(self.kp._keystring, '') - - def test_invalid_special_key(self): - """Test invalid special key.""" - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, (Qt.ControlModifier | - Qt.AltModifier))) - self.assertFalse(self.kp.execute.called) - self.assertEqual(self.kp._keystring, '') - - def test_keychain(self): - """Test valid keychain.""" - # Press 'x' which is ignored because of no match - self.kp.handle(helpers.fake_keyevent(Qt.Key_X, text='x')) - # Then start the real chain - self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a')) - self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None) - self.assertEqual(self.kp._keystring, '') - - @mock.patch('qutebrowser.keyinput.basekeyparser.config', - new=stubs.ConfigStub(CONFIG)) - def test_ambiguous_keychain(self): - """Test ambiguous keychain.""" - timer = self.kp._ambiguous_timer - self.assertFalse(timer.isActive()) - # We start with 'a' where the keychain gives us an ambiguous result. - # Then we check if the timer has been set up correctly - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a')) - self.assertFalse(self.kp.execute.called) - self.assertTrue(timer.isSingleShot()) - self.assertEqual(timer.interval(), 100) - self.assertTrue(timer.isActive()) - # Now we type an 'x' and check 'ax' has been executed and the timer - # stopped. - self.kp.handle(helpers.fake_keyevent(Qt.Key_X, text='x')) - self.kp.execute.assert_called_once_with('ax', self.kp.Type.chain, None) - self.assertFalse(timer.isActive()) - self.assertEqual(self.kp._keystring, '') - - def test_invalid_keychain(self): - """Test invalid keychain.""" - self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_C, text='c')) - self.assertEqual(self.kp._keystring, '') - - -@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer', - new=stubs.FakeTimer) -class CountTests(unittest.TestCase): - - """Test execute() with counts.""" - - def setUp(self): - objreg.register('key-config', fake_keyconfig) - self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True, - supports_count=True) - self.kp.execute = mock.Mock() - self.kp.read_config('test') - - def test_no_count(self): - """Test with no count added.""" - self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a')) - self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None) - self.assertEqual(self.kp._keystring, '') - - def test_count_0(self): - """Test with count=0.""" - self.kp.handle(helpers.fake_keyevent(Qt.Key_0, text='0')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a')) - self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, 0) - self.assertEqual(self.kp._keystring, '') - - def test_count_42(self): - """Test with count=42.""" - self.kp.handle(helpers.fake_keyevent(Qt.Key_4, text='4')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_2, text='2')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a')) - self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, 42) - self.assertEqual(self.kp._keystring, '') - - def test_count_42_invalid(self): - """Test with count=42 and invalid command.""" - # Invalid call with ccx gets ignored - self.kp.handle(helpers.fake_keyevent(Qt.Key_4, text='4')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_2, text='2')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='c')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='c')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='x')) - self.assertFalse(self.kp.execute.called) - self.assertEqual(self.kp._keystring, '') - # Valid call with ccc gets the correct count - self.kp.handle(helpers.fake_keyevent(Qt.Key_4, text='2')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_2, text='3')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='c')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='c')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='c')) - self.kp.execute.assert_called_once_with('ccc', self.kp.Type.chain, 23) - self.assertEqual(self.kp._keystring, '') - - def tearDown(self): - objreg.global_registry.clear() - - -if __name__ == '__main__': - unittest.main() diff --git a/qutebrowser/test/misc/__init__.py b/qutebrowser/test/misc/__init__.py deleted file mode 100644 index 667e8201d..000000000 --- a/qutebrowser/test/misc/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# 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 . - -"""The qutebrowser test suite.""" diff --git a/qutebrowser/test/test_helpers.py b/qutebrowser/test/test_helpers.py deleted file mode 100644 index 13e224374..000000000 --- a/qutebrowser/test/test_helpers.py +++ /dev/null @@ -1,84 +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 . - - -"""Test test helpers.""" - -import os -import unittest - -from qutebrowser.test import helpers - - -class TestEnvironSetTemp(unittest.TestCase): - - """Test the environ_set_temp helper.""" - - def test_environ_set(self): - """Test environ_set_temp with something which was set already.""" - os.environ['QUTEBROWSER_ENVIRON_TEST'] = 'oldval' - with helpers.environ_set_temp({'QUTEBROWSER_ENVIRON_TEST': 'newval'}): - self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST'], 'newval') - self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST'], 'oldval') - - def test_environ_unset(self): - """Test environ_set_temp with something which wasn't set yet.""" - with helpers.environ_set_temp({'QUTEBROWSER_ENVIRON_TEST': 'newval'}): - self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST'], 'newval') - self.assertNotIn('QUTEBROWSER_ENVIRON_TEST', os.environ) - - def test_environ_multiple(self): - """Test environ_set_temp with multiple values.""" - os.environ['QUTEBROWSER_ENVIRON_TEST_1'] = 'oldval_1' - os.environ['QUTEBROWSER_ENVIRON_TEST_3'] = 'oldval_3' - env = { - 'QUTEBROWSER_ENVIRON_TEST_1': 'newval_1', - 'QUTEBROWSER_ENVIRON_TEST_2': 'newval_2', - 'QUTEBROWSER_ENVIRON_TEST_3': None, - } - with helpers.environ_set_temp(env): - self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST_1'], - 'newval_1') - self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST_2'], - 'newval_2') - self.assertNotIn('QUTEBROWSER_ENVIRON_TEST_3', os.environ) - self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST_1'], 'oldval_1') - self.assertNotIn('QUTEBROWSER_ENVIRON_TEST_2', os.environ) - self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST_3'], 'oldval_3') - - def test_environ_none_set(self): - """Test environ_set_temp with something which was set already.""" - os.environ['QUTEBROWSER_ENVIRON_TEST'] = 'oldval' - with helpers.environ_set_temp({'QUTEBROWSER_ENVIRON_TEST': None}): - self.assertNotIn('QUTEBROWSER_ENVIRON_TEST', os.environ) - self.assertEqual(os.environ['QUTEBROWSER_ENVIRON_TEST'], 'oldval') - - def test_environ_none_unset(self): - """Test environ_set_temp with something which wasn't set yet.""" - with helpers.environ_set_temp({'QUTEBROWSER_ENVIRON_TEST': None}): - self.assertNotIn('QUTEBROWSER_ENVIRON_TEST', os.environ) - self.assertNotIn('QUTEBROWSER_ENVIRON_TEST', os.environ) - - def tearDown(self): - if 'QUTEBROWSER_ENVIRON_TEST' in os.environ: - # if some test failed - del os.environ['QUTEBROWSER_ENVIRON_TEST'] - -if __name__ == '__main__': - unittest.main() diff --git a/qutebrowser/test/test_stubs.py b/qutebrowser/test/test_stubs.py deleted file mode 100644 index 476c43774..000000000 --- a/qutebrowser/test/test_stubs.py +++ /dev/null @@ -1,107 +0,0 @@ -# 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 . - - -"""Test test stubs.""" - -import unittest -from unittest import mock - -from qutebrowser.test import stubs - - -class TestFakeTimer(unittest.TestCase): - - """Test FakeTimer.""" - - def setUp(self): - self.timer = stubs.FakeTimer() - - def test_timeout(self): - """Test whether timeout calls the functions.""" - func = mock.Mock() - func2 = mock.Mock() - self.timer.timeout.connect(func) - self.timer.timeout.connect(func2) - self.assertFalse(func.called) - self.assertFalse(func2.called) - self.timer.timeout.emit() - func.assert_called_once_with() - func2.assert_called_once_with() - - def test_disconnect_all(self): - """Test disconnect without arguments.""" - func = mock.Mock() - self.timer.timeout.connect(func) - self.timer.timeout.disconnect() - self.timer.timeout.emit() - self.assertFalse(func.called) - - def test_disconnect_one(self): - """Test disconnect with a single argument.""" - func = mock.Mock() - self.timer.timeout.connect(func) - self.timer.timeout.disconnect(func) - self.timer.timeout.emit() - self.assertFalse(func.called) - - def test_disconnect_all_invalid(self): - """Test disconnecting with no connections.""" - with self.assertRaises(TypeError): - self.timer.timeout.disconnect() - - def test_disconnect_one_invalid(self): - """Test disconnecting with an invalid connection.""" - func1 = mock.Mock() - func2 = mock.Mock() - self.timer.timeout.connect(func1) - with self.assertRaises(TypeError): - self.timer.timeout.disconnect(func2) - self.assertFalse(func1.called) - self.assertFalse(func2.called) - self.timer.timeout.emit() - func1.assert_called_once_with() - - def test_singleshot(self): - """Test setting singleShot.""" - self.assertFalse(self.timer.singleShot()) - self.timer.setSingleShot(True) - self.assertTrue(self.timer.singleShot()) - self.timer.start() - self.assertTrue(self.timer.isActive()) - self.timer.timeout.emit() - self.assertFalse(self.timer.isActive()) - - def test_active(self): - """Test isActive.""" - self.assertFalse(self.timer.isActive()) - self.timer.start() - self.assertTrue(self.timer.isActive()) - self.timer.stop() - self.assertFalse(self.timer.isActive()) - - def test_interval(self): - """Test setting an interval.""" - self.assertEqual(self.timer.interval(), 0) - self.timer.setInterval(1000) - self.assertEqual(self.timer.interval(), 1000) - - -if __name__ == '__main__': - unittest.main() diff --git a/qutebrowser/test/utils/__init__.py b/qutebrowser/test/utils/__init__.py deleted file mode 100644 index 292d89a9d..000000000 --- a/qutebrowser/test/utils/__init__.py +++ /dev/null @@ -1,20 +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 . - -"""Tests for the qutebrowser.utils package.""" diff --git a/qutebrowser/test/utils/test_standarddir.py b/qutebrowser/test/utils/test_standarddir.py deleted file mode 100644 index 3dfa3fe98..000000000 --- a/qutebrowser/test/utils/test_standarddir.py +++ /dev/null @@ -1,138 +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 . - -"""Tests for qutebrowser.utils.standarddir.""" - -import os -import os.path -import sys -import shutil -import unittest -import tempfile - -from qutebrowser.utils import standarddir -from qutebrowser.test import helpers, qApp - - -class GetStandardDirLinuxTests(unittest.TestCase): - - """Tests for standarddir under Linux. - - Attributes: - temp_dir: A temporary directory. - old_name: The old applicationName. - """ - - def setUp(self): - self.temp_dir = tempfile.mkdtemp() - self.old_name = qApp.applicationName() - qApp.setApplicationName('qutebrowser') - - @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") - def test_data_explicit(self): - """Test data dir with XDG_DATA_HOME explicitly set.""" - with helpers.environ_set_temp({'XDG_DATA_HOME': self.temp_dir}): - standarddir.init(None) - expected = os.path.join(self.temp_dir, 'qutebrowser') - self.assertEqual(standarddir.data(), expected) - - @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") - def test_config_explicit(self): - """Test config dir with XDG_CONFIG_HOME explicitly set.""" - with helpers.environ_set_temp({'XDG_CONFIG_HOME': self.temp_dir}): - standarddir.init(None) - expected = os.path.join(self.temp_dir, 'qutebrowser') - self.assertEqual(standarddir.config(), expected) - - @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") - def test_cache_explicit(self): - """Test cache dir with XDG_CACHE_HOME explicitly set.""" - with helpers.environ_set_temp({'XDG_CACHE_HOME': self.temp_dir}): - standarddir.init(None) - expected = os.path.join(self.temp_dir, 'qutebrowser') - self.assertEqual(standarddir.cache(), expected) - - @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") - def test_data(self): - """Test data dir with XDG_DATA_HOME not set.""" - env = {'HOME': self.temp_dir, 'XDG_DATA_HOME': None} - with helpers.environ_set_temp(env): - standarddir.init(None) - expected = os.path.join(self.temp_dir, '.local', 'share', - 'qutebrowser') - self.assertEqual(standarddir.data(), expected) - - @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") - def test_config(self): - """Test config dir with XDG_CONFIG_HOME not set.""" - env = {'HOME': self.temp_dir, 'XDG_CONFIG_HOME': None} - with helpers.environ_set_temp(env): - standarddir.init(None) - expected = os.path.join(self.temp_dir, '.config', 'qutebrowser') - self.assertEqual(standarddir.config(), expected) - - @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") - def test_cache(self): - """Test cache dir with XDG_CACHE_HOME not set.""" - env = {'HOME': self.temp_dir, 'XDG_CACHE_HOME': None} - with helpers.environ_set_temp(env): - standarddir.init(None) - expected = os.path.join(self.temp_dir, '.cache', 'qutebrowser') - self.assertEqual(standarddir.cache(), expected) - - def tearDown(self): - qApp.setApplicationName(self.old_name) - shutil.rmtree(self.temp_dir) - - -class GetStandardDirWindowsTests(unittest.TestCase): - - """Tests for standarddir under Windows. - - Attributes: - old_name: The old applicationName. - """ - - def setUp(self): - self.old_name = qApp.applicationName() - # We can't store the files in a temp dir, so we don't chose qutebrowser - qApp.setApplicationName('qutebrowser_test') - standarddir.init(None) - - def tearDown(self): - qApp.setApplicationName(self.old_name) - - @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") - def test_data(self): - """Test data dir.""" - self.assertEqual(standarddir.data().split(os.sep)[-2:], - ['qutebrowser_test', 'data'], standarddir.data()) - - @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") - def test_config(self): - """Test config dir.""" - self.assertEqual(standarddir.config().split(os.sep)[-1], - 'qutebrowser_test', - standarddir.config()) - - @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") - def test_cache(self): - """Test cache dir.""" - self.assertEqual(standarddir.cache().split(os.sep)[-2:], - ['qutebrowser_test', 'cache'], standarddir.cache()) diff --git a/qutebrowser/test/utils/usertypes/__init__.py b/qutebrowser/test/utils/usertypes/__init__.py deleted file mode 100644 index 9dc19e4ab..000000000 --- a/qutebrowser/test/utils/usertypes/__init__.py +++ /dev/null @@ -1,20 +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 . - -"""Tests for qutebrowser.utils.usertype.""" diff --git a/qutebrowser/test/testfile b/qutebrowser/utils/testfile similarity index 100% rename from qutebrowser/test/testfile rename to qutebrowser/utils/testfile diff --git a/scripts/misc_checks.py b/scripts/misc_checks.py index 9371b3350..4e2c185f5 100644 --- a/scripts/misc_checks.py +++ b/scripts/misc_checks.py @@ -76,7 +76,7 @@ def check_spelling(target): # Words which look better when splitted, but might need some fine tuning. words |= {'keystrings', 'webelements', 'mouseevent', 'keysequence', 'normalmode', 'eventloops', 'sizehint', 'statemachine', - 'metaobject', 'logrecord', 'monkeypatch', 'filetype'} + 'metaobject', 'logrecord', 'filetype'} seen = collections.defaultdict(list) try: diff --git a/scripts/run_pylint_on_tests.py b/scripts/run_pylint_on_tests.py new file mode 100644 index 000000000..94ba89c7d --- /dev/null +++ b/scripts/run_pylint_on_tests.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# 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 . + +"""Run pylint on tests. + +This is needed because pylint can't check a folder which isn't a package: +https://bitbucket.org/logilab/pylint/issue/512/ +""" + +import os +import sys +import os.path +import subprocess + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), os.pardir)) + +from scripts import utils + + +def main(): + """Main entry point. + + Return: + The pylint exit status. + """ + utils.change_cwd() + files = [] + for dirpath, _dirnames, filenames in os.walk('tests'): + for fn in filenames: + if os.path.splitext(fn)[1] == '.py': + files.append(os.path.join(dirpath, fn)) + disabled = ['attribute-defined-outside-init', 'redefined-outer-name', + 'unused-argument'] + no_docstring_rgx = ['^__.*__$', '^setup$'] + args = (['--disable={}'.format(','.join(disabled)), + '--no-docstring-rgx=({})'.format('|'.join(no_docstring_rgx))] + + sys.argv[1:] + files) + ret = subprocess.call(['pylint'] + args) + return ret + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/qutebrowser/test/browser/http/test_content_disposition.py b/tests/browser/http/test_content_disposition.py similarity index 60% rename from qutebrowser/test/browser/http/test_content_disposition.py rename to tests/browser/http/test_content_disposition.py index 63f5505fd..576998bcf 100644 --- a/qutebrowser/test/browser/http/test_content_disposition.py +++ b/tests/browser/http/test_content_disposition.py @@ -20,11 +20,11 @@ """Tests for qutebrowser.browser.http.parse_content_disposition.""" import os -import unittest import logging +import pytest + from qutebrowser.browser import http -from qutebrowser.test import stubs from qutebrowser.utils import log @@ -34,86 +34,90 @@ DEFAULT_NAME = 'qutebrowser-download' # These test cases are based on http://greenbytes.de/tech/tc2231/ -class AttachmentTestCase(unittest.TestCase): +class _HeaderChecker(object): + """Helper class with some convenience methods to check filenames. - """Helper class with some convenience methods to check filenames.""" + Attrs: + caplog: fixture from pytest-capturelog + stubs: fixture that provides testing stubs + """ - def _check_filename(self, header, filename): + def __init__(self, caplog, stubs): + self.caplog = caplog + self.stubs = stubs + + def check_filename(self, header, filename, expected_inline=False): """Check if the passed header has the given filename.""" - reply = stubs.FakeNetworkReply(headers={'Content-Disposition': header}) + reply = self.stubs.FakeNetworkReply( + headers={'Content-Disposition': header}) cd_inline, cd_filename = http.parse_content_disposition(reply) - self.assertIsNotNone(cd_filename) - self.assertEqual(cd_filename, filename) - self.assertFalse(cd_inline) + assert cd_filename is not None + assert cd_filename == filename + assert cd_inline == expected_inline - def _check_ignored(self, header): + def check_ignored(self, header): """Check if the passed header is ignored.""" - reply = stubs.FakeNetworkReply(headers={'Content-Disposition': header}) - with self.assertLogs(log.rfc6266, logging.ERROR): + reply = self.stubs.FakeNetworkReply( + headers={'Content-Disposition': header}) + with self.caplog.atLevel(logging.ERROR, logger=log.rfc6266.name): + # with self.assertLogs(log.rfc6266, logging.ERROR): cd_inline, cd_filename = http.parse_content_disposition(reply) - self.assertEqual(cd_filename, DEFAULT_NAME) - self.assertTrue(cd_inline) + assert cd_filename == DEFAULT_NAME + assert cd_inline - def _check_unnamed(self, header): + def check_unnamed(self, header): """Check if the passed header results in an unnamed attachment.""" - reply = stubs.FakeNetworkReply(headers={'Content-Disposition': header}) + reply = self.stubs.FakeNetworkReply( + headers={'Content-Disposition': header}) cd_inline, cd_filename = http.parse_content_disposition(reply) - self.assertEqual(cd_filename, DEFAULT_NAME) - self.assertFalse(cd_inline) + assert cd_filename == DEFAULT_NAME + assert not cd_inline -class InlineTests(unittest.TestCase): +@pytest.fixture +def header_checker(caplog, stubs): + """Fixture that provides a _HeaderChecker class for tests""" + return _HeaderChecker(caplog, stubs) + +class TestInline: """Various tests relating to the "inline" disposition type. See Section 4.2 of RFC 6266. """ - def _check_filename(self, header, filename): - """Check if the passed header has the given filename.""" - reply = stubs.FakeNetworkReply(headers={'Content-Disposition': header}) - cd_inline, cd_filename = http.parse_content_disposition(reply) - self.assertEqual(cd_filename, filename) - self.assertTrue(cd_inline) - - def _check_ignored(self, header): - """Check if the passed header is ignored.""" - reply = stubs.FakeNetworkReply(headers={'Content-Disposition': header}) - cd_inline, cd_filename = http.parse_content_disposition(reply) - self.assertEqual(cd_filename, DEFAULT_NAME) - self.assertTrue(cd_inline) - - def test_inlonly(self): + def test_inlonly(self, header_checker): """'inline' only This should be equivalent to not including the header at all. """ - self._check_ignored('inline') + header_checker.check_ignored('inline') - def test_inlonlyquoted(self): + def test_inlonlyquoted(self, header_checker): """'inline' only, using double quotes This is invalid syntax, thus the header should be ignored. """ - with self.assertLogs(log.rfc6266, logging.ERROR): - self._check_ignored('"inline"') + header_checker.check_ignored('"inline"') - def test_inlwithasciifilename(self): + def test_inlwithasciifilename(self, header_checker): """'inline', specifying a filename of foo.html Some UAs use this filename in a subsequent "save" operation. """ - self._check_filename('inline; filename="foo.html"', 'foo.html') + header_checker.check_filename('inline; filename="foo.html"', + 'foo.html', expected_inline=True) - def test_inlwithfnattach(self): + def test_inlwithfnattach(self, header_checker): """'inline', specifying a filename of "Not an attachment!". This checks for proper parsing for disposition types. """ - self._check_filename('inline; filename="Not an attachment!"', - "Not an attachment!") + header_checker.check_filename('inline; filename="Not an attachment!"', + "Not an attachment!", + expected_inline=True) - def test_inlwithasciifilenamepdf(self): + def test_inlwithasciifilenamepdf(self, header_checker): """'inline', specifying a filename of foo.pdf. Some UAs use this filename in a subsequent "save" operation. This @@ -122,17 +126,17 @@ class InlineTests(unittest.TestCase): with the latest Acrobat Reader plugin, or, in the case of Chrome, using the built-in PDF handler). """ - self._check_filename('inline; filename="foo.pdf"', "foo.pdf") + header_checker.check_filename('inline; filename="foo.pdf"', "foo.pdf", + expected_inline=True) -class AttachmentTests(AttachmentTestCase): - +class TestAttachment: """Various tests relating to the "attachment" disposition type. See Section 4.2 of RFC 6266. """ - def test_attonly(self): + def test_attonly(self, stubs): """'attachment' only. UA should offer to download the resource. @@ -140,53 +144,55 @@ class AttachmentTests(AttachmentTestCase): reply = stubs.FakeNetworkReply( headers={'Content-Disposition': 'attachment'}) cd_inline, cd_filename = http.parse_content_disposition(reply) - self.assertFalse(cd_inline) - self.assertEqual(cd_filename, DEFAULT_NAME) + assert not cd_inline + assert cd_filename == DEFAULT_NAME - def test_attonlyquoted(self): + def test_attonlyquoted(self, header_checker): """'attachment' only, using double quotes This is invalid syntax, thus the header should be ignored. """ - self._check_ignored('"attachment"') + header_checker.check_ignored('"attachment"') # we can't test attonly403 here. - def test_attonlyucase(self): + def test_attonlyucase(self, header_checker): """'ATTACHMENT' only UA should offer to download the resource. - """ - self._check_unnamed('ATTACHMENT') + """ + header_checker.check_unnamed('ATTACHMENT') - def test_attwithasciifilename(self): + def test_attwithasciifilename(self, header_checker): """'attachment', specifying a filename of foo.html UA should offer to download the resource as "foo.html". """ - self._check_filename('attachment; filename="foo.html"', 'foo.html') + header_checker.check_filename('attachment; filename="foo.html"', + 'foo.html') - def test_attwithasciifilename25(self): + def test_attwithasciifilename25(self, header_checker): """'attachment', with a 25 character filename.""" - self._check_filename( + header_checker.check_filename( 'attachment; filename="0000000000111111111122222"', '0000000000111111111122222') - def test_attwithasciifilename35(self): + def test_attwithasciifilename35(self, header_checker): """'attachment', with a 35 character filename.""" - self._check_filename( + header_checker.check_filename( 'attachment; filename="00000000001111111111222222222233333"', '00000000001111111111222222222233333') - def test_attwithasciifnescapedchar(self): + def test_attwithasciifnescapedchar(self, header_checker): r"""'attachment', specifying a filename of f\oo.html. (the first 'o' being escaped) UA should offer to download the resource as "foo.html". """ - self._check_filename(r'attachment; filename="f\oo.html"', 'foo.html') + header_checker.check_filename(r'attachment; filename="f\oo.html"', + 'foo.html') - def test_attwithasciifnescapedquote(self): + def test_attwithasciifnescapedquote(self, header_checker): r"""'attachment', specifying a filename of \"quoting\" tested.html (using double quotes around "quoting" to test... quoting) @@ -195,19 +201,19 @@ class AttachmentTests(AttachmentTestCase): tested.html' (stripping the quotes may be ok for security reasons, but getting confused by them is not). """ - self._check_filename(r'attachment; filename="\"quoting\" tested.html"', - '"quoting" tested.html') + header = r'attachment; filename="\"quoting\" tested.html"' + header_checker.check_filename(header, '"quoting" tested.html') - def test_attwithquotedsemicolon(self): + def test_attwithquotedsemicolon(self, header_checker): """'attachment', specifying a filename of Here's a semicolon;.html. This checks for proper parsing for parameters. """ - self._check_filename( + header_checker.check_filename( 'attachment; filename="Here\'s a semicolon;.html"', "Here's a semicolon;.html") - def test_attwithfilenameandextparam(self): + def test_attwithfilenameandextparam(self, header_checker): """'attachment', specifying a filename of foo.html. And an extension parameter "foo" which should be ignored (see Section @@ -215,11 +221,11 @@ class AttachmentTests(AttachmentTestCase): UA should offer to download the resource as "foo.html". """ - self._check_filename( + header_checker.check_filename( 'attachment; foo="bar"; filename="foo.html"', 'foo.html') - def test_attwithfilenameandextparamescaped(self): + def test_attwithfilenameandextparamescaped(self, header_checker): """'attachment', specifying a filename of foo.html. And an extension parameter "foo" which should be ignored (see Section @@ -229,35 +235,36 @@ class AttachmentTests(AttachmentTestCase): UA should offer to download the resource as "foo.html". """ - self._check_filename( + header_checker.check_filename( r'attachment; foo="\"\\";filename="foo.html"', 'foo.html') - def test_attwithasciifilenameucase(self): + def test_attwithasciifilenameucase(self, header_checker): """'attachment', specifying a filename of foo.html UA should offer to download the resource as "foo.html". """ - self._check_filename(r'attachment; FILENAME="foo.html"', 'foo.html') + header_checker.check_filename(r'attachment; FILENAME="foo.html"', + 'foo.html') - def test_attwithasciifilenamenq(self): + def test_attwithasciifilenamenq(self, header_checker): """'attachment', specifying a filename of foo.html. (using a token instead of a quoted-string). Note that was invalid according to Section 19.5.1 of RFC 2616. """ - self._check_filename('attachment; filename=foo.html', 'foo.html') + header_checker.check_filename('attachment; filename=foo.html', + 'foo.html') - def test_attwithtokfncommanq(self): + def test_attwithtokfncommanq(self, header_checker): """'attachment', specifying a filename of foo,bar.html. (using a comma despite using token syntax). """ - self._check_ignored('attachment; filename=foo,bar.html') + header_checker.check_ignored('attachment; filename=foo,bar.html') - # With relaxed=True we accept that - @unittest.expectedFailure - def test_attwithasciifilenamenqs(self): + @pytest.mark.xfail(reason='With relaxed=True we accept that') + def test_attwithasciifilenamenqs(self, header_checker): """'attachment', specifying a filename of foo.html. (using a token instead of a quoted-string, and adding a trailing @@ -267,9 +274,9 @@ class AttachmentTests(AttachmentTestCase): incorrect, as no other parameter follows. Thus the header field should be ignored. """ - self._check_ignored('attachment; filename=foo.html ;') + header_checker.check_ignored('attachment; filename=foo.html ;') - def test_attemptyparam(self): + def test_attemptyparam(self, header_checker): """'attachment', specifying a filename of foo. (but including an empty parameter). @@ -278,34 +285,36 @@ class AttachmentTests(AttachmentTestCase): incorrect, as no other parameter follows. Thus the header field should be ignored. """ - self._check_ignored('attachment; ;filename=foo') + header_checker.check_ignored('attachment; ;filename=foo') - def test_attwithasciifilenamenqws(self): + def test_attwithasciifilenamenqws(self, header_checker): """'attachment', specifying a filename of foo bar.html. (without using quoting). This is invalid. "token" does not allow whitespace. """ - self._check_ignored('attachment; filename=foo bar.html') + header_checker.check_ignored('attachment; filename=foo bar.html') - def test_attwithfntokensq(self): + def test_attwithfntokensq(self, header_checker): """'attachment', specifying a filename of 'foo.bar' (using single quotes). """ - self._check_filename("attachment; filename='foo.bar'", "'foo.bar'") + header_checker.check_filename("attachment; filename='foo.bar'", + "'foo.bar'") - def test_attwithisofnplain(self): + def test_attwithisofnplain(self, header_checker): """'attachment', specifying a filename of foo-ä.html. (using plain ISO-8859-1) UA should offer to download the resource as "foo-ä.html". """ - self._check_filename('attachment; filename="foo-ä.html"', 'foo-ä.html') + header_checker.check_filename('attachment; filename="foo-ä.html"', + 'foo-ä.html') - def test_attwithutf8fnplain(self): + def test_attwithutf8fnplain(self, header_checker): """'attachment', specifying a filename of foo-ä.html. (which happens to be foo-ä.html using UTF-8 encoding). @@ -314,29 +323,30 @@ class AttachmentTests(AttachmentTestCase): "foo-ä.html" instead indicates that the UA tried to be smart by detecting something that happens to look like UTF-8. """ - self._check_filename('attachment; filename="foo-ä.html"', - 'foo-ä.html') + header_checker.check_filename('attachment; filename="foo-ä.html"', + 'foo-ä.html') - def test_attwithfnrawpctenca(self): + def test_attwithfnrawpctenca(self, header_checker): """'attachment', specifying a filename of foo-%41.html UA should offer to download the resource as "foo-%41.html". Displaying "foo-A.html" instead would indicate that the UA has attempted to percent-decode the parameter. """ - self._check_filename('attachment; filename="foo-%41.html"', - 'foo-%41.html') + header_checker.check_filename('attachment; filename="foo-%41.html"', + 'foo-%41.html') - def test_attwithfnusingpct(self): + def test_attwithfnusingpct(self, header_checker): """'attachment', specifying a filename of 50%.html UA should offer to download the resource as "50%.html". This tests how UAs that fails at attwithfnrawpctenca handle "%" characters that do not start a "% hexdig hexdig" sequence. """ - self._check_filename('attachment; filename="50%.html"', '50%.html') + header_checker.check_filename('attachment; filename="50%.html"', + '50%.html') - def test_attwithfnrawpctencaq(self): + def test_attwithfnrawpctencaq(self, header_checker): """'attachment', specifying a filename of foo-%41.html. Using an escape character (this tests whether adding an escape @@ -345,10 +355,10 @@ class AttachmentTests(AttachmentTestCase): UA should offer to download the resource as "foo-%41.html". """ - self._check_filename(r'attachment; filename="foo-%\41.html"', - 'foo-%41.html') + header_checker.check_filename(r'attachment; filename="foo-%\41.html"', + 'foo-%41.html') - def test_attwithnamepct(self): + def test_attwithnamepct(self, header_checker): """'attachment', specifying a name parameter of foo-%41.html. (this test was added to observe the behavior of the (unspecified) treatment of "name" as synonym for "filename"; see Ned Freed's summary[1] where @@ -359,17 +369,18 @@ class AttachmentTests(AttachmentTestCase): [1] http://www.imc.org/ietf-smtp/mail-archive/msg05023.html """ - self._check_unnamed('attachment; name="foo-%41.html"') + header_checker.check_unnamed('attachment; name="foo-%41.html"') - def test_attwithfilenamepctandiso(self): + def test_attwithfilenamepctandiso(self, header_checker): """'attachment', specifying a filename parameter of ä-%41.html. (this test was added to observe the behavior when non-ASCII characters and percent-hexdig sequences are combined) """ - self._check_filename('attachment; filename="ä-%41.html"', 'ä-%41.html') + header_checker.check_filename('attachment; filename="ä-%41.html"', + 'ä-%41.html') - def test_attwithfnrawpctenclong(self): + def test_attwithfnrawpctenclong(self, header_checker): """'attachment', specifying a filename of foo-%c3%a4-%e2%82%ac.html. (using raw percent encoded UTF-8 to represent foo-ä-€.html) @@ -380,28 +391,29 @@ class AttachmentTests(AttachmentTestCase): (using UTF-8). Displaying something else would indicate that the UA tried to percent-decode, but used a different encoding. """ - self._check_filename( + header_checker.check_filename( 'attachment; filename="foo-%c3%a4-%e2%82%ac.html"', 'foo-%c3%a4-%e2%82%ac.html') - def test_attwithasciifilenamews1(self): + def test_attwithasciifilenamews1(self, header_checker): """'attachment', specifying a filename of foo.html. (With one blank space before the equals character). UA should offer to download the resource as "foo.html". """ - self._check_filename('attachment; filename ="foo.html"', 'foo.html') + header_checker.check_filename('attachment; filename ="foo.html"', + 'foo.html') - def test_attwith2filenames(self): + def test_attwith2filenames(self, header_checker): """'attachment', specifying two filename parameters. This is invalid syntax. """ - self._check_ignored( + header_checker.check_ignored( 'attachment; filename="foo.html"; filename="bar.html"') - def test_attfnbrokentoken(self): + def test_attfnbrokentoken(self, header_checker): """'attachment', specifying a filename of foo[1](2).html. Missing the quotes. Also, "[", "]", "(" and ")" are not allowed in the @@ -410,9 +422,9 @@ class AttachmentTests(AttachmentTestCase): This is invalid according to Section 19.5.1 of RFC 2616 and RFC 6266, so UAs should ignore it. """ - self._check_ignored('attachment; filename=foo[1](2).html') + header_checker.check_ignored('attachment; filename=foo[1](2).html') - def test_attfnbrokentokeniso(self): + def test_attfnbrokentokeniso(self, header_checker): """'attachment', specifying a filename of foo-ä.html. Missing the quotes. @@ -420,9 +432,9 @@ class AttachmentTests(AttachmentTestCase): This is invalid, as the umlaut is not a valid token character, so UAs should ignore it. """ - self._check_ignored('attachment; filename=foo-ä.html') + header_checker.check_ignored('attachment; filename=foo-ä.html') - def test_attfnbrokentokenutf(self): + def test_attfnbrokentokenutf(self, header_checker): """'attachment', specifying a filename of foo-ä.html. (which happens to be foo-ä.html using UTF-8 encoding) but missing the @@ -431,31 +443,31 @@ class AttachmentTests(AttachmentTestCase): This is invalid, as the umlaut is not a valid token character, so UAs should ignore it. """ - self._check_ignored('attachment; filename=foo-ä.html') + header_checker.check_ignored('attachment; filename=foo-ä.html') - def test_attmissingdisposition(self): + def test_attmissingdisposition(self, header_checker): """Disposition type missing, filename specified. This is invalid, so UAs should ignore it. """ - self._check_ignored('filename=foo.html') + header_checker.check_ignored('filename=foo.html') - def test_attmissingdisposition2(self): + def test_attmissingdisposition2(self, header_checker): """Disposition type missing, filename specified after extension. This is invalid, so UAs should ignore it. """ - self._check_ignored('x=y; filename=foo.html') + header_checker.check_ignored('x=y; filename=foo.html') - def test_attmissingdisposition3(self): + def test_attmissingdisposition3(self, header_checker): """Disposition type missing, filename "qux". Can it be more broken? (Probably) This is invalid, so UAs should ignore it. """ - self._check_ignored('"foo; filename=bar;baz"; filename=qux') + header_checker.check_ignored('"foo; filename=bar;baz"; filename=qux') - def test_attmissingdisposition4(self): + def test_attmissingdisposition4(self, header_checker): """Disposition type missing. Two filenames specified separated by a comma (this is syntactically @@ -464,62 +476,62 @@ class AttachmentTests(AttachmentTestCase): This is invalid, so UAs should ignore it. """ - self._check_ignored('filename=foo.html, filename=bar.html') + header_checker.check_ignored('filename=foo.html, filename=bar.html') - def test_emptydisposition(self): + def test_emptydisposition(self, header_checker): """Disposition type missing (but delimiter present). Filename specified. This is invalid, so UAs should ignore it. """ - self._check_ignored('; filename=foo.html') + header_checker.check_ignored('; filename=foo.html') - def test_doublecolon(self): + def test_doublecolon(self, header_checker): """Header field value starts with a colon. This is invalid, so UAs should ignore it. """ - self._check_ignored(': inline; attachment; filename=foo.html') + header_checker.check_ignored(': inline; attachment; filename=foo.html') - def test_attandinline(self): + def test_attandinline(self, header_checker): """Both disposition types specified. This is invalid, so UAs should ignore it. """ - self._check_ignored('inline; attachment; filename=foo.html') + header_checker.check_ignored('inline; attachment; filename=foo.html') - def test_attandinline2(self): + def test_attandinline2(self, header_checker): """Both disposition types specified. This is invalid, so UAs should ignore it. """ - self._check_ignored('attachment; inline; filename=foo.html') + header_checker.check_ignored('attachment; inline; filename=foo.html') - def test_attbrokenquotedfn(self): + def test_attbrokenquotedfn(self, header_checker): """'attachment', specifying a filename parameter that is broken. (quoted-string followed by more characters). This is invalid syntax. This is invalid, so UAs should ignore it. """ - self._check_ignored('attachment; filename="foo.html".txt') + header_checker.check_ignored('attachment; filename="foo.html".txt') - def test_attbrokenquotedfn2(self): + def test_attbrokenquotedfn2(self, header_checker): """'attachment', specifying a filename parameter that is broken. (missing ending double quote). This is invalid syntax. This is invalid, so UAs should ignore it. """ - self._check_ignored('attachment; filename="bar') + header_checker.check_ignored('attachment; filename="bar') - def test_attbrokenquotedfn3(self): + def test_attbrokenquotedfn3(self, header_checker): """'attachment', specifying a filename parameter that is broken. (disallowed characters in token syntax). This is invalid syntax. This is invalid, so UAs should ignore it. """ - self._check_ignored('attachment; filename=foo"bar;baz"qux') + header_checker.check_ignored('attachment; filename=foo"bar;baz"qux') - def test_attmultinstances(self): + def test_attmultinstances(self, header_checker): """'attachment', two comma-separated instances of the header field. As Content-Disposition doesn't use a list-style syntax, this is invalid @@ -528,53 +540,54 @@ class AttachmentTests(AttachmentTestCase): This is invalid, so UAs should ignore it. """ - self._check_ignored( + header_checker.check_ignored( 'attachment; filename=foo.html, attachment; filename=bar.html') - def test_attmissingdelim(self): + def test_attmissingdelim(self, header_checker): """Uses two parameters, but the mandatory delimiter ";" is missing. This is invalid, so UAs should ignore it. """ - self._check_ignored('attachment; foo=foo filename=bar') + header_checker.check_ignored('attachment; foo=foo filename=bar') - def test_attmissingdelim2(self): + def test_attmissingdelim2(self, header_checker): """Uses two parameters, but the mandatory delimiter ";" is missing. This is invalid, so UAs should ignore it. """ - self._check_ignored('attachment; filename=bar foo=foo') + header_checker.check_ignored('attachment; filename=bar foo=foo') - def test_attmissingdelim3(self): + def test_attmissingdelim3(self, header_checker): """";" missing between disposition type and filename parameter. This is invalid, so UAs should ignore it. """ - self._check_ignored('attachment filename=bar') + header_checker.check_ignored('attachment filename=bar') - def test_attreversed(self): + def test_attreversed(self, header_checker): """filename parameter and disposition type reversed. This is invalid, so UAs should ignore it. """ - self._check_ignored('filename=foo.html; attachment') + header_checker.check_ignored('filename=foo.html; attachment') - def test_attconfusedparam(self): + def test_attconfusedparam(self, header_checker): """'attachment', specifying an "xfilename" parameter. Should be treated as unnamed attachment. """ - self._check_unnamed('attachment; xfilename=foo.html') + header_checker.check_unnamed('attachment; xfilename=foo.html') - def test_attabspath(self): + def test_attabspath(self, header_checker): """'attachment', specifying an absolute filename in the fs root. Either ignore the filename altogether, or discard the path information. """ - self._check_filename('attachment; filename="/foo.html"', 'foo.html') + header_checker.check_filename('attachment; filename="/foo.html"', + 'foo.html') - @unittest.skipUnless(os.name == 'posix', "requires POSIX") - def test_attabspathwin_unix(self): + @pytest.mark.skipif(os.name != 'posix', reason="requires POSIX") + def test_attabspathwin_unix(self, header_checker): """'attachment', specifying an absolute filename in the fs root. Either ignore the filename altogether, or discard the path information. @@ -585,11 +598,11 @@ class AttachmentTests(AttachmentTestCase): apparently some UAs consider the backslash a legitimate filename character. """ - self._check_filename(r'attachment; filename="\\foo.html"', - r'\foo.html') + header_checker.check_filename(r'attachment; filename="\\foo.html"', + r'\foo.html') - @unittest.skipUnless(os.name == 'nt', "requires Windows") - def test_attabspathwin_win(self): + @pytest.mark.skipif(os.name != 'nt', reason="requires Windows") + def test_attabspathwin_win(self, header_checker): """'attachment', specifying an absolute filename in the fs root. Either ignore the filename altogether, or discard the path information. @@ -600,62 +613,64 @@ class AttachmentTests(AttachmentTestCase): apparently some UAs consider the backslash a legitimate filename character. """ - self._check_filename(r'attachment; filename="\\foo.html"', 'foo.html') + header_checker.check_filename(r'attachment; filename="\\foo.html"', + 'foo.html') + # Note we do not check the "Additional parameters" section. -class DispositionTypeExtensionTests(AttachmentTestCase): - +class TestDispositionTypeExtension: """Tests checking behavior for disposition type extensions. They should be treated as "attachment", see Section 4.2 of RFC 6266. """ - def test_dispext(self): + def test_dispext(self, header_checker): """'foobar' only This should be equivalent to using "attachment". """ - self._check_unnamed('foobar') + header_checker.check_unnamed('foobar') - def test_dispextbadfn(self): + def test_dispextbadfn(self, header_checker): """'attachment', with no filename parameter""" - self._check_unnamed('attachment; example="filename=example.txt"') + header_checker.check_unnamed( + 'attachment; example="filename=example.txt"') -class CharacterSetTests(AttachmentTestCase): - +class TestCharacterSet: """Various tests using the parameter value encoding defined in RFC 5987.""" - def test_attwithisofn2231iso(self): + def test_attwithisofn2231iso(self, header_checker): """'attachment', specifying a filename of foo-ä.html. Using RFC2231/5987 encoded ISO-8859-1. UA should offer to download the resource as "foo-ä.html". """ - self._check_filename("attachment; filename*=iso-8859-1''foo-%E4.html", - 'foo-ä.html') + header_checker.check_filename( + "attachment; filename*=iso-8859-1''foo-%E4.html", + 'foo-ä.html') - def test_attwithfn2231utf8(self): + def test_attwithfn2231utf8(self, header_checker): """'attachment', specifying a filename of foo-ä-€.html. Using RFC2231/5987 encoded UTF-8. UA should offer to download the resource as "foo-ä-€.html". """ - self._check_filename( + header_checker.check_filename( "attachment; filename*=UTF-8''foo-%c3%a4-%e2%82%ac.html", 'foo-ä-€.html') - def test_attwithfn2231noc(self): + def test_attwithfn2231noc(self, header_checker): """Behavior is undefined in RFC 2231. The charset part is missing, although UTF-8 was used. """ - self._check_ignored( + header_checker.check_ignored( "attachment; filename*=''foo-%c3%a4-%e2%82%ac.html") - def test_attwithfn2231utf8comp(self): + def test_attwithfn2231utf8comp(self, header_checker): """'attachment', specifying a filename of foo-ä.html. Using RFC2231 encoded UTF-8, but choosing the decomposed form @@ -665,10 +680,11 @@ class CharacterSetTests(AttachmentTestCase): UA should offer to download the resource as "foo-ä.html". """ - self._check_filename("attachment; filename*=UTF-8''foo-a%cc%88.html", - 'foo-ä.html') + header_checker.check_filename( + "attachment; filename*=UTF-8''foo-a%cc%88.html", + 'foo-ä.html') - def test_attwithfn2231utf8_bad(self): + def test_attwithfn2231utf8_bad(self, header_checker): """'attachment', specifying a filename of foo-ä-€.html. Using RFC2231 encoded UTF-8, but declaring ISO-8859-1. @@ -676,10 +692,10 @@ class CharacterSetTests(AttachmentTestCase): The octet %82 does not represent a valid ISO-8859-1 code point, so the UA should really ignore the parameter. """ - self._check_ignored("attachment; " - "iso-8859-1''foo-%c3%a4-%e2%82%ac.html") + header_checker.check_ignored("attachment; " + "iso-8859-1''foo-%c3%a4-%e2%82%ac.html") - def test_attwithfn2231iso_bad(self): + def test_attwithfn2231iso_bad(self, header_checker): """'attachment', specifying a filename of foo-ä.html. Using RFC2231 encoded ISO-8859-1, but declaring UTF-8. @@ -687,36 +703,40 @@ class CharacterSetTests(AttachmentTestCase): The octet %E4 does not represent a valid UTF-8 octet sequence, so the UA should really ignore the parameter. """ - self._check_ignored("attachment; filename*=utf-8''foo-%E4.html") + header_checker.check_ignored( + "attachment; filename*=utf-8''foo-%E4.html") - def test_attwithfn2231ws1(self): + def test_attwithfn2231ws1(self, header_checker): """'attachment', specifying a filename of foo-ä.html. Using RFC2231 encoded UTF-8, with whitespace before "*=" The parameter is invalid, thus should be ignored. """ - self._check_ignored("attachment; filename *=UTF-8''foo-%c3%a4.html") + header_checker.check_ignored( + "attachment; filename *=UTF-8''foo-%c3%a4.html") - def test_attwithfn2231ws2(self): + def test_attwithfn2231ws2(self, header_checker): """'attachment', specifying a filename of foo-ä.html. Using RFC2231 encoded UTF-8, with whitespace after "*=". UA should offer to download the resource as "foo-ä.html". """ - self._check_filename("attachment; filename*= UTF-8''foo-%c3%a4.html", - 'foo-ä.html') + header_checker.check_filename( + "attachment; filename*= UTF-8''foo-%c3%a4.html", + 'foo-ä.html') - def test_attwithfn2231ws3(self): + def test_attwithfn2231ws3(self, header_checker): """'attachment', specifying a filename of foo-ä.html. Using RFC2231 encoded UTF-8, with whitespace inside "* =" UA should offer to download the resource as "foo-ä.html". """ - self._check_filename("attachment; filename* =UTF-8''foo-%c3%a4.html", - 'foo-ä.html') + header_checker.check_filename( + "attachment; filename* =UTF-8''foo-%c3%a4.html", + 'foo-ä.html') - def test_attwithfn2231quot(self): + def test_attwithfn2231quot(self, header_checker): """'attachment', specifying a filename of foo-ä.html. Using RFC2231 encoded UTF-8, with double quotes around the parameter @@ -724,9 +744,10 @@ class CharacterSetTests(AttachmentTestCase): The parameter is invalid, thus should be ignored. """ - self._check_ignored("attachment; filename*=\"UTF-8''foo-%c3%a4.html\"") + header_checker.check_ignored( + "attachment; filename*=\"UTF-8''foo-%c3%a4.html\"") - def test_attwithfn2231quot2(self): + def test_attwithfn2231quot2(self, header_checker): """'attachment', specifying a filename of foo bar.html. Using "filename*", but missing character encoding and language (this @@ -734,70 +755,74 @@ class CharacterSetTests(AttachmentTestCase): The parameter is invalid, thus should be ignored. """ - self._check_ignored('attachment; filename*="foo%20bar.html"') + header_checker.check_ignored('attachment; filename*="foo%20bar.html"') - def test_attwithfn2231singleqmissing(self): + def test_attwithfn2231singleqmissing(self, header_checker): """'attachment', specifying a filename of foo-ä.html. Using RFC2231 encoded UTF-8, but a single quote is missing. The parameter is invalid, thus should be ignored. """ - self._check_ignored("attachment; filename*=UTF-8'foo-%c3%a4.html") + header_checker.check_ignored( + "attachment; filename*=UTF-8'foo-%c3%a4.html") - def test_attwithfn2231nbadpct1(self): + def test_attwithfn2231nbadpct1(self, header_checker): """'attachment', specifying a filename of foo%. Using RFC2231 encoded UTF-8, with a single "%" at the end. The parameter is invalid, thus should be ignored. """ - self._check_ignored("attachment; filename*=UTF-8''foo%") + header_checker.check_ignored("attachment; filename*=UTF-8''foo%") - def test_attwithfn2231nbadpct2(self): + def test_attwithfn2231nbadpct2(self, header_checker): """'attachment', specifying a filename of f%oo.html. Using RFC2231 encoded UTF-8, with a "%" not starting a percent-escape. The parameter is invalid, thus should be ignored. """ - self._check_ignored("attachment; filename*=UTF-8''f%oo.html") + header_checker.check_ignored("attachment; filename*=UTF-8''f%oo.html") - def test_attwithfn2231dpct(self): + def test_attwithfn2231dpct(self, header_checker): """'attachment', specifying a filename of A-%41.html. Using RFC2231 encoded UTF-8. """ - self._check_filename("attachment; filename*=UTF-8''A-%2541.html", - 'A-%41.html') + header_checker.check_filename( + "attachment; filename*=UTF-8''A-%2541.html", + 'A-%41.html') - @unittest.skipUnless(os.name == 'posix', "requires POSIX") - def test_attwithfn2231abspathdisguised_unix(self): + @pytest.mark.skipif(os.name != 'posix', reason="requires POSIX") + def test_attwithfn2231abspathdisguised_unix(self, header_checker): r"""'attachment', specifying a filename of \foo.html. Using RFC2231 encoded UTF-8. """ - self._check_filename("attachment; filename*=UTF-8''%5cfoo.html", - r'\foo.html') + header_checker.check_filename( + "attachment; filename*=UTF-8''%5cfoo.html", + r'\foo.html') - @unittest.skipUnless(os.name == 'nt', "requires Windows") - def test_attwithfn2231abspathdisguised_win(self): + @pytest.mark.skipif(os.name != 'nt', reason="requires Windows") + def test_attwithfn2231abspathdisguised_win(self, header_checker): r"""'attachment', specifying a filename of \foo.html. Using RFC2231 encoded UTF-8. """ - self._check_filename("attachment; filename*=UTF-8''%5cfoo.html", - r'foo.html') + header_checker.check_filename( + "attachment; filename*=UTF-8''%5cfoo.html", + r'foo.html') + # Note we do not test the "RFC2231 Encoding: Continuations (optional)" section -class EncodingFallbackTests(AttachmentTestCase): - +class TestEncodingFallback: """Test the same parameter both in traditional and extended format. This tests how the UA behaves when the same parameter name appears both in traditional and RFC 2231/5987 extended format. """ - def test_attfnboth(self): + def test_attfnboth(self, header_checker): """'attachment', specifying a filename in both formats. foo-ae.html in the traditional format, and foo-ä.html in RFC2231 @@ -807,10 +832,11 @@ class EncodingFallbackTests(AttachmentTestCase): RFC 2231/5987 encoded parameter ("filename*") should take precedence when understood. """ - self._check_filename("attachment; filename=\"foo-ae.html\"; " - "filename*=UTF-8''foo-%c3%a4.html", 'foo-ä.html') + header_checker.check_filename("attachment; filename=\"foo-ae.html\"; " + "filename*=UTF-8''foo-%c3%a4.html", + 'foo-ä.html') - def test_attfnboth2(self): + def test_attfnboth2(self, header_checker): """'attachment', specifying a filename in both formats. foo-ae.html in the traditional format, and foo-ä.html in RFC2231 @@ -820,10 +846,11 @@ class EncodingFallbackTests(AttachmentTestCase): RFC 2231/5987 encoded parameter ("filename*") should take precedence when understood. """ - self._check_filename("attachment; filename*=UTF-8''foo-%c3%a4.html; " - "filename=\"foo-ae.html\"", 'foo-ä.html') + header_checker.check_filename( + "attachment; filename*=UTF-8''foo-%c3%a4.html; " + "filename=\"foo-ae.html\"", 'foo-ä.html') - def test_attfnboth3(self): + def test_attfnboth3(self, header_checker): """'attachment', specifying an ambiguous filename. currency-sign=¤ in the simple RFC2231/5987 format, and euro-sign=€ in @@ -832,11 +859,11 @@ class EncodingFallbackTests(AttachmentTestCase): A UA that supports could pick either, or ignore both because of the ambiguity. """ - self._check_ignored("attachment; " - "filename*0*=ISO-8859-15''euro-sign%3d%a4; " - "filename*=ISO-8859-1''currency-sign%3d%a4") + header_checker.check_ignored( + "attachment; filename*0*=ISO-8859-15''euro-sign%3d%a4; " + "filename*=ISO-8859-1''currency-sign%3d%a4") - def test_attnewandfn(self): + def test_attnewandfn(self, header_checker): """'attachment', specifying a new parameter "foobar". Plus a filename of foo.html in the traditional format. @@ -844,12 +871,12 @@ class EncodingFallbackTests(AttachmentTestCase): "foobar" should be ignored, thus "foo.html" be used as filename (this tests whether the UA properly skips unknown parameters). """ - self._check_filename('attachment; foobar=x; filename="foo.html"', - 'foo.html') + header_checker.check_filename( + 'attachment; foobar=x; filename="foo.html"', + 'foo.html') -class RFC2047EncodingTests(AttachmentTestCase): - +class TestRFC2047Encoding: """These tests RFC 2047 style encoding. Note that according to Section 5 of RFC 2047, this encoding does not apply @@ -866,33 +893,28 @@ class RFC2047EncodingTests(AttachmentTestCase): which do support the RFC2231 encoding as well. """ - def test_attrfc2047token(self): + def test_attrfc2047token(self, header_checker): """Uses RFC 2047 style encoded word. "=" is invalid inside the token production, so this is invalid. """ - self._check_ignored( + header_checker.check_ignored( 'attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=') - def test_attrfc2047quoted(self): + def test_attrfc2047quoted(self, header_checker): """Uses RFC 2047 style encoded word. Using the quoted-string production. """ - self._check_filename( + header_checker.check_filename( 'attachment; filename="=?ISO-8859-1?Q?foo-=E4.html?="', '=?ISO-8859-1?Q?foo-=E4.html?=') -class OurTests(AttachmentTestCase): - +class TestOur: """Our own tests, not based on http://greenbytes.de/tech/tc2231/""" - def test_att_double_space(self): + def test_att_double_space(self, header_checker): """'attachment' with double space in the filename.""" - self._check_filename('attachment; filename="foo bar.html"', - 'foo bar.html') - - -if __name__ == '__main__': - unittest.main() + header_checker.check_filename('attachment; filename="foo bar.html"', + 'foo bar.html') diff --git a/qutebrowser/test/browser/http/test_http.py b/tests/browser/http/test_http.py similarity index 75% rename from qutebrowser/test/browser/http/test_http.py rename to tests/browser/http/test_http.py index 3dea72585..38ef1b42b 100644 --- a/qutebrowser/test/browser/http/test_http.py +++ b/tests/browser/http/test_http.py @@ -23,46 +23,39 @@ Note that tests for parse_content_disposition are in their own test_content_disposition.py file. """ -import unittest - from qutebrowser.browser import http -from qutebrowser.test import stubs -class ParseContentTypeTests(unittest.TestCase): +class TestParseContentType: """Test for parse_content_type.""" - def test_not_existing(self): + def test_not_existing(self, stubs): """Test without any Content-Type header.""" reply = stubs.FakeNetworkReply() mimetype, rest = http.parse_content_type(reply) - self.assertIsNone(mimetype) - self.assertIsNone(rest) + assert mimetype is None + assert rest is None - def test_mimetype(self): + def test_mimetype(self, stubs): """Test with simple Content-Type header.""" reply = stubs.FakeNetworkReply( headers={'Content-Type': 'image/example'}) mimetype, rest = http.parse_content_type(reply) - self.assertEqual(mimetype, 'image/example') - self.assertIsNone(rest) + assert mimetype == 'image/example' + assert rest is None - def test_empty(self): + def test_empty(self, stubs): """Test with empty Content-Type header.""" reply = stubs.FakeNetworkReply(headers={'Content-Type': ''}) mimetype, rest = http.parse_content_type(reply) - self.assertEqual(mimetype, '') - self.assertIsNone(rest) + assert mimetype == '' + assert rest is None - def test_additional(self): + def test_additional(self, stubs): """Test with Content-Type header with additional informations.""" reply = stubs.FakeNetworkReply( headers={'Content-Type': 'image/example; encoding=UTF-8'}) mimetype, rest = http.parse_content_type(reply) - self.assertEqual(mimetype, 'image/example') - self.assertEqual(rest, ' encoding=UTF-8') - - -if __name__ == '__main__': - unittest.main() + assert mimetype == 'image/example' + assert rest == ' encoding=UTF-8' diff --git a/tests/browser/test_tabhistory.py b/tests/browser/test_tabhistory.py new file mode 100644 index 000000000..6a955a38b --- /dev/null +++ b/tests/browser/test_tabhistory.py @@ -0,0 +1,139 @@ +# 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 webelement.tabhistory.""" + +from PyQt5.QtCore import QUrl, QPoint +import pytest + +from qutebrowser.browser import tabhistory +from qutebrowser.browser.tabhistory import TabHistoryItem as Item +from qutebrowser.utils import qtutils + + +class TestSerializeHistory: + + """Tests for serialize().""" + + ITEMS = [ + Item(QUrl('https://www.heise.de/'), QUrl('http://www.heise.de/'), + 'heise'), + Item(QUrl('http://example.com/%E2%80%A6'), + QUrl('http://example.com/%E2%80%A6'), 'percent', active=True), + Item(QUrl('http://example.com/?foo=bar'), + QUrl('http://original.url.example.com/'), 'arg', + user_data={'foo': 23, 'bar': 42}), + # From https://github.com/OtterBrowser/otter-browser/issues/709#issuecomment-74749471 + Item( + QUrl('http://github.com/OtterBrowser/24/134/2344/otter-browser/' + 'issues/709/'), + QUrl('http://github.com/OtterBrowser/24/134/2344/otter-browser/' + 'issues/709/'), + 'Page not found | github', + user_data={'zoom': 149, 'scroll-pos': QPoint(0, 0)}), + Item( + QUrl('https://mail.google.com/mail/u/0/#label/some+label/' + '234lkjsd0932lkjf884jqwerdf4'), + QUrl('https://mail.google.com/mail/u/0/#label/some+label/' + '234lkjsd0932lkjf884jqwerdf4'), + '"some label" - email@gmail.com - Gmail"', + user_data={'zoom': 120, 'scroll-pos': QPoint(0, 0)}), + ] + + @pytest.fixture(autouse=True) + def setup(self, webpage): + self.page = webpage + self.history = self.page.history() + assert self.history.count() == 0 + + stream, _data, self.user_data = tabhistory.serialize(self.ITEMS) + qtutils.deserialize_stream(stream, self.history) + + def test_count(self): + """Check if the history's count was loaded correctly.""" + assert self.history.count() == len(self.ITEMS) + + @pytest.mark.parametrize('i', range(len(ITEMS))) + def test_valid(self, i): + """Check if all items are valid.""" + assert self.history.itemAt(i).isValid() + + @pytest.mark.parametrize('i', range(len(ITEMS))) + def test_no_userdata(self, i): + """Check if all items have no user data.""" + assert self.history.itemAt(i).userData() is None + + def test_userdata(self): + """Check if all user data has been restored to self.user_data.""" + userdata_items = [item.user_data for item in self.ITEMS] + assert userdata_items == self.user_data + + def test_currentitem(self): + """Check if the current item index was loaded correctly.""" + assert self.history.currentItemIndex() == 1 + + @pytest.mark.parametrize('i, item', enumerate(ITEMS)) + def test_urls(self, i, item): + """Check if the URLs were loaded correctly.""" + assert self.history.itemAt(i).url() == item.url + + @pytest.mark.parametrize('i, item', enumerate(ITEMS)) + def test_original_urls(self, i, item): + """Check if the original URLs were loaded correctly.""" + assert self.history.itemAt(i).originalUrl() == item.original_url + + @pytest.mark.parametrize('i, item', enumerate(ITEMS)) + def test_titles(self, i, item): + """Check if the titles were loaded correctly.""" + assert self.history.itemAt(i).title() == item.title + + +class TestSerializeHistorySpecial: + + """Tests for serialize() without items set up in setup.""" + + @pytest.fixture(autouse=True) + def setup(self, webpage): + """Set up the initial QWebPage for each test.""" + self.page = webpage + self.history = self.page.history() + assert self.history.count() == 0 + + def test_no_active_item(self): + """Check tabhistory.serialize with no active item.""" + items = [Item(QUrl(), QUrl(), '')] + with pytest.raises(ValueError): + tabhistory.serialize(items) + + def test_two_active_items(self): + """Check tabhistory.serialize with two active items.""" + items = [Item(QUrl(), QUrl(), '', active=True), + Item(QUrl(), QUrl(), ''), + Item(QUrl(), QUrl(), '', active=True)] + with pytest.raises(ValueError): + tabhistory.serialize(items) + + def test_empty(self): + """Check tabhistory.serialize with no items.""" + items = [] + stream, _data, user_data = tabhistory.serialize(items) + qtutils.deserialize_stream(stream, self.history) + assert self.history.count() == 0 + assert self.history.currentItemIndex() == 0 + assert not user_data diff --git a/qutebrowser/test/browser/test_webelem.py b/tests/browser/test_webelem.py similarity index 70% rename from qutebrowser/test/browser/test_webelem.py rename to tests/browser/test_webelem.py index dc320d341..0461b0987 100644 --- a/qutebrowser/test/browser/test_webelem.py +++ b/tests/browser/test_webelem.py @@ -21,15 +21,14 @@ """Tests for the webelement utils.""" -import unittest from unittest import mock import collections.abc from PyQt5.QtCore import QRect, QPoint from PyQt5.QtWebKit import QWebElement +import pytest from qutebrowser.browser import webelem -from qutebrowser.test import stubs def get_webelem(geometry=None, frame=None, null=False, visibility='', @@ -87,17 +86,17 @@ def get_webelem(geometry=None, frame=None, null=False, visibility='', return wrapped -class WebElementWrapperTests(unittest.TestCase): +class TestWebElementWrapper: """Test WebElementWrapper.""" def test_nullelem(self): """Test __init__ with a null element.""" - with self.assertRaises(webelem.IsNullError): + with pytest.raises(webelem.IsNullError): get_webelem(null=True) -class IsVisibleInvalidTests(unittest.TestCase): +class TestIsVisibleInvalid: """Tests for is_visible with invalid elements. @@ -105,7 +104,8 @@ class IsVisibleInvalidTests(unittest.TestCase): frame: The FakeWebFrame we're using to test. """ - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self, stubs): self.frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100)) def test_nullelem(self): @@ -116,15 +116,15 @@ class IsVisibleInvalidTests(unittest.TestCase): """ elem = get_webelem() elem._elem.isNull.return_value = True - with self.assertRaises(webelem.IsNullError): + with pytest.raises(webelem.IsNullError): elem.is_visible(self.frame) def test_invalid_invisible(self): """Test elements with an invalid geometry which are invisible.""" elem = get_webelem(QRect(0, 0, 0, 0), self.frame) - self.assertFalse(elem.geometry().isValid()) - self.assertEqual(elem.geometry().x(), 0) - self.assertFalse(elem.is_visible(self.frame)) + assert not elem.geometry().isValid() + assert elem.geometry().x() == 0 + assert not elem.is_visible(self.frame) def test_invalid_visible(self): """Test elements with an invalid geometry which are visible. @@ -133,11 +133,11 @@ class IsVisibleInvalidTests(unittest.TestCase): which *are* visible, but don't have a valid geometry. """ elem = get_webelem(QRect(10, 10, 0, 0), self.frame) - self.assertFalse(elem.geometry().isValid()) - self.assertTrue(elem.is_visible(self.frame)) + assert not elem.geometry().isValid() + assert elem.is_visible(self.frame) -class IsVisibleScrollTests(unittest.TestCase): +class TestIsVisibleScroll: """Tests for is_visible when the frame is scrolled. @@ -145,22 +145,23 @@ class IsVisibleScrollTests(unittest.TestCase): frame: The FakeWebFrame we're using to test. """ - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self, stubs): self.frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100), scroll=QPoint(10, 10)) def test_invisible(self): """Test elements which should be invisible due to scrolling.""" elem = get_webelem(QRect(5, 5, 4, 4), self.frame) - self.assertFalse(elem.is_visible(self.frame)) + assert not elem.is_visible(self.frame) def test_visible(self): """Test elements which still should be visible after scrolling.""" elem = get_webelem(QRect(10, 10, 1, 1), self.frame) - self.assertTrue(elem.is_visible(self.frame)) + assert elem.is_visible(self.frame) -class IsVisibleCssTests(unittest.TestCase): +class TestIsVisibleCss: """Tests for is_visible with CSS attributes. @@ -168,33 +169,34 @@ class IsVisibleCssTests(unittest.TestCase): frame: The FakeWebFrame we're using to test. """ - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self, stubs): self.frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100)) def test_visibility_visible(self): """Check that elements with "visibility = visible" are visible.""" elem = get_webelem(QRect(0, 0, 10, 10), self.frame, visibility='visible') - self.assertTrue(elem.is_visible(self.frame)) + assert elem.is_visible(self.frame) def test_visibility_hidden(self): """Check that elements with "visibility = hidden" are not visible.""" elem = get_webelem(QRect(0, 0, 10, 10), self.frame, visibility='hidden') - self.assertFalse(elem.is_visible(self.frame)) + assert not elem.is_visible(self.frame) def test_display_inline(self): """Check that elements with "display = inline" are visible.""" elem = get_webelem(QRect(0, 0, 10, 10), self.frame, display='inline') - self.assertTrue(elem.is_visible(self.frame)) + assert elem.is_visible(self.frame) def test_display_none(self): """Check that elements with "display = none" are not visible.""" elem = get_webelem(QRect(0, 0, 10, 10), self.frame, display='none') - self.assertFalse(elem.is_visible(self.frame)) + assert not elem.is_visible(self.frame) -class IsVisibleIframeTests(unittest.TestCase): +class TestIsVisibleIframe: """Tests for is_visible with a child frame. @@ -204,7 +206,8 @@ class IsVisibleIframeTests(unittest.TestCase): elem1-elem4: FakeWebElements to test. """ - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self, stubs): """Set up the following base situation. 0, 0 300, 0 @@ -236,64 +239,64 @@ class IsVisibleIframeTests(unittest.TestCase): def test_not_scrolled(self): """Test base situation.""" - self.assertTrue(self.frame.geometry().contains(self.iframe.geometry())) - self.assertTrue(self.elem1.is_visible(self.frame)) - self.assertTrue(self.elem2.is_visible(self.frame)) - self.assertFalse(self.elem3.is_visible(self.frame)) - self.assertTrue(self.elem4.is_visible(self.frame)) + assert self.frame.geometry().contains(self.iframe.geometry()) + assert self.elem1.is_visible(self.frame) + assert self.elem2.is_visible(self.frame) + assert not self.elem3.is_visible(self.frame) + assert self.elem4.is_visible(self.frame) def test_iframe_scrolled(self): """Scroll iframe down so elem3 gets visible and elem1/elem2 not.""" self.iframe.scrollPosition.return_value = QPoint(0, 100) - self.assertFalse(self.elem1.is_visible(self.frame)) - self.assertFalse(self.elem2.is_visible(self.frame)) - self.assertTrue(self.elem3.is_visible(self.frame)) - self.assertTrue(self.elem4.is_visible(self.frame)) + assert not self.elem1.is_visible(self.frame) + assert not self.elem2.is_visible(self.frame) + assert self.elem3.is_visible(self.frame) + assert self.elem4.is_visible(self.frame) def test_mainframe_scrolled_iframe_visible(self): """Scroll mainframe down so iframe is partly visible but elem1 not.""" self.frame.scrollPosition.return_value = QPoint(0, 50) geom = self.frame.geometry().translated(self.frame.scrollPosition()) - self.assertFalse(geom.contains(self.iframe.geometry())) - self.assertTrue(geom.intersects(self.iframe.geometry())) - self.assertFalse(self.elem1.is_visible(self.frame)) - self.assertTrue(self.elem2.is_visible(self.frame)) - self.assertFalse(self.elem3.is_visible(self.frame)) - self.assertTrue(self.elem4.is_visible(self.frame)) + assert not geom.contains(self.iframe.geometry()) + assert geom.intersects(self.iframe.geometry()) + assert not self.elem1.is_visible(self.frame) + assert self.elem2.is_visible(self.frame) + assert not self.elem3.is_visible(self.frame) + assert self.elem4.is_visible(self.frame) def test_mainframe_scrolled_iframe_invisible(self): """Scroll mainframe down so iframe is invisible.""" self.frame.scrollPosition.return_value = QPoint(0, 110) geom = self.frame.geometry().translated(self.frame.scrollPosition()) - self.assertFalse(geom.contains(self.iframe.geometry())) - self.assertFalse(geom.intersects(self.iframe.geometry())) - self.assertFalse(self.elem1.is_visible(self.frame)) - self.assertFalse(self.elem2.is_visible(self.frame)) - self.assertFalse(self.elem3.is_visible(self.frame)) - self.assertTrue(self.elem4.is_visible(self.frame)) + assert not geom.contains(self.iframe.geometry()) + assert not geom.intersects(self.iframe.geometry()) + assert not self.elem1.is_visible(self.frame) + assert not self.elem2.is_visible(self.frame) + assert not self.elem3.is_visible(self.frame) + assert self.elem4.is_visible(self.frame) -class IsWritableTests(unittest.TestCase): +class TestIsWritable: """Check is_writable.""" def test_writable(self): """Test a normal element.""" elem = get_webelem() - self.assertTrue(elem.is_writable()) + assert elem.is_writable() def test_disabled(self): """Test a disabled element.""" elem = get_webelem(attributes=['disabled']) - self.assertFalse(elem.is_writable()) + assert not elem.is_writable() def test_readonly(self): """Test a readonly element.""" elem = get_webelem(attributes=['readonly']) - self.assertFalse(elem.is_writable()) + assert not elem.is_writable() -class JavascriptEscapeTests(unittest.TestCase): +class TestJavascriptEscape: """Check javascript_escape. @@ -301,33 +304,30 @@ class JavascriptEscapeTests(unittest.TestCase): STRINGS: A list of (input, output) tuples. """ - STRINGS = ( + @pytest.mark.parametrize('before, after', [ ('foo\\bar', r'foo\\bar'), ('foo\nbar', r'foo\nbar'), ("foo'bar", r"foo\'bar"), ('foo"bar', r'foo\"bar'), - ) - - def test_fake_escape(self): + ]) + def test_fake_escape(self, before, after): """Test javascript escaping.""" - for before, after in self.STRINGS: - with self.subTest(before=before): - self.assertEqual(webelem.javascript_escape(before), after) + assert webelem.javascript_escape(before) == after -class GetChildFramesTests(unittest.TestCase): +class TestGetChildFrames: """Check get_child_frames.""" - def test_single_frame(self): + def test_single_frame(self, stubs): """Test get_child_frames with a single frame without children.""" frame = stubs.FakeChildrenFrame() children = webelem.get_child_frames(frame) - self.assertEqual(len(children), 1) - self.assertIs(children[0], frame) + assert len(children) == 1 + assert children[0] is frame frame.childFrames.assert_called_once_with() - def test_one_level(self): + def test_one_level(self, stubs): r"""Test get_child_frames with one level of children. o parent @@ -338,15 +338,15 @@ class GetChildFramesTests(unittest.TestCase): child2 = stubs.FakeChildrenFrame() parent = stubs.FakeChildrenFrame([child1, child2]) children = webelem.get_child_frames(parent) - self.assertEqual(len(children), 3) - self.assertIs(children[0], parent) - self.assertIs(children[1], child1) - self.assertIs(children[2], child2) + assert len(children) == 3 + assert children[0] is parent + assert children[1] is child1 + assert children[2] is child2 parent.childFrames.assert_called_once_with() child1.childFrames.assert_called_once_with() child2.childFrames.assert_called_once_with() - def test_multiple_levels(self): + def test_multiple_levels(self, stubs): r"""Test get_child_frames with multiple levels of children. o root @@ -360,189 +360,191 @@ class GetChildFramesTests(unittest.TestCase): stubs.FakeChildrenFrame(second[2:4])] root = stubs.FakeChildrenFrame(first) children = webelem.get_child_frames(root) - self.assertEqual(len(children), 7) - self.assertIs(children[0], root) + assert len(children) == 7 + assert children[0] is root for frame in [root] + first + second: - with self.subTest(frame=frame): - frame.childFrames.assert_called_once_with() + frame.childFrames.assert_called_once_with() -class IsEditableTests(unittest.TestCase): +class TestIsEditable: """Tests for is_editable.""" - def setUp(self): + @pytest.yield_fixture(autouse=True) + def setup(self): + old_config = webelem.config webelem.config = None + yield + webelem.config = old_config + + @pytest.fixture + def stub_config(self, stubs, 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 def test_input_plain(self): """Test with plain input element.""" elem = get_webelem(tagname='input') - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_input_text(self): """Test with text input element.""" elem = get_webelem(tagname='input', attributes={'type': 'text'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_input_text_caps(self): """Test with text input element with caps attributes.""" elem = get_webelem(tagname='INPUT', attributes={'TYPE': 'TEXT'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_input_email(self): """Test with email input element.""" elem = get_webelem(tagname='input', attributes={'type': 'email'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_input_url(self): """Test with url input element.""" elem = get_webelem(tagname='input', attributes={'type': 'url'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_input_tel(self): """Test with tel input element.""" elem = get_webelem(tagname='input', attributes={'type': 'tel'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_input_number(self): """Test with number input element.""" elem = get_webelem(tagname='input', attributes={'type': 'number'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_input_password(self): """Test with password input element.""" elem = get_webelem(tagname='input', attributes={'type': 'password'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_input_search(self): """Test with search input element.""" elem = get_webelem(tagname='input', attributes={'type': 'search'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_input_button(self): """Button should not be editable.""" elem = get_webelem(tagname='input', attributes={'type': 'button'}) - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_input_checkbox(self): """Checkbox should not be editable.""" elem = get_webelem(tagname='input', attributes={'type': 'checkbox'}) - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_textarea(self): """Test textarea element.""" elem = get_webelem(tagname='textarea') - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_select(self): """Test selectbox.""" elem = get_webelem(tagname='select') - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_input_disabled(self): """Test disabled input element.""" elem = get_webelem(tagname='input', attributes={'disabled': None}) - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_input_readonly(self): """Test readonly input element.""" elem = get_webelem(tagname='input', attributes={'readonly': None}) - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_textarea_disabled(self): """Test disabled textarea element.""" elem = get_webelem(tagname='textarea', attributes={'disabled': None}) - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_textarea_readonly(self): """Test readonly textarea element.""" elem = get_webelem(tagname='textarea', attributes={'readonly': None}) - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() - @mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( - {'input': {'insert-mode-on-plugins': True}})) - def test_embed_true(self): + def test_embed_true(self, stub_config): """Test embed-element with insert-mode-on-plugins true.""" + stub_config.data['input']['insert-mode-on-plugins'] = True elem = get_webelem(tagname='embed') - self.assertTrue(elem.is_editable()) + assert elem.is_editable() - @mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( - {'input': {'insert-mode-on-plugins': True}})) - def test_applet_true(self): + def test_applet_true(self, stub_config): """Test applet-element with insert-mode-on-plugins true.""" + stub_config.data['input']['insert-mode-on-plugins'] = True elem = get_webelem(tagname='applet') - self.assertTrue(elem.is_editable()) + assert elem.is_editable() - @mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( - {'input': {'insert-mode-on-plugins': False}})) - def test_embed_false(self): + def test_embed_false(self, stub_config): """Test embed-element with insert-mode-on-plugins false.""" + stub_config.data['input']['insert-mode-on-plugins'] = False elem = get_webelem(tagname='embed') - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() - @mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( - {'input': {'insert-mode-on-plugins': False}})) - def test_applet_false(self): + def test_applet_false(self, stub_config): """Test applet-element with insert-mode-on-plugins false.""" + stub_config.data['input']['insert-mode-on-plugins'] = False elem = get_webelem(tagname='applet') - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_object_no_type(self): """Test object-element without type.""" elem = get_webelem(tagname='object') - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_object_image(self): """Test object-element with image type.""" elem = get_webelem(tagname='object', attributes={'type': 'image/gif'}) - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() - @mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( - {'input': {'insert-mode-on-plugins': True}})) - def test_object_application(self): + def test_object_application(self, stub_config): """Test object-element with application type.""" + stub_config.data['input']['insert-mode-on-plugins'] = True elem = get_webelem(tagname='object', attributes={'type': 'application/foo'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() - @mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( - {'input': {'insert-mode-on-plugins': False}})) - def test_object_application_false(self): + def test_object_application_false(self, stub_config): """Test object-element with application type but not ...-on-plugins.""" + stub_config.data['input']['insert-mode-on-plugins'] = False elem = get_webelem(tagname='object', attributes={'type': 'application/foo'}) - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() - @mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( - {'input': {'insert-mode-on-plugins': True}})) - def test_object_classid(self): + def test_object_classid(self, stub_config): """Test object-element with classid.""" + stub_config.data['input']['insert-mode-on-plugins'] = True elem = get_webelem(tagname='object', attributes={'type': 'foo', 'classid': 'foo'}) - self.assertTrue(elem.is_editable()) + assert elem.is_editable() - @mock.patch('qutebrowser.browser.webelem.config', new=stubs.ConfigStub( - {'input': {'insert-mode-on-plugins': False}})) - def test_object_classid_false(self): + def test_object_classid_false(self, stub_config): """Test object-element with classid but not insert-mode-on-plugins.""" + stub_config.data['input']['insert-mode-on-plugins'] = False elem = get_webelem(tagname='object', attributes={'type': 'foo', 'classid': 'foo'}) - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_div_empty(self): """Test div-element without class.""" elem = get_webelem(tagname='div') - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_div_noneditable(self): """Test div-element with non-editable class.""" elem = get_webelem(tagname='div', classes='foo-kix-bar') - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_div_xik(self): """Test div-element with xik class.""" elem = get_webelem(tagname='div', classes='foo kix-foo') - self.assertTrue(elem.is_editable()) + assert elem.is_editable() def test_div_xik_caps(self): """Test div-element with xik class in caps. @@ -550,13 +552,9 @@ class IsEditableTests(unittest.TestCase): This tests if classes are case sensitive as they should. """ elem = get_webelem(tagname='div', classes='KIX-FOO') - self.assertFalse(elem.is_editable()) + assert not elem.is_editable() def test_div_codemirror(self): """Test div-element with codemirror class.""" elem = get_webelem(tagname='div', classes='foo CodeMirror-foo') - self.assertTrue(elem.is_editable()) - - -if __name__ == '__main__': - unittest.main() + assert elem.is_editable() diff --git a/qutebrowser/test/config/test_config.py b/tests/config/test_config.py similarity index 69% rename from qutebrowser/test/config/test_config.py rename to tests/config/test_config.py index 1680a5289..6bee98ee4 100644 --- a/qutebrowser/test/config/test_config.py +++ b/tests/config/test_config.py @@ -22,27 +22,25 @@ import os import os.path -import unittest import configparser -import tempfile import types -import shutil import argparse from unittest import mock from PyQt5.QtCore import QObject from PyQt5.QtGui import QColor +import pytest from qutebrowser.config import config, configexc -from qutebrowser.test import helpers from qutebrowser.utils import objreg, standarddir -class ConfigParserTests(unittest.TestCase): +class TestConfigParser: """Test reading of ConfigParser.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.cp = configparser.ConfigParser(interpolation=None, comment_prefixes='#') self.cp.optionxform = lambda opt: opt # be case-insensitive @@ -52,43 +50,41 @@ class ConfigParserTests(unittest.TestCase): """Test a simple option which is not transformed.""" self.cp.read_dict({'general': {'ignore-case': 'false'}}) self.cfg._from_cp(self.cp) - self.assertFalse(self.cfg.get('general', 'ignore-case')) + assert not self.cfg.get('general', 'ignore-case') def test_transformed_section_old(self): """Test a transformed section with the old name.""" self.cp.read_dict({'permissions': {'allow-plugins': 'true'}}) self.cfg._from_cp(self.cp) - self.assertTrue(self.cfg.get('content', 'allow-plugins')) + assert self.cfg.get('content', 'allow-plugins') def test_transformed_section_new(self): """Test a transformed section with the new name.""" self.cp.read_dict({'content': {'allow-plugins': 'true'}}) self.cfg._from_cp(self.cp) - self.assertTrue(self.cfg.get('content', 'allow-plugins')) + assert self.cfg.get('content', 'allow-plugins') def test_transformed_option_old(self): """Test a transformed option with the old name.""" - # WORKAROUND for unknown PyQt bug - # Instance of 'str' has no 'name' member self.cp.read_dict({'colors': {'tab.fg.odd': 'pink'}}) self.cfg._from_cp(self.cp) - self.assertEqual(self.cfg.get('colors', 'tabs.fg.odd').name(), - QColor('pink').name()) + actual = self.cfg.get('colors', 'tabs.fg.odd').name() + expected = QColor('pink').name() + assert actual == expected def test_transformed_option_new(self): """Test a transformed section with the new name.""" - # WORKAROUND for unknown PyQt bug - # Instance of 'str' has no 'name' member self.cp.read_dict({'colors': {'tabs.fg.odd': 'pink'}}) self.cfg._from_cp(self.cp) - self.assertEqual(self.cfg.get('colors', 'tabs.fg.odd').name(), - QColor('pink').name()) + actual = self.cfg.get('colors', 'tabs.fg.odd').name() + expected = QColor('pink').name() + assert actual == expected def test_invalid_value(self): """Test setting an invalid value.""" self.cp.read_dict({'general': {'ignore-case': 'invalid'}}) self.cfg._from_cp(self.cp) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.cfg._validate_all() def test_invalid_value_interpolated(self): @@ -96,7 +92,7 @@ class ConfigParserTests(unittest.TestCase): self.cp.read_dict({'general': {'ignore-case': 'smart', 'wrap-search': '${ignore-case}'}}) self.cfg._from_cp(self.cp) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.cfg._validate_all() def test_interpolation(self): @@ -104,8 +100,8 @@ class ConfigParserTests(unittest.TestCase): self.cp.read_dict({'general': {'ignore-case': 'false', 'wrap-search': '${ignore-case}'}}) self.cfg._from_cp(self.cp) - self.assertFalse(self.cfg.get('general', 'ignore-case')) - self.assertFalse(self.cfg.get('general', 'wrap-search')) + assert not self.cfg.get('general', 'ignore-case') + assert not self.cfg.get('general', 'wrap-search') def test_interpolation_cross_section(self): """Test setting an interpolated value from another section.""" @@ -116,50 +112,50 @@ class ConfigParserTests(unittest.TestCase): } ) self.cfg._from_cp(self.cp) - self.assertFalse(self.cfg.get('general', 'ignore-case')) - self.assertFalse(self.cfg.get('network', 'do-not-track')) + assert not self.cfg.get('general', 'ignore-case') + assert not self.cfg.get('network', 'do-not-track') def test_invalid_interpolation(self): """Test an invalid interpolation.""" self.cp.read_dict({'general': {'ignore-case': '${foo}'}}) self.cfg._from_cp(self.cp) - with self.assertRaises(configparser.InterpolationError): + with pytest.raises(configparser.InterpolationError): self.cfg._validate_all() def test_invalid_interpolation_syntax(self): """Test an invalid interpolation syntax.""" self.cp.read_dict({'general': {'ignore-case': '${'}}) - with self.assertRaises(configexc.InterpolationSyntaxError): + with pytest.raises(configexc.InterpolationSyntaxError): self.cfg._from_cp(self.cp) def test_invalid_section(self): """Test an invalid section.""" self.cp.read_dict({'foo': {'bar': 'baz'}}) - with self.assertRaises(configexc.NoSectionError): + with pytest.raises(configexc.NoSectionError): self.cfg._from_cp(self.cp) def test_invalid_option(self): """Test an invalid option.""" self.cp.read_dict({'general': {'bar': 'baz'}}) - with self.assertRaises(configexc.NoOptionError): + with pytest.raises(configexc.NoOptionError): self.cfg._from_cp(self.cp) def test_invalid_section_relaxed(self): """Test an invalid section with relaxed=True.""" self.cp.read_dict({'foo': {'bar': 'baz'}}) self.cfg._from_cp(self.cp, relaxed=True) - with self.assertRaises(configexc.NoSectionError): + with pytest.raises(configexc.NoSectionError): self.cfg.get('foo', 'bar') # pylint: disable=bad-config-call def test_invalid_option_relaxed(self): """Test an invalid option with relaxed=True.""" self.cp.read_dict({'general': {'bar': 'baz'}}) self.cfg._from_cp(self.cp, relaxed=True) - with self.assertRaises(configexc.NoOptionError): + with pytest.raises(configexc.NoOptionError): self.cfg.get('general', 'bar') # pylint: disable=bad-config-call -class DefaultConfigTests(unittest.TestCase): +class TestDefaultConfig: """Test validating of the default config.""" @@ -169,40 +165,32 @@ class DefaultConfigTests(unittest.TestCase): conf._validate_all() -class ConfigInitTests(unittest.TestCase): +class TestConfigInit: """Test initializing of the config.""" - def setUp(self): - self.temp_dir = tempfile.mkdtemp() - self.conf_path = os.path.join(self.temp_dir, 'config') - self.data_path = os.path.join(self.temp_dir, 'data') - self.cache_path = os.path.join(self.temp_dir, 'cache') - os.mkdir(self.conf_path) - os.mkdir(self.data_path) - os.mkdir(self.cache_path) + @pytest.yield_fixture(autouse=True) + def setup(self, tmpdir): + self.conf_path = (tmpdir / 'config').ensure(dir=1) + self.data_path = (tmpdir / 'data').ensure(dir=1) + self.cache_path = (tmpdir / 'cache').ensure(dir=1) self.env = { - 'XDG_CONFIG_HOME': self.conf_path, - 'XDG_DATA_HOME': self.data_path, - 'XDG_CACHE_HOME': self.cache_path, + 'XDG_CONFIG_HOME': str(self.conf_path), + 'XDG_DATA_HOME': str(self.data_path), + 'XDG_CACHE_HOME': str(self.cache_path), } objreg.register('app', QObject()) objreg.register('save-manager', mock.MagicMock()) args = argparse.Namespace(relaxed_config=False) objreg.register('args', args) - - def tearDown(self): - shutil.rmtree(self.temp_dir) + yield objreg.global_registry.clear() - def test_config_none(self): + def test_config_none(self, monkeypatch): """Test initializing with config path set to None.""" args = types.SimpleNamespace(confdir='') - with helpers.environ_set_temp(self.env): - standarddir.init(args) - config.init() - self.assertFalse(os.listdir(self.conf_path)) - - -if __name__ == '__main__': - unittest.main() + for k, v in self.env.items(): + monkeypatch.setenv(k, v) + standarddir.init(args) + config.init() + assert not os.listdir(str(self.conf_path)) diff --git a/qutebrowser/test/config/test_configtypes.py b/tests/config/test_configtypes.py similarity index 69% rename from qutebrowser/test/config/test_configtypes.py rename to tests/config/test_configtypes.py index 022d89c46..0b6ea21b7 100644 --- a/qutebrowser/test/config/test_configtypes.py +++ b/tests/config/test_configtypes.py @@ -18,21 +18,20 @@ """Tests for qutebrowser.config.configtypes.""" -import unittest import re import collections import os.path import base64 -from unittest import mock - -from qutebrowser.config import configtypes, configexc -from qutebrowser.test import stubs, helpers -from qutebrowser.utils import debug, utils +import itertools +import pytest from PyQt5.QtCore import QUrl from PyQt5.QtGui import QColor, QFont from PyQt5.QtNetwork import QNetworkProxy +from qutebrowser.config import configtypes, configexc +from qutebrowser.utils import debug, utils + class Font(QFont): @@ -70,72 +69,80 @@ class NetworkProxy(QNetworkProxy): password=self.password()) -class ValidValuesTest(unittest.TestCase): +@pytest.fixture +def os_path(mocker): + """Fixture that mocks and returns os.path from the configtypes module.""" + return mocker.patch('qutebrowser.config.configtypes.os.path', + autospec=True) + + +class TestValidValues: """Test ValidValues.""" def test_contains_without_desc(self): """Test __contains__ without a description.""" vv = configtypes.ValidValues('foo', 'bar') - self.assertIn('foo', vv) - self.assertNotIn("baz", vv) + assert 'foo' in vv + assert 'baz' not in vv def test_contains_with_desc(self): """Test __contains__ with a description.""" vv = configtypes.ValidValues(('foo', "foo desc"), ('bar', "bar desc")) - self.assertIn('foo', vv) - self.assertIn('bar', vv) - self.assertNotIn("baz", vv) + assert 'foo' in vv + assert 'bar' in vv + assert 'baz' not in vv def test_contains_mixed_desc(self): """Test __contains__ with mixed description.""" vv = configtypes.ValidValues(('foo', "foo desc"), 'bar') - self.assertIn('foo', vv) - self.assertIn('bar', vv) - self.assertNotIn("baz", vv) + assert 'foo' in vv + assert 'bar' in vv + assert 'baz' not in vv def test_iter_without_desc(self): """Test __iter__ without a description.""" vv = configtypes.ValidValues('foo', 'bar') - self.assertEqual(list(vv), ['foo', 'bar']) + assert list(vv) == ['foo', 'bar'] def test_iter_with_desc(self): """Test __iter__ with a description.""" vv = configtypes.ValidValues(('foo', "foo desc"), ('bar', "bar desc")) - self.assertEqual(list(vv), ['foo', 'bar']) + assert list(vv) == ['foo', 'bar'] def test_iter_with_mixed_desc(self): """Test __iter__ with mixed description.""" vv = configtypes.ValidValues(('foo', "foo desc"), 'bar') - self.assertEqual(list(vv), ['foo', 'bar']) + assert list(vv) == ['foo', 'bar'] def test_descriptions(self): """Test descriptions.""" vv = configtypes.ValidValues(('foo', "foo desc"), ('bar', "bar desc"), 'baz') - self.assertEqual(vv.descriptions['foo'], "foo desc") - self.assertEqual(vv.descriptions['bar'], "bar desc") - self.assertNotIn('baz', vv.descriptions) + assert vv.descriptions['foo'] == "foo desc" + assert vv.descriptions['bar'] == "bar desc" + assert 'baz' not in vv.descriptions -class BaseTypeTests(unittest.TestCase): +class TestBaseType: """Test BaseType.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.BaseType() def test_transform(self): """Test transform with a value.""" - self.assertEqual(self.t.transform("foobar"), "foobar") + assert self.t.transform("foobar") == "foobar" def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_validate_not_implemented(self): """Test validate without valid_values set.""" - with self.assertRaises(NotImplementedError): + with pytest.raises(NotImplementedError): self.t.validate("foo") def test_validate_none_ok(self): @@ -147,40 +154,38 @@ class BaseTypeTests(unittest.TestCase): """Test validate with valid_values set.""" self.t.valid_values = configtypes.ValidValues('foo', 'bar') self.t.validate('bar') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('baz') def test_complete_none(self): """Test complete with valid_values not set.""" - self.assertIsNone(self.t.complete()) + assert self.t.complete() is None def test_complete_without_desc(self): """Test complete with valid_values set without description.""" self.t.valid_values = configtypes.ValidValues('foo', 'bar') - self.assertEqual(self.t.complete(), [('foo', ''), ('bar', '')]) + assert self.t.complete() == [('foo', ''), ('bar', '')] def test_complete_with_desc(self): """Test complete with valid_values set with description.""" self.t.valid_values = configtypes.ValidValues(('foo', "foo desc"), ('bar', "bar desc")) - self.assertEqual(self.t.complete(), [('foo', "foo desc"), - ('bar', "bar desc")]) + assert self.t.complete() == [('foo', "foo desc"), ('bar', "bar desc")] def test_complete_mixed_desc(self): """Test complete with valid_values set with mixed description.""" self.t.valid_values = configtypes.ValidValues(('foo', "foo desc"), 'bar') - self.assertEqual(self.t.complete(), [('foo', "foo desc"), - ('bar', "")]) + assert self.t.complete() == [('foo', "foo desc"), ('bar', "")] -class StringTests(unittest.TestCase): +class TestString: """Test String.""" def test_minlen_toosmall(self): """Test __init__ with a minlen < 1.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): configtypes.String(minlen=0) def test_minlen_ok(self): @@ -189,7 +194,7 @@ class StringTests(unittest.TestCase): def test_maxlen_toosmall(self): """Test __init__ with a maxlen < 1.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): configtypes.String(maxlen=0) def test_maxlen_ok(self): @@ -198,13 +203,13 @@ class StringTests(unittest.TestCase): def test_minlen_gt_maxlen(self): """Test __init__ with a minlen bigger than the maxlen.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): configtypes.String(minlen=2, maxlen=1) def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" t = configtypes.String() - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate("") def test_validate_empty_none_ok(self): @@ -222,15 +227,15 @@ class StringTests(unittest.TestCase): t = configtypes.String(forbidden='xyz') t.validate("foobar") t.validate("foXbar") - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate("foybar") - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate("foxbar") def test_validate_minlen_toosmall(self): """Test validate with a minlen and a too short string.""" t = configtypes.String(minlen=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('f') def test_validate_minlen_ok(self): @@ -241,7 +246,7 @@ class StringTests(unittest.TestCase): def test_validate_maxlen_toolarge(self): """Test validate with a maxlen and a too long string.""" t = configtypes.String(maxlen=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('fob') def test_validate_maxlen_ok(self): @@ -258,22 +263,23 @@ class StringTests(unittest.TestCase): def test_validate_range_bad(self): """Test validate with both min/maxlen and a bad string.""" t = configtypes.String(minlen=2, maxlen=3) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('f') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('fooo') def test_transform(self): """Test if transform doesn't alter the value.""" t = configtypes.String() - self.assertEqual(t.transform('foobar'), 'foobar') + assert t.transform('foobar') == 'foobar' -class ListTests(unittest.TestCase): +class TestList: """Test List.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.List() def test_validate_single(self): @@ -286,7 +292,7 @@ class ListTests(unittest.TestCase): def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -296,70 +302,70 @@ class ListTests(unittest.TestCase): def test_validate_empty_item(self): """Test validate with empty item and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('foo,,bar') def test_validate_empty_item_none_ok(self): """Test validate with empty item and none_ok = True.""" t = configtypes.List(none_ok=True) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('foo,,bar') def test_transform_single(self): """Test transform with a single value.""" - self.assertEqual(self.t.transform('foo'), ['foo']) + assert self.t.transform('foo') == ['foo'] def test_transform_more(self): """Test transform with multiple values.""" - self.assertEqual(self.t.transform('foo,bar,baz'), - ['foo', 'bar', 'baz']) + assert self.t.transform('foo,bar,baz') == ['foo', 'bar', 'baz'] def test_transform_empty(self): """Test transform with an empty value.""" - self.assertEqual(self.t.transform(''), None) + assert self.t.transform('') is None -class BoolTests(unittest.TestCase): +class TestBool: """Test Bool.""" + # https://bitbucket.org/logilab/pylint/issue/511/ + # pylint: disable=undefined-variable + TESTS = {True: ['1', 'yes', 'YES', 'true', 'TrUe', 'on'], False: ['0', 'no', 'NO', 'false', 'FaLsE', 'off']} INVALID = ['10', 'yess', 'false_'] - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.Bool() - def test_transform(self): + @pytest.mark.parametrize('out, inp', + [(out, inp) for out, inputs in TESTS.items() for + inp in inputs]) + def test_transform(self, out, inp): """Test transform with all values.""" - for out, inputs in self.TESTS.items(): - for inp in inputs: - with self.subTest(inp=inp, out=out): - self.assertEqual(self.t.transform(inp), out, inp) + assert self.t.transform(inp) == out def test_transform_empty(self): """Test transform with none_ok = False and an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None - def test_validate_valid(self): + @pytest.mark.parametrize('val', itertools.chain(*TESTS.values())) + def test_validate_valid(self, val): """Test validate with valid values.""" - for vallist in self.TESTS.values(): - for val in vallist: - with self.subTest(val=val): - self.t.validate(val) + self.t.validate(val) - def test_validate_invalid(self): + @pytest.mark.parametrize('val', INVALID) + def test_validate_invalid(self, val): """Test validate with invalid values.""" - for val in self.INVALID: - with self.subTest(val=val): - with self.assertRaises(configexc.ValidationError): - self.t.validate(val) + with pytest.raises(configexc.ValidationError): + self.t.validate(val) def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" t = configtypes.Bool() - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('') def test_validate_empty_none_ok(self): @@ -368,13 +374,13 @@ class BoolTests(unittest.TestCase): t.validate('') -class IntTests(unittest.TestCase): +class TestInt: """Test Int.""" def test_minval_gt_maxval(self): """Test __init__ with a minval bigger than the maxval.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): configtypes.Int(minval=2, maxval=1) def test_validate_int(self): @@ -385,13 +391,13 @@ class IntTests(unittest.TestCase): def test_validate_string(self): """Test validate with something which isn't an int.""" t = configtypes.Int() - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('foobar') def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" t = configtypes.Int() - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('') def test_validate_empty_none_ok(self): @@ -402,7 +408,7 @@ class IntTests(unittest.TestCase): def test_validate_minval_toosmall(self): """Test validate with a minval and a too small int.""" t = configtypes.Int(minval=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1') def test_validate_minval_ok(self): @@ -413,7 +419,7 @@ class IntTests(unittest.TestCase): def test_validate_maxval_toolarge(self): """Test validate with a maxval and a too big int.""" t = configtypes.Int(maxval=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('3') def test_validate_maxval_ok(self): @@ -430,27 +436,28 @@ class IntTests(unittest.TestCase): def test_validate_range_bad(self): """Test validate with both min/maxval and a bad int.""" t = configtypes.Int(minval=2, maxval=3) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('4') def test_transform_none(self): """Test transform with an empty value.""" t = configtypes.Int(none_ok=True) - self.assertIsNone(t.transform('')) + assert t.transform('') is None def test_transform_int(self): """Test transform with an int.""" t = configtypes.Int() - self.assertEqual(t.transform('1337'), 1337) + assert t.transform('1337') == 1337 -class IntListTests(unittest.TestCase): +class TestIntList: """Test IntList.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.IntList() def test_validate_good(self): @@ -459,7 +466,7 @@ class IntListTests(unittest.TestCase): def test_validate_empty(self): """Test validate with an empty value.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('23,,42') def test_validate_empty_none_ok(self): @@ -469,30 +476,29 @@ class IntListTests(unittest.TestCase): def test_validate_bad(self): """Test validate with bad values.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('23,foo,1337') def test_transform_single(self): """Test transform with a single value.""" - self.assertEqual(self.t.transform('1337'), [1337]) + assert self.t.transform('1337') == [1337] def test_transform_more(self): """Test transform with multiple values.""" - self.assertEqual(self.t.transform('23,42,1337'), - [23, 42, 1337]) + assert self.t.transform('23,42,1337') == [23, 42, 1337] def test_transform_empty(self): """Test transform with an empty value.""" - self.assertEqual(self.t.transform('23,,42'), [23, None, 42]) + assert self.t.transform('23,,42') == [23, None, 42] -class FloatTests(unittest.TestCase): +class TestFloat: """Test Float.""" def test_minval_gt_maxval(self): """Test __init__ with a minval bigger than the maxval.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): configtypes.Float(minval=2, maxval=1) def test_validate_float(self): @@ -508,13 +514,13 @@ class FloatTests(unittest.TestCase): def test_validate_string(self): """Test validate with something which isn't an float.""" t = configtypes.Float() - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('foobar') def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" t = configtypes.Float() - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('') def test_validate_empty_none_ok(self): @@ -525,7 +531,7 @@ class FloatTests(unittest.TestCase): def test_validate_minval_toosmall(self): """Test validate with a minval and a too small float.""" t = configtypes.Float(minval=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1.99') def test_validate_minval_ok(self): @@ -536,7 +542,7 @@ class FloatTests(unittest.TestCase): def test_validate_maxval_toolarge(self): """Test validate with a maxval and a too big float.""" t = configtypes.Float(maxval=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('2.01') def test_validate_maxval_ok(self): @@ -553,47 +559,48 @@ class FloatTests(unittest.TestCase): def test_validate_range_bad(self): """Test validate with both min/maxval and a bad float.""" t = configtypes.Float(minval=2, maxval=3) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1.99') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('3.01') def test_transform_empty(self): """Test transform with an empty value.""" t = configtypes.Float() - self.assertIsNone(t.transform('')) + assert t.transform('') is None def test_transform_float(self): """Test transform with an float.""" t = configtypes.Float() - self.assertEqual(t.transform('1337.42'), 1337.42) + assert t.transform('1337.42') == 1337.42 def test_transform_int(self): """Test transform with an int.""" t = configtypes.Float() - self.assertEqual(t.transform('1337'), 1337.00) + assert t.transform('1337') == 1337.00 -class PercTests(unittest.TestCase): +class TestPerc: """Test Perc.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.Perc() def test_minval_gt_maxval(self): """Test __init__ with a minval bigger than the maxval.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): configtypes.Perc(minval=2, maxval=1) def test_validate_int(self): """Test validate with a normal int (not a percentage).""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('1337') def test_validate_string(self): """Test validate with something which isn't a percentage.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('1337%%') def test_validate_perc(self): @@ -602,7 +609,7 @@ class PercTests(unittest.TestCase): def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -613,7 +620,7 @@ class PercTests(unittest.TestCase): def test_validate_minval_toosmall(self): """Test validate with a minval and a too small percentage.""" t = configtypes.Perc(minval=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1%') def test_validate_minval_ok(self): @@ -624,7 +631,7 @@ class PercTests(unittest.TestCase): def test_validate_maxval_toolarge(self): """Test validate with a maxval and a too big percentage.""" t = configtypes.Perc(maxval=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('3%') def test_validate_maxval_ok(self): @@ -641,30 +648,31 @@ class PercTests(unittest.TestCase): def test_validate_range_bad(self): """Test validate with both min/maxval and a bad percentage.""" t = configtypes.Perc(minval=2, maxval=3) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1%') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('4%') def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_transform_perc(self): """Test transform with a percentage.""" - self.assertEqual(self.t.transform('1337%'), 1337) + assert self.t.transform('1337%') == 1337 -class PercListTests(unittest.TestCase): +class TestPercList: """Test PercList.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.PercList() def test_minval_gt_maxval(self): """Test __init__ with a minval bigger than the maxval.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): configtypes.PercList(minval=2, maxval=1) def test_validate_good(self): @@ -673,13 +681,13 @@ class PercListTests(unittest.TestCase): def test_validate_bad(self): """Test validate with bad values.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('23%,42%%,1337%') def test_validate_minval_toosmall(self): """Test validate with a minval and a too small percentage.""" t = configtypes.PercList(minval=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1%') def test_validate_minval_ok(self): @@ -690,7 +698,7 @@ class PercListTests(unittest.TestCase): def test_validate_maxval_toolarge(self): """Test validate with a maxval and a too big percentage.""" t = configtypes.PercList(maxval=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('3%') def test_validate_maxval_ok(self): @@ -707,14 +715,14 @@ class PercListTests(unittest.TestCase): def test_validate_range_bad(self): """Test validate with both min/maxval and a bad percentage.""" t = configtypes.PercList(minval=2, maxval=3) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1%') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('4%') def test_validate_empty(self): """Test validate with an empty value.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('23%,,42%') def test_validate_empty_none_ok(self): @@ -724,37 +732,38 @@ class PercListTests(unittest.TestCase): def test_transform_single(self): """Test transform with a single value.""" - self.assertEqual(self.t.transform('1337%'), [1337]) + assert self.t.transform('1337%') == [1337] def test_transform_more(self): """Test transform with multiple values.""" - self.assertEqual(self.t.transform('23%,42%,1337%'), [23, 42, 1337]) + assert self.t.transform('23%,42%,1337%') == [23, 42, 1337] def test_transform_empty(self): """Test transform with an empty value.""" - self.assertEqual(self.t.transform('23%,,42%'), [23, None, 42]) + assert self.t.transform('23%,,42%') == [23, None, 42] -class PercOrIntTests(unittest.TestCase): +class TestPercOrInt: """Test PercOrInt.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.PercOrInt() def test_minint_gt_maxint(self): """Test __init__ with a minint bigger than the maxint.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): configtypes.PercOrInt(minint=2, maxint=1) def test_minperc_gt_maxperc(self): """Test __init__ with a minperc bigger than the maxperc.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): configtypes.PercOrInt(minperc=2, maxperc=1) def test_validate_string(self): """Test validate with something which isn't a percentage.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('1337%%') def test_validate_perc(self): @@ -767,7 +776,7 @@ class PercOrIntTests(unittest.TestCase): def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -778,7 +787,7 @@ class PercOrIntTests(unittest.TestCase): def test_validate_minint_toosmall(self): """Test validate with a minint and a too small int.""" t = configtypes.PercOrInt(minint=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1') def test_validate_minint_ok(self): @@ -789,7 +798,7 @@ class PercOrIntTests(unittest.TestCase): def test_validate_maxint_toolarge(self): """Test validate with a maxint and a too big int.""" t = configtypes.PercOrInt(maxint=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('3') def test_validate_maxint_ok(self): @@ -806,15 +815,15 @@ class PercOrIntTests(unittest.TestCase): def test_validate_int_range_bad(self): """Test validate with both min/maxint and a bad int.""" t = configtypes.PercOrInt(minint=2, maxint=3) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('4') def test_validate_minperc_toosmall(self): """Test validate with a minperc and a too small perc.""" t = configtypes.PercOrInt(minperc=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1%') def test_validate_minperc_ok(self): @@ -825,7 +834,7 @@ class PercOrIntTests(unittest.TestCase): def test_validate_maxperc_toolarge(self): """Test validate with a maxperc and a too big perc.""" t = configtypes.PercOrInt(maxperc=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('3%') def test_validate_maxperc_ok(self): @@ -842,9 +851,9 @@ class PercOrIntTests(unittest.TestCase): def test_validate_perc_range_bad(self): """Test validate with both min/maxperc and a bad perc.""" t = configtypes.PercOrInt(minperc=2, maxperc=3) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1%') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('4%') def test_validate_both_range_int(self): @@ -861,30 +870,31 @@ class PercOrIntTests(unittest.TestCase): def test_transform_none(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_transform_perc(self): """Test transform with a percentage.""" - self.assertEqual(self.t.transform('1337%'), '1337%') + assert self.t.transform('1337%') == '1337%' def test_transform_int(self): """Test transform with an int.""" - self.assertEqual(self.t.transform('1337'), '1337') + assert self.t.transform('1337') == '1337' -@mock.patch('qutebrowser.config.configtypes.cmdutils', new=stubs.FakeCmdUtils( - {'cmd1': stubs.FakeCommand("desc 1"), - 'cmd2': stubs.FakeCommand("desc 2")})) -class CommandTests(unittest.TestCase): +class TestCommand: """Test Command.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self, mocker, stubs): self.t = configtypes.Command() + cmd_utils = stubs.FakeCmdUtils({'cmd1': stubs.FakeCommand("desc 1"), + 'cmd2': stubs.FakeCommand("desc 2")}) + mocker.patch('qutebrowser.config.configtypes.cmdutils', new=cmd_utils) def test_validate_empty(self): """Test validate with an empty string.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -904,34 +914,37 @@ class CommandTests(unittest.TestCase): def test_validate_invalid_command(self): """Test validate with an invalid command.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('cmd3') def test_validate_invalid_command_args(self): """Test validate with an invalid command and arguments.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('cmd3 foo bar') def test_transform(self): """Make sure transform doesn't alter values.""" - self.assertEqual(self.t.transform('foo bar'), 'foo bar') + assert self.t.transform('foo bar') == 'foo bar' def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_complete(self): """Test complete.""" items = self.t.complete() - self.assertEqual(len(items), 2) - self.assertIn(('cmd1', "desc 1"), items) - self.assertIn(('cmd2', "desc 2"), items) + assert len(items) == 2 + assert ('cmd1', "desc 1") in items + assert ('cmd2', "desc 2") in items -class ColorSystemTests(unittest.TestCase): +class TestColorSystem: """Test ColorSystem.""" + # https://bitbucket.org/logilab/pylint/issue/511/ + # pylint: disable=undefined-variable + TESTS = { 'RGB': QColor.Rgb, 'rgb': QColor.Rgb, @@ -942,12 +955,13 @@ class ColorSystemTests(unittest.TestCase): } INVALID = ['RRGB', 'HSV '] - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.ColorSystem() def test_validate_empty(self): """Test validate with an empty string.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -955,31 +969,28 @@ class ColorSystemTests(unittest.TestCase): t = configtypes.ColorSystem(none_ok=True) t.validate('') - def test_validate_valid(self): + @pytest.mark.parametrize('val', TESTS) + def test_validate_valid(self, val): """Test validate with valid values.""" - for val in self.TESTS: - with self.subTest(val=val): - self.t.validate(val) + self.t.validate(val) - def test_validate_invalid(self): + @pytest.mark.parametrize('val', INVALID) + def test_validate_invalid(self, val): """Test validate with invalid values.""" - for val in self.INVALID: - with self.subTest(val=val): - with self.assertRaises(configexc.ValidationError, msg=val): - self.t.validate(val) + with pytest.raises(configexc.ValidationError, msg=val): + self.t.validate(val) - def test_transform(self): + @pytest.mark.parametrize('k, v', TESTS.items()) + def test_transform(self, k, v): """Test transform.""" - for k, v in self.TESTS.items(): - with self.subTest(v=v): - self.assertEqual(self.t.transform(k), v, k) + assert self.t.transform(k) == v def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None -class QtColorTests(unittest.TestCase): +class TestQtColor: """Test QtColor.""" @@ -987,12 +998,13 @@ class QtColorTests(unittest.TestCase): INVALID = ['#00000G', '#123456789ABCD', '#12', 'foobar', '42'] INVALID_QT = ['rgb(0, 0, 0)'] - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.QtColor() def test_validate_empty(self): """Test validate with an empty string.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1000,37 +1012,35 @@ class QtColorTests(unittest.TestCase): t = configtypes.QtColor(none_ok=True) t.validate('') - def test_validate_valid(self): + @pytest.mark.parametrize('v', VALID) + def test_validate_valid(self, v): """Test validate with valid values.""" - for v in self.VALID: - with self.subTest(v=v): - self.t.validate(v) + self.t.validate(v) - def test_validate_invalid(self): + @pytest.mark.parametrize('val', INVALID + INVALID_QT) + def test_validate_invalid(self, val): """Test validate with invalid values.""" - for val in self.INVALID + self.INVALID_QT: - with self.subTest(val=val): - with self.assertRaises(configexc.ValidationError, msg=val): - self.t.validate(val) + with pytest.raises(configexc.ValidationError, msg=val): + self.t.validate(val) - def test_transform(self): + @pytest.mark.parametrize('v', VALID) + def test_transform(self, v): """Test transform.""" - for v in self.VALID: - with self.subTest(v=v): - self.assertEqual(self.t.transform(v), QColor(v), v) + assert self.t.transform(v) == QColor(v) def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None -class CssColorTests(QtColorTests): +class TestCssColor(TestQtColor): """Test CssColor.""" - VALID = QtColorTests.VALID + ['-foobar(42)'] + VALID = TestQtColor.VALID + ['-foobar(42)'] - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.CssColor() def test_validate_empty_none_ok(self): @@ -1038,18 +1048,22 @@ class CssColorTests(QtColorTests): t = configtypes.CssColor(none_ok=True) t.validate('') - def test_transform(self): + @pytest.mark.parametrize('v', VALID) + def test_validate_valid(self, v): + """Test validate with valid values.""" + super().test_validate_valid(v) + + @pytest.mark.parametrize('v', VALID) + def test_transform(self, v): """Make sure transform doesn't alter the value.""" - for v in self.VALID: - with self.subTest(v=v): - self.assertEqual(self.t.transform(v), v, v) + assert self.t.transform(v) == v -class QssColorTests(QtColorTests): +class TestQssColor(TestQtColor): """Test QssColor.""" - VALID = QtColorTests.VALID + [ + VALID = TestQtColor.VALID + [ 'rgba(255, 255, 255, 255)', 'hsv(359, 255, 255)', 'hsva(359, 255, 255, 255)', 'hsv(10%, 10%, 10%)', 'qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 white, stop: 0.4 ' @@ -1059,10 +1073,11 @@ class QssColorTests(QtColorTests): 'qradialgradient(cx:0, cy:0, radius: 1, fx:0.5, fy:0.5, stop:0 ' 'white, stop:1 green)' ] - INVALID = QtColorTests.INVALID + ['rgb(1, 2, 3, 4)', 'foo(1, 2, 3)'] + INVALID = TestQtColor.INVALID + ['rgb(1, 2, 3, 4)', 'foo(1, 2, 3)'] INVALID_QT = [] - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.QssColor() def test_validate_empty_none_ok(self): @@ -1070,21 +1085,33 @@ class QssColorTests(QtColorTests): t = configtypes.QssColor(none_ok=True) t.validate('') - def test_transform(self): + @pytest.mark.parametrize('v', VALID) + def test_validate_valid(self, v): + """Test validate with valid values.""" + super().test_validate_valid(v) + + @pytest.mark.parametrize('val', INVALID + INVALID_QT) + def test_validate_invalid(self, val): + """Test validate with invalid values.""" + super().test_validate_invalid(val) + + @pytest.mark.parametrize('v', VALID) + def test_transform(self, v): """Make sure transform doesn't alter the value.""" - for v in self.VALID: - with self.subTest(v=v): - self.assertEqual(self.t.transform(v), v, v) + assert self.t.transform(v) == v FontDesc = collections.namedtuple('FontDesc', ['style', 'weight', 'pt', 'px', 'family']) -class FontTests(unittest.TestCase): +class TestFont: """Test Font/QtFont.""" + # https://bitbucket.org/logilab/pylint/issue/511/ + # pylint: disable=undefined-variable + TESTS = { # (style, weight, pointsize, pixelsize, family '"Foobar Neue"': @@ -1122,17 +1149,28 @@ class FontTests(unittest.TestCase): 'bold italic 10pt "Foobar Neue"': FontDesc(QFont.StyleItalic, QFont.Bold, 10, None, 'Foobar Neue'), } - INVALID = ['green "Foobar Neue"', 'italic green "Foobar Neue"', - 'bold bold "Foobar Neue"', 'bold italic "Foobar Neue"' - 'bold', '10pt 20px "Foobar Neue"'] - def setUp(self): + INVALID = [ + 'green "Foobar Neue"', + 'italic green "Foobar Neue"', + 'bold bold "Foobar Neue"', + 'bold italic "Foobar Neue"', + '10pt 20px "Foobar Neue"', + 'bold', + 'italic', + 'green', + '10pt', + '10pt ""', + ] + + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.Font() self.t2 = configtypes.QtFont() def test_validate_empty(self): """Test validate with an empty string.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1142,40 +1180,25 @@ class FontTests(unittest.TestCase): t.validate('') t2.validate('') - def test_validate_valid(self): + @pytest.mark.parametrize('val, attr', + itertools.product(TESTS, ['t', 't2'])) + def test_validate_valid(self, val, attr): """Test validate with valid values.""" - for val in self.TESTS: - with self.subTest(val=val): - with self.subTest(t="t1"): - self.t.validate(val) - with self.subTest(t="t2"): - self.t2.validate(val) + getattr(self, attr).validate(val) - # FIXME - # https://github.com/The-Compiler/qutebrowser/issues/103 - @unittest.expectedFailure - def test_validate_invalid(self): + @pytest.mark.parametrize('val, attr', + itertools.product(INVALID, ['t', 't2'])) + @pytest.mark.xfail(reason='FIXME: #103') + def test_validate_invalid(self, val, attr): """Test validate with invalid values.""" - for val in self.INVALID: - with self.subTest(val=val): - with self.subTest(t="t1"): - with self.assertRaises(configexc.ValidationError, - msg=val): - self.t.validate(val) - with self.subTest(t="t2"): - with self.assertRaises(configexc.ValidationError, - msg=val): - self.t2.validate(val) + with pytest.raises(configexc.ValidationError, msg=val): + getattr(self, attr).validate(val) - def test_transform(self): + @pytest.mark.parametrize('string, desc', TESTS.items()) + def test_transform(self, string, desc): """Test transform.""" - for string, desc in self.TESTS.items(): - with self.subTest(string=string, desc=desc): - with self.subTest(t="t1"): - self.assertEqual(self.t.transform(string), string, string) - with self.subTest(t="t2"): - self.assertEqual(Font(self.t2.transform(string)), - Font.fromdesc(desc), string) + assert self.t.transform(string) == string + assert Font(self.t2.transform(string)) == Font.fromdesc(desc) def test_transform_float(self): """Test QtFont's transform with a float as point size. @@ -1184,19 +1207,19 @@ class FontTests(unittest.TestCase): rounding as appropriate. """ value = Font(self.t2.transform('10.5pt "Foobar Neue"')) - self.assertEqual(value.family(), 'Foobar Neue') - self.assertEqual(value.weight(), QFont.Normal) - self.assertEqual(value.style(), QFont.StyleNormal) - self.assertGreaterEqual(value.pointSize(), 10) - self.assertLessEqual(value.pointSize(), 11) + assert value.family() == 'Foobar Neue' + assert value.weight() == QFont.Normal + assert value.style() == QFont.StyleNormal + assert value.pointSize() >= 10 + assert value.pointSize() <= 11 def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) - self.assertIsNone(self.t2.transform('')) + assert self.t.transform('') is None + assert self.t2.transform('') is None -class FontFamilyTests(unittest.TestCase): +class TestFontFamily: """Test FontFamily.""" @@ -1218,12 +1241,13 @@ class FontFamilyTests(unittest.TestCase): 'bold italic 10pt "Foobar Neue"', ] - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.FontFamily() def test_validate_empty(self): """Test validate with an empty string.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1231,34 +1255,33 @@ class FontFamilyTests(unittest.TestCase): t = configtypes.FontFamily(none_ok=True) t.validate('') - def test_validate_valid(self): + @pytest.mark.parametrize('val', TESTS) + def test_validate_valid(self, val): """Test validate with valid values.""" - for val in self.TESTS: - with self.subTest(val=val): - self.t.validate(val) + self.t.validate(val) - def test_validate_invalid(self): + @pytest.mark.parametrize('val', INVALID) + def test_validate_invalid(self, val): """Test validate with invalid values.""" - for val in self.INVALID: - with self.subTest(val=val): - with self.assertRaises(configexc.ValidationError, msg=val): - self.t.validate(val) + with pytest.raises(configexc.ValidationError): + self.t.validate(val) def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None -class RegexTests(unittest.TestCase): +class TestRegex: """Test Regex.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.Regex() def test_validate_empty(self): """Test validate with an empty string.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1272,23 +1295,24 @@ class RegexTests(unittest.TestCase): def test_validate_invalid(self): """Test validate with an invalid regex.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate(r'(foo|bar))?baz[fis]h') def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_transform(self): """Test transform.""" - self.assertEqual(self.t.transform(r'foobar'), re.compile(r'foobar')) + assert self.t.transform(r'foobar') == re.compile(r'foobar') -class RegexListTests(unittest.TestCase): +class TestRegexList: """Test RegexList.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.RegexList() def test_validate_good(self): @@ -1297,7 +1321,7 @@ class RegexListTests(unittest.TestCase): def test_validate_empty(self): """Test validate with an empty value.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate(r'(foo|bar),,1337{42}') def test_validate_empty_none_ok(self): @@ -1307,39 +1331,38 @@ class RegexListTests(unittest.TestCase): def test_validate_bad(self): """Test validate with bad values.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate(r'(foo|bar),((),1337{42}') def test_transform_single(self): """Test transform with a single value.""" - self.assertEqual(self.t.transform('foo'), [re.compile('foo')]) + assert self.t.transform('foo') == [re.compile('foo')] def test_transform_more(self): """Test transform with multiple values.""" - self.assertEqual(self.t.transform('foo,bar,baz'), - [re.compile('foo'), re.compile('bar'), - re.compile('baz')]) + expected = [re.compile('foo'), re.compile('bar'), re.compile('baz')] + assert self.t.transform('foo,bar,baz') == expected def test_transform_empty(self): """Test transform with an empty value.""" - self.assertEqual(self.t.transform('foo,,bar'), - [re.compile('foo'), None, re.compile('bar')]) + expected = [re.compile('foo'), None, re.compile('bar')] + assert self.t.transform('foo,,bar') == expected -@mock.patch('qutebrowser.config.configtypes.os.path', autospec=True) -class FileTests(unittest.TestCase): +class TestFile: """Test File.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.File() - def test_validate_empty(self, _os_path): + def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate("") - def test_validate_empty_none_ok(self, _os_path): + def test_validate_empty_none_ok(self): """Test validate with empty string and none_ok = True.""" t = configtypes.File(none_ok=True) t.validate("") @@ -1348,7 +1371,7 @@ class FileTests(unittest.TestCase): """Test validate with a file which does not exist.""" os_path.expanduser.side_effect = lambda x: x os_path.isfile.return_value = False - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('foobar') def test_validate_exists_abs(self, os_path): @@ -1363,7 +1386,7 @@ class FileTests(unittest.TestCase): os_path.expanduser.side_effect = lambda x: x os_path.isfile.return_value = True os_path.isabs.return_value = False - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('foobar') def test_validate_expanduser(self, os_path): @@ -1374,38 +1397,38 @@ class FileTests(unittest.TestCase): self.t.validate('~/foobar') os_path.expanduser.assert_called_once_with('~/foobar') - def test_validate_invalid_encoding(self, os_path): + def test_validate_invalid_encoding(self, os_path, unicode_encode_err): """Test validate with an invalid encoding, e.g. LC_ALL=C.""" - os_path.isfile.side_effect = helpers.unicode_encode_err - os_path.isabs.side_effect = helpers.unicode_encode_err - with self.assertRaises(configexc.ValidationError): + os_path.isfile.side_effect = unicode_encode_err + os_path.isabs.side_effect = unicode_encode_err + with pytest.raises(configexc.ValidationError): self.t.validate('foobar') def test_transform(self, os_path): """Test transform.""" os_path.expanduser.side_effect = lambda x: x.replace('~', '/home/foo') - self.assertEqual(self.t.transform('~/foobar'), '/home/foo/foobar') + assert self.t.transform('~/foobar') == '/home/foo/foobar' os_path.expanduser.assert_called_once_with('~/foobar') - def test_transform_empty(self, _os_path): + def test_transform_empty(self): """Test transform with none_ok = False and an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None -@mock.patch('qutebrowser.config.configtypes.os.path', autospec=True) -class DirectoryTests(unittest.TestCase): +class TestDirectory: """Test Directory.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.Directory() - def test_validate_empty(self, _os_path): + def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate("") - def test_validate_empty_none_ok(self, _os_path): + def test_validate_empty_none_ok(self): """Test validate with empty string and none_ok = True.""" t = configtypes.Directory(none_ok=True) t.validate("") @@ -1414,7 +1437,7 @@ class DirectoryTests(unittest.TestCase): """Test validate with a directory which does not exist.""" os_path.expanduser.side_effect = lambda x: x os_path.isdir.return_value = False - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('foobar') def test_validate_exists_abs(self, os_path): @@ -1429,7 +1452,7 @@ class DirectoryTests(unittest.TestCase): os_path.expanduser.side_effect = lambda x: x os_path.isdir.return_value = True os_path.isabs.return_value = False - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('foobar') def test_validate_expanduser(self, os_path): @@ -1441,41 +1464,42 @@ class DirectoryTests(unittest.TestCase): self.t.validate('~/foobar') os_path.expanduser.assert_called_once_with('~/foobar') - def test_validate_expandvars(self, os_path): + def test_validate_expandvars(self, os_path, monkeypatch): """Test if validate expands the user correctly.""" os_path.expandvars.side_effect = lambda x: x.replace('$BAR', '/home/foo/bar') os_path.expanduser.side_effect = lambda x: x os_path.isdir.side_effect = lambda path: path == '/home/foo/bar/foobar' os_path.isabs.return_value = True - with helpers.environ_set_temp({'BAR': '/home/foo/bar'}): - self.t.validate('$BAR/foobar') - os_path.expandvars.assert_called_once_with('$BAR/foobar') + monkeypatch.setenv('BAR', '/home/foo/bar') + self.t.validate('$BAR/foobar') + os_path.expandvars.assert_called_once_with('$BAR/foobar') - def test_validate_invalid_encoding(self, os_path): + def test_validate_invalid_encoding(self, os_path, unicode_encode_err): """Test validate with an invalid encoding, e.g. LC_ALL=C.""" - os_path.isdir.side_effect = helpers.unicode_encode_err - os_path.isabs.side_effect = helpers.unicode_encode_err - with self.assertRaises(configexc.ValidationError): + os_path.isdir.side_effect = unicode_encode_err + os_path.isabs.side_effect = unicode_encode_err + with pytest.raises(configexc.ValidationError): self.t.validate('foobar') def test_transform(self, os_path): """Test transform.""" os_path.expandvars.side_effect = lambda x: x os_path.expanduser.side_effect = lambda x: x.replace('~', '/home/foo') - self.assertEqual(self.t.transform('~/foobar'), '/home/foo/foobar') + assert self.t.transform('~/foobar') == '/home/foo/foobar' os_path.expanduser.assert_called_once_with('~/foobar') - def test_transform_empty(self, _os_path): + def test_transform_empty(self): """Test transform with none_ok = False and an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None -class WebKitByteTests(unittest.TestCase): +class TestWebKitByte: """Test WebKitBytes.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.WebKitBytes() def test_validate_empty(self): @@ -1497,18 +1521,18 @@ class WebKitByteTests(unittest.TestCase): def test_validate_int_negative(self): """Test validate with a negative int.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('-1') def test_validate_int_negative_suffix(self): """Test validate with a negative int with suffix.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('-1k') def test_validate_int_toobig(self): """Test validate with an int which is too big.""" t = configtypes.WebKitBytes(maxsize=10) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('11') def test_validate_int_not_toobig(self): @@ -1519,37 +1543,38 @@ class WebKitByteTests(unittest.TestCase): def test_validate_int_toobig_suffix(self): """Test validate with an int which is too big with suffix.""" t = configtypes.WebKitBytes(maxsize=10) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1k') def test_validate_int_invalid_suffix(self): """Test validate with an int with an invalid suffix.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('56x') def test_validate_int_double_suffix(self): """Test validate with an int with a double suffix.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('56kk') def test_transform_empty(self): """Test transform with none_ok = False and an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_transform_int(self): """Test transform with a simple value.""" - self.assertEqual(self.t.transform('10'), 10) + assert self.t.transform('10') == 10 def test_transform_int_suffix(self): """Test transform with a value with suffix.""" - self.assertEqual(self.t.transform('1k'), 1024) + assert self.t.transform('1k') == 1024 -class WebKitBytesListTests(unittest.TestCase): +class TestWebKitBytesList: """Test WebKitBytesList.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.WebKitBytesList() def test_validate_good(self): @@ -1560,15 +1585,15 @@ class WebKitBytesListTests(unittest.TestCase): def test_validate_bad(self): """Test validate with bad values.""" t = configtypes.WebKitBytesList() - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('23,56kk,1337') def test_validate_maxsize_toolarge(self): """Test validate with a maxsize and a too big size.""" t = configtypes.WebKitBytesList(maxsize=2) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('3') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('3k') def test_validate_maxsize_ok(self): @@ -1578,7 +1603,7 @@ class WebKitBytesListTests(unittest.TestCase): def test_validate_empty(self): """Test validate with an empty value.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('23,,42') def test_validate_empty_none_ok(self): @@ -1589,7 +1614,7 @@ class WebKitBytesListTests(unittest.TestCase): def test_validate_len_tooshort(self): """Test validate with a too short length.""" t = configtypes.WebKitBytesList(length=3) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1,2') def test_validate_len_ok(self): @@ -1600,32 +1625,33 @@ class WebKitBytesListTests(unittest.TestCase): def test_validate_len_toolong(self): """Test validate with a too long length.""" t = configtypes.WebKitBytesList(length=3) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('1,2,3,4') def test_transform_single(self): """Test transform with a single value.""" - self.assertEqual(self.t.transform('1k'), [1024]) + assert self.t.transform('1k') == [1024] def test_transform_more(self): """Test transform with multiple values.""" - self.assertEqual(self.t.transform('23,2k,1337'), [23, 2048, 1337]) + assert self.t.transform('23,2k,1337'), [23, 2048, 1337] def test_transform_empty(self): """Test transform with an empty value.""" - self.assertEqual(self.t.transform('23,,42'), [23, None, 42]) + assert self.t.transform('23,,42'), [23, None, 42] -class ShellCommandTests(unittest.TestCase): +class TestShellCommand: """Test ShellCommand.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.ShellCommand() def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate("") def test_validate_empty_none_ok(self): @@ -1645,37 +1671,38 @@ class ShellCommandTests(unittest.TestCase): def test_validate_placeholder_invalid(self): """Test validate with an invalid placeholder.""" t = configtypes.ShellCommand(placeholder='{}') - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('foo{} bar') def test_transform_single(self): """Test transform with a single word.""" - self.assertEqual(self.t.transform('foobar'), ['foobar']) + assert self.t.transform('foobar') == ['foobar'] def test_transform_double(self): """Test transform with two words.""" - self.assertEqual(self.t.transform('foobar baz'), ['foobar', 'baz']) + assert self.t.transform('foobar baz'), ['foobar', 'baz'] def test_transform_quotes(self): """Test transform with a quoted word.""" - self.assertEqual(self.t.transform('foo "bar baz" fish'), - ['foo', 'bar baz', 'fish']) + expected = ['foo', 'bar baz', 'fish'] + assert self.t.transform('foo "bar baz" fish') == expected def test_transform_empty(self): """Test transform with none_ok = False and an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None -class ProxyTests(unittest.TestCase): +class TestProxy: """Test Proxy.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.Proxy() def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1693,12 +1720,12 @@ class ProxyTests(unittest.TestCase): def test_validate_invalid(self): """Test validate with an invalid URL.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate(':') def test_validate_scheme(self): """Test validate with a URL with wrong scheme.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('ftp://example.com/') def test_validate_http(self): @@ -1715,76 +1742,79 @@ class ProxyTests(unittest.TestCase): def test_complete(self): """Test complete.""" - self.assertEqual(self.t.complete(), - [('system', "Use the system wide proxy."), - ('none', "Don't use any proxy"), - ('http://', 'HTTP proxy URL'), - ('socks://', 'SOCKS proxy URL')]) + actual = self.t.complete() + expected = [('system', "Use the system wide proxy."), + ('none', "Don't use any proxy"), + ('http://', 'HTTP proxy URL'), + ('socks://', 'SOCKS proxy URL')] + assert actual == expected def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_transform_system(self): """Test transform with system proxy.""" - self.assertIs(self.t.transform('system'), configtypes.SYSTEM_PROXY) + assert self.t.transform('system') is configtypes.SYSTEM_PROXY def test_transform_none(self): """Test transform with no proxy.""" - self.assertEqual(NetworkProxy(self.t.transform('none')), - NetworkProxy(QNetworkProxy.NoProxy)) + actual = NetworkProxy(self.t.transform('none')) + expected = NetworkProxy(QNetworkProxy.NoProxy) + assert actual == expected def test_transform_socks(self): """Test transform with a socks proxy.""" - proxy = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com') - val = NetworkProxy(self.t.transform('socks://example.com/')) - self.assertEqual(proxy, val) + actual = NetworkProxy(self.t.transform('socks://example.com/')) + expected = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com') + assert actual == expected def test_transform_socks5(self): """Test transform with a socks5 proxy.""" - proxy = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com') - val = NetworkProxy(self.t.transform('socks5://example.com')) - self.assertEqual(proxy, val) + actual = NetworkProxy(self.t.transform('socks5://example.com')) + expected = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com') + assert actual == expected def test_transform_http_port(self): """Test transform with a http proxy with set port.""" - proxy = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2342) - val = NetworkProxy(self.t.transform('socks5://example.com:2342')) - self.assertEqual(proxy, val) + actual = NetworkProxy(self.t.transform('socks5://example.com:2342')) + expected = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2342) + assert actual == expected def test_transform_socks_user(self): """Test transform with a socks proxy with set user.""" - proxy = NetworkProxy( + actual = NetworkProxy(self.t.transform('socks5://foo@example.com')) + expected = NetworkProxy( QNetworkProxy.Socks5Proxy, 'example.com', 0, 'foo') - val = NetworkProxy(self.t.transform('socks5://foo@example.com')) - self.assertEqual(proxy, val) + assert actual == expected def test_transform_socks_user_password(self): """Test transform with a socks proxy with set user/password.""" - proxy = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, - 'foo', 'bar') - val = NetworkProxy(self.t.transform('socks5://foo:bar@example.com')) - self.assertEqual(proxy, val) + actual = NetworkProxy(self.t.transform('socks5://foo:bar@example.com')) + expected = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, + 'foo', 'bar') + assert actual == expected def test_transform_socks_user_password_port(self): """Test transform with a socks proxy with set port/user/password.""" - proxy = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323, - 'foo', 'bar') - val = NetworkProxy( + actual = NetworkProxy( self.t.transform('socks5://foo:bar@example.com:2323')) - self.assertEqual(proxy, val) + expected = NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323, + 'foo', 'bar') + assert actual == expected -class SearchEngineNameTests(unittest.TestCase): +class TestSearchEngineName: """Test SearchEngineName.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.SearchEngineName() def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1794,23 +1824,24 @@ class SearchEngineNameTests(unittest.TestCase): def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_transform(self): """Test transform with a value.""" - self.assertEqual(self.t.transform("foobar"), "foobar") + assert self.t.transform("foobar") == "foobar" -class SearchEngineUrlTests(unittest.TestCase): +class TestSearchEngineUrl: """Test SearchEngineUrl.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.SearchEngineUrl() def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1820,7 +1851,7 @@ class SearchEngineUrlTests(unittest.TestCase): def test_validate_no_placeholder(self): """Test validate with no placeholder.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('foo') def test_validate(self): @@ -1829,28 +1860,29 @@ class SearchEngineUrlTests(unittest.TestCase): def test_validate_invalid_url(self): """Test validate with an invalid URL.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate(':{}') def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_transform(self): """Test transform with a value.""" - self.assertEqual(self.t.transform("foobar"), "foobar") + assert self.t.transform("foobar") == "foobar" -class FuzzyUrlTests(unittest.TestCase): +class TestFuzzyUrl: """Test FuzzyUrl.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.FuzzyUrl() def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1868,78 +1900,83 @@ class FuzzyUrlTests(unittest.TestCase): def test_validate_invalid_url(self): """Test validate with an invalid URL.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('::foo') def test_validate_invalid_search(self): """Test validate with an invalid search term.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('foo bar') def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_transform(self): """Test transform with a value.""" - self.assertEqual(self.t.transform("example.com"), - QUrl('http://example.com')) + assert self.t.transform("example.com") == QUrl('http://example.com') -class UserStyleSheetTests(unittest.TestCase): +class TestUserStyleSheet: """Test UserStyleSheet.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.UserStyleSheet() - @mock.patch('qutebrowser.config.configtypes.os.path', autospec=True) - def test_validate_invalid_encoding(self, os_path): + def test_validate_invalid_encoding(self, mocker, unicode_encode_err): """Test validate with an invalid encoding, e.g. LC_ALL=C.""" - os_path.isfile.side_effect = helpers.unicode_encode_err - os_path.isabs.side_effect = helpers.unicode_encode_err - with self.assertRaises(configexc.ValidationError): + os_path = mocker.patch('qutebrowser.config.configtypes.os.path', + autospec=True) + os_path.isfile.side_effect = unicode_encode_err + os_path.isabs.side_effect = unicode_encode_err + with pytest.raises(configexc.ValidationError): self.t.validate('foobar') def test_transform_empty(self): """Test transform with an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None def test_transform_file(self): """Test transform with a filename.""" path = os.path.join(os.path.sep, 'foo', 'bar') - self.assertEqual(self.t.transform(path), QUrl("file:///foo/bar")) + assert self.t.transform(path) == QUrl("file:///foo/bar") - def test_transform_file_expandvars(self): + def test_transform_file_expandvars(self, monkeypatch): """Test transform with a filename (expandvars).""" - with helpers.environ_set_temp({'FOO': 'foo'}): - path = os.path.join(os.path.sep, '$FOO', 'bar') - self.assertEqual(self.t.transform(path), QUrl("file:///foo/bar")) + monkeypatch.setenv('FOO', 'foo') + path = os.path.join(os.path.sep, '$FOO', 'bar') + assert self.t.transform(path) == QUrl("file:///foo/bar") def test_transform_base64(self): """Test transform with a data string.""" b64 = base64.b64encode(b"test").decode('ascii') url = QUrl("data:text/css;charset=utf-8;base64,{}".format(b64)) - self.assertEqual(self.t.transform("test"), url) + assert self.t.transform("test") == url -class AutoSearchTests(unittest.TestCase): +class TestAutoSearch: """Test AutoSearch.""" + # https://bitbucket.org/logilab/pylint/issue/511/ + # pylint: disable=undefined-variable + TESTS = { - 'naive': ['naive', 'NAIVE'] + BoolTests.TESTS[True], + 'naive': ['naive', 'NAIVE'] + TestBool.TESTS[True], 'dns': ['dns', 'DNS'], - False: BoolTests.TESTS[False], + False: TestBool.TESTS[False], } INVALID = ['ddns', 'foo'] - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.AutoSearch() def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1947,49 +1984,51 @@ class AutoSearchTests(unittest.TestCase): t = configtypes.AutoSearch(none_ok=True) t.validate('') - def test_validate_valid(self): + @pytest.mark.parametrize('val', [val for vallist in TESTS.values() + for val in vallist]) + def test_validate_valid(self, val): """Test validate with valid values.""" - for vallist in self.TESTS.values(): - for val in vallist: - with self.subTest(val=val): - self.t.validate(val) + self.t.validate(val) - def test_validate_invalid(self): + @pytest.mark.parametrize('val', INVALID) + def test_validate_invalid(self, val): """Test validate with invalid values.""" - for val in self.INVALID: - with self.subTest(val=val): - with self.assertRaises(configexc.ValidationError): - self.t.validate(val) + with pytest.raises(configexc.ValidationError): + self.t.validate(val) - def test_transform(self): + @pytest.mark.parametrize('out, inp', + [(out, inp) for (out, inputs) in TESTS.items() + for inp in inputs]) + def test_transform(self, out, inp): """Test transform with all values.""" - for out, inputs in self.TESTS.items(): - for inp in inputs: - with self.subTest(inp=inp): - self.assertEqual(self.t.transform(inp), out, inp) + assert self.t.transform(inp) == out def test_transform_empty(self): """Test transform with none_ok = False and an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None -class IgnoreCaseTests(unittest.TestCase): +class TestIgnoreCase: """Test IgnoreCase.""" + # https://bitbucket.org/logilab/pylint/issue/511/ + # pylint: disable=undefined-variable + TESTS = { 'smart': ['smart', 'SMART'], - True: BoolTests.TESTS[True], - False: BoolTests.TESTS[False], + True: TestBool.TESTS[True], + False: TestBool.TESTS[False], } INVALID = ['ssmart', 'foo'] - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.IgnoreCase() def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -1997,42 +2036,42 @@ class IgnoreCaseTests(unittest.TestCase): t = configtypes.IgnoreCase(none_ok=True) t.validate('') - def test_validate_valid(self): + @pytest.mark.parametrize('val', + [val for vallist in TESTS.values() for val in + vallist]) + def test_validate_valid(self, val): """Test validate with valid values.""" - for vallist in self.TESTS.values(): - for val in vallist: - with self.subTest(val=val): - self.t.validate(val) + self.t.validate(val) - def test_validate_invalid(self): + @pytest.mark.parametrize('val', INVALID) + def test_validate_invalid(self, val): """Test validate with invalid values.""" - for val in self.INVALID: - with self.subTest(val=val): - with self.assertRaises(configexc.ValidationError): - self.t.validate(val) + with pytest.raises(configexc.ValidationError): + self.t.validate(val) - def test_transform(self): + @pytest.mark.parametrize('out, inp', + [(out, inp) for (out, inputs) in TESTS.items() + for inp in inputs]) + def test_transform(self, out, inp): """Test transform with all values.""" - for out, inputs in self.TESTS.items(): - for inp in inputs: - with self.subTest(inp=inp): - self.assertEqual(self.t.transform(inp), out, inp) + assert self.t.transform(inp) == out def test_transform_empty(self): """Test transform with none_ok = False and an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None -class EncodingTests(unittest.TestCase): +class TestEncoding: """Test Encoding.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.Encoding() def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -2040,31 +2079,31 @@ class EncodingTests(unittest.TestCase): t = configtypes.Encoding(none_ok=True) t.validate('') - def test_validate_valid(self): + @pytest.mark.parametrize('val', ('utf-8', 'UTF-8', 'iso8859-1')) + def test_validate_valid(self, val): """Test validate with valid values.""" - for val in ('utf-8', 'UTF-8', 'iso8859-1'): - with self.subTest(val=val): - self.t.validate(val) + self.t.validate(val) def test_validate_invalid(self): """Test validate with an invalid value.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('blubber') def test_transform(self): """Test if transform doesn't alter the value.""" - self.assertEqual(self.t.transform('utf-8'), 'utf-8') + assert self.t.transform('utf-8') == 'utf-8' def test_transform_empty(self): """Test transform with none_ok = False and an empty value.""" - self.assertIsNone(self.t.transform('')) + assert self.t.transform('') is None -class UrlListTests(unittest.TestCase): +class TestUrlList: """Test UrlList.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.UrlList() def test_validate_single(self): @@ -2077,7 +2116,7 @@ class UrlListTests(unittest.TestCase): def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -2087,41 +2126,43 @@ class UrlListTests(unittest.TestCase): def test_validate_empty_item(self): """Test validate with empty item and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('foo,,bar') def test_validate_empty_item_none_ok(self): """Test validate with empty item and none_ok = True.""" t = configtypes.UrlList(none_ok=True) - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): t.validate('foo,,bar') def test_transform_single(self): """Test transform with a single value.""" - self.assertEqual(self.t.transform('http://qutebrowser.org/'), - [QUrl('http://qutebrowser.org/')]) + actual = self.t.transform('http://qutebrowser.org/') + expected = [QUrl('http://qutebrowser.org/')] + assert actual == expected def test_transform_more(self): """Test transform with multiple values.""" - self.assertEqual( - self.t.transform('http://qutebrowser.org/,http://heise.de/'), - [QUrl('http://qutebrowser.org/'), QUrl('http://heise.de/')]) + actual = self.t.transform('http://qutebrowser.org/,http://heise.de/') + expected = [QUrl('http://qutebrowser.org/'), QUrl('http://heise.de/')] + assert actual == expected def test_transform_empty(self): """Test transform with an empty value.""" - self.assertEqual(self.t.transform(''), None) + assert self.t.transform('') is None -class FormatStringTests(unittest.TestCase): +class TestFormatString: """Test FormatString.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.FormatString(fields=('foo', 'bar')) def test_transform(self): """Test if transform doesn't alter the value.""" - self.assertEqual(self.t.transform('foo {bar} baz'), 'foo {bar} baz') + assert self.t.transform('foo {bar} baz') == 'foo {bar} baz' def test_validate_simple(self): """Test validate with a simple string.""" @@ -2133,17 +2174,17 @@ class FormatStringTests(unittest.TestCase): def test_validate_invalid_placeholders(self): """Test validate with invalid placeholders.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('{foo} {bar} {baz}') def test_validate_invalid_placeholders_syntax(self): """Test validate with invalid placeholders syntax.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('{foo} {bar') def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate('') def test_validate_empty_none_ok(self): @@ -2152,16 +2193,17 @@ class FormatStringTests(unittest.TestCase): t.validate('') -class UserAgentTests(unittest.TestCase): +class TestUserAgent: """Test UserAgent.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.t = configtypes.UserAgent() def test_validate_empty(self): """Test validate with empty string and none_ok = False.""" - with self.assertRaises(configexc.ValidationError): + with pytest.raises(configexc.ValidationError): self.t.validate("") def test_validate_empty_none_ok(self): @@ -2175,8 +2217,4 @@ class UserAgentTests(unittest.TestCase): def test_transform(self): """Test if transform doesn't alter the value.""" - self.assertEqual(self.t.transform('foobar'), 'foobar') - - -if __name__ == '__main__': - unittest.main() + assert self.t.transform('foobar') == 'foobar' diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 000000000..d3411694c --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,78 @@ +# 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 . + +"""The qutebrowser test suite contest file.""" + +import pytest + + +@pytest.fixture(scope='session', autouse=True) +def app_and_logging(qapp): + """Initialize a QApplication and logging. + + This ensures that a QApplication is created and used by all tests. + """ + from log import init + init() + + +@pytest.fixture(scope='session') +def stubs(): + """Provide access to stub objects useful for testing.""" + import stubs + return stubs + + +@pytest.fixture(scope='session') +def unicode_encode_err(): + """Provide a fake UnicodeEncodeError exception.""" + return UnicodeEncodeError('ascii', # codec + '', # object + 0, # start + 2, # end + 'fake exception') # reason + + +@pytest.fixture +def webpage(): + """Get a new QWebPage object.""" + from PyQt5.QtWebKitWidgets import QWebPage + from PyQt5.QtNetwork import QNetworkAccessManager + + page = QWebPage() + nam = page.networkAccessManager() + nam.setNetworkAccessible(QNetworkAccessManager.NotAccessible) + return page + + +@pytest.fixture +def fake_keyevent_factory(): + """Fixture that when called will return a mock instance of a QKeyEvent.""" + from unittest import mock + from PyQt5.QtGui import QKeyEvent + + def fake_keyevent(key, modifiers=0, text=''): + """Generate a new fake QKeyPressEvent.""" + evtmock = mock.create_autospec(QKeyEvent, instance=True) + evtmock.key.return_value = key + evtmock.modifiers.return_value = modifiers + evtmock.text.return_value = text + return evtmock + + return fake_keyevent diff --git a/test/html/jsconfirm.html b/tests/html/jsconfirm.html similarity index 100% rename from test/html/jsconfirm.html rename to tests/html/jsconfirm.html diff --git a/test/html/jsprompt.html b/tests/html/jsprompt.html similarity index 100% rename from test/html/jsprompt.html rename to tests/html/jsprompt.html diff --git a/tests/keyinput/test_basekeyparser.py b/tests/keyinput/test_basekeyparser.py new file mode 100644 index 000000000..7164bffbd --- /dev/null +++ b/tests/keyinput/test_basekeyparser.py @@ -0,0 +1,289 @@ +# 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 . + +# pylint: disable=protected-access + +"""Tests for BaseKeyParser.""" + +import logging +from unittest import mock + +from PyQt5.QtCore import Qt +import pytest + +from qutebrowser.keyinput import basekeyparser +from qutebrowser.utils import objreg, log + + +CONFIG = {'input': {'timeout': 100}} + + +BINDINGS = {'test': {'': 'ctrla', + 'a': 'a', + 'ba': 'ba', + 'ax': 'ax', + 'ccc': 'ccc'}, + 'test2': {'foo': 'bar', '': 'ctrlx'}} + + +@pytest.yield_fixture +def fake_keyconfig(): + """Create a mock of a KeyConfiguration and register it into objreg.""" + fake_keyconfig = mock.Mock(spec=['get_bindings_for']) + fake_keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s] + objreg.register('key-config', fake_keyconfig) + yield + objreg.delete('key-config') + + +@pytest.fixture +def mock_timer(mocker, stubs): + """Mock the Timer class used by the usertypes module with a stub.""" + mocker.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer', + new=stubs.FakeTimer) + + +class TestSplitCount: + + """Test the _split_count method. + + Attributes: + kp: The BaseKeyParser we're testing. + """ + + @pytest.fixture(autouse=True) + def setup(self): + self.kp = basekeyparser.BaseKeyParser(0, supports_count=True) + + def test_onlycount(self): + """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') + + +@pytest.mark.usefixtures('fake_keyconfig', 'mock_timer') +class TestReadConfig: + + """Test reading the config.""" + + def test_read_config_invalid(self): + """Test reading config without setting it before.""" + kp = basekeyparser.BaseKeyParser(0) + with pytest.raises(ValueError): + kp.read_config() + + def test_read_config_valid(self): + """Test reading config.""" + kp = basekeyparser.BaseKeyParser(0, supports_count=True, + supports_chains=True) + kp.read_config('test') + assert 'ccc' in kp.bindings + assert 'ctrl+a' in kp.special_bindings + kp.read_config('test2') + assert 'ccc' not in kp.bindings + assert 'ctrl+a' not in kp.special_bindings + assert 'foo' in kp.bindings + assert 'ctrl+x' in kp.special_bindings + + +@pytest.mark.usefixtures('mock_timer') +class TestSpecialKeys: + + """Check execute() with special keys. + + Attributes: + kp: The BaseKeyParser to be tested. + """ + + @pytest.fixture(autouse=True) + def setup(self, caplog, fake_keyconfig): + self.kp = basekeyparser.BaseKeyParser(0) + self.kp.execute = mock.Mock() + with caplog.atLevel(logging.WARNING, log.keyboard.name): + # Ignoring keychain 'ccc' in mode 'test' because keychains are not + # supported there. + self.kp.read_config('test') + + def test_valid_key(self, fake_keyevent_factory): + """Test a valid special keyevent.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_A, Qt.ControlModifier)) + self.kp.handle(fake_keyevent_factory(Qt.Key_X, Qt.ControlModifier)) + self.kp.execute.assert_called_once_with('ctrla', self.kp.Type.special) + + def test_invalid_key(self, fake_keyevent_factory): + """Test an invalid special keyevent.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_A, (Qt.ControlModifier | + Qt.AltModifier))) + assert not self.kp.execute.called + + def test_keychain(self, fake_keyevent_factory): + """Test a keychain.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_B)) + self.kp.handle(fake_keyevent_factory(Qt.Key_A)) + assert not self.kp.execute.called + + +@pytest.mark.usefixtures('mock_timer') +class TestKeyChain: + + """Test execute() with keychain support. + + Attributes: + kp: The BaseKeyParser to be tested. + """ + + @pytest.fixture(autouse=True) + def setup(self, fake_keyconfig): + """Set up mocks and read the test config.""" + self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True, + supports_count=False) + self.kp.execute = mock.Mock() + self.kp.read_config('test') + + def test_valid_special_key(self, fake_keyevent_factory): + """Test valid special key.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_A, Qt.ControlModifier)) + self.kp.handle(fake_keyevent_factory(Qt.Key_X, Qt.ControlModifier)) + self.kp.execute.assert_called_once_with('ctrla', self.kp.Type.special) + assert self.kp._keystring == '' + + def test_invalid_special_key(self, fake_keyevent_factory): + """Test invalid special key.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_A, (Qt.ControlModifier | + Qt.AltModifier))) + assert not self.kp.execute.called + assert self.kp._keystring == '' + + def test_keychain(self, fake_keyevent_factory): + """Test valid keychain.""" + # Press 'x' which is ignored because of no match + self.kp.handle(fake_keyevent_factory(Qt.Key_X, text='x')) + # Then start the real chain + self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b')) + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a')) + 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): + """Test ambiguous keychain.""" + mocker.patch('qutebrowser.keyinput.basekeyparser.config', + new=stubs.ConfigStub(CONFIG)) + timer = self.kp._ambiguous_timer + assert not timer.isActive() + # We start with 'a' where the keychain gives us an ambiguous result. + # Then we check if the timer has been set up correctly + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a')) + assert not self.kp.execute.called + assert timer.isSingleShot() + assert timer.interval() == 100 + assert timer.isActive() + # Now we type an 'x' and check 'ax' has been executed and the timer + # stopped. + self.kp.handle(fake_keyevent_factory(Qt.Key_X, text='x')) + self.kp.execute.assert_called_once_with('ax', self.kp.Type.chain, None) + assert not timer.isActive() + assert self.kp._keystring == '' + + def test_invalid_keychain(self, fake_keyevent_factory): + """Test invalid keychain.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b')) + self.kp.handle(fake_keyevent_factory(Qt.Key_C, text='c')) + assert self.kp._keystring == '' + + +@pytest.mark.usefixtures('mock_timer') +class TestCount: + + """Test execute() with counts.""" + + @pytest.fixture(autouse=True) + def setup(self, fake_keyconfig): + self.kp = basekeyparser.BaseKeyParser(0, supports_chains=True, + supports_count=True) + self.kp.execute = mock.Mock() + self.kp.read_config('test') + + def test_no_count(self, fake_keyevent_factory): + """Test with no count added.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b')) + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a')) + self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None) + assert self.kp._keystring == '' + + def test_count_0(self, fake_keyevent_factory): + """Test with count=0.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_0, text='0')) + self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b')) + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a')) + self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, 0) + assert self.kp._keystring == '' + + def test_count_42(self, fake_keyevent_factory): + """Test with count=42.""" + self.kp.handle(fake_keyevent_factory(Qt.Key_4, text='4')) + self.kp.handle(fake_keyevent_factory(Qt.Key_2, text='2')) + self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b')) + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a')) + self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, 42) + assert self.kp._keystring == '' + + def test_count_42_invalid(self, fake_keyevent_factory): + """Test with count=42 and invalid command.""" + # Invalid call with ccx gets ignored + self.kp.handle(fake_keyevent_factory(Qt.Key_4, text='4')) + self.kp.handle(fake_keyevent_factory(Qt.Key_2, text='2')) + self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='c')) + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='c')) + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='x')) + assert not self.kp.execute.called + assert self.kp._keystring == '' + # Valid call with ccc gets the correct count + self.kp.handle(fake_keyevent_factory(Qt.Key_4, text='2')) + self.kp.handle(fake_keyevent_factory(Qt.Key_2, text='3')) + self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='c')) + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='c')) + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='c')) + self.kp.execute.assert_called_once_with('ccc', self.kp.Type.chain, 23) + assert self.kp._keystring == '' diff --git a/qutebrowser/test/keyinput/test_modeparsers.py b/tests/keyinput/test_modeparsers.py similarity index 66% rename from qutebrowser/test/keyinput/test_modeparsers.py rename to tests/keyinput/test_modeparsers.py index 910b2bea9..e177ad5a3 100644 --- a/qutebrowser/test/keyinput/test_modeparsers.py +++ b/tests/keyinput/test_modeparsers.py @@ -21,11 +21,10 @@ from PyQt5.QtCore import Qt -import unittest from unittest import mock +import pytest from qutebrowser.keyinput import modeparsers -from qutebrowser.test import stubs, helpers from qutebrowser.utils import objreg @@ -39,11 +38,7 @@ fake_keyconfig = mock.Mock(spec=['get_bindings_for']) fake_keyconfig.get_bindings_for.side_effect = lambda s: BINDINGS[s] -@mock.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer', - new=stubs.FakeTimer) -@mock.patch('qutebrowser.keyinput.modeparsers.config', - new=stubs.ConfigStub(CONFIG)) -class NormalKeyParserTests(unittest.TestCase): +class TestsNormalKeyParser: """Tests for NormalKeyParser. @@ -53,46 +48,47 @@ class NormalKeyParserTests(unittest.TestCase): # pylint: disable=protected-access - def setUp(self): + @pytest.yield_fixture(autouse=True) + def setup(self, mocker, stubs): """Set up mocks and read the test config.""" + mocker.patch('qutebrowser.keyinput.basekeyparser.usertypes.Timer', + new=stubs.FakeTimer) + mocker.patch('qutebrowser.keyinput.modeparsers.config', + new=stubs.ConfigStub(CONFIG)) + objreg.register('key-config', fake_keyconfig) self.kp = modeparsers.NormalKeyParser(0) self.kp.execute = mock.Mock() - - def tearDown(self): + yield objreg.delete('key-config') - def test_keychain(self): + def test_keychain(self, fake_keyevent_factory): """Test valid keychain.""" # Press 'x' which is ignored because of no match - self.kp.handle(helpers.fake_keyevent(Qt.Key_X, text='x')) + self.kp.handle(fake_keyevent_factory(Qt.Key_X, text='x')) # Then start the real chain - self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b')) - self.kp.handle(helpers.fake_keyevent(Qt.Key_A, text='a')) + self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b')) + self.kp.handle(fake_keyevent_factory(Qt.Key_A, text='a')) self.kp.execute.assert_called_once_with('ba', self.kp.Type.chain, None) - self.assertEqual(self.kp._keystring, '') + assert self.kp._keystring == '' - def test_partial_keychain_timeout(self): + def test_partial_keychain_timeout(self, fake_keyevent_factory): """Test partial keychain timeout.""" timer = self.kp._partial_timer - self.assertFalse(timer.isActive()) + assert not timer.isActive() # Press 'b' for a partial match. # Then we check if the timer has been set up correctly - self.kp.handle(helpers.fake_keyevent(Qt.Key_B, text='b')) - self.assertTrue(timer.isSingleShot()) - self.assertEqual(timer.interval(), 100) - self.assertTrue(timer.isActive()) + self.kp.handle(fake_keyevent_factory(Qt.Key_B, text='b')) + assert timer.isSingleShot() + assert timer.interval() == 100 + assert timer.isActive() - self.assertFalse(self.kp.execute.called) - self.assertEqual(self.kp._keystring, 'b') + assert not self.kp.execute.called + assert self.kp._keystring == 'b' # Now simulate a timeout and check the keystring has been cleared. keystring_updated_mock = mock.Mock() self.kp.keystring_updated.connect(keystring_updated_mock) timer.timeout.emit() - self.assertFalse(self.kp.execute.called) - self.assertEqual(self.kp._keystring, '') + assert not self.kp.execute.called + assert self.kp._keystring == '' keystring_updated_mock.assert_called_once_with('') - - -if __name__ == '__main__': - unittest.main() diff --git a/qutebrowser/test/log.py b/tests/log.py similarity index 100% rename from qutebrowser/test/log.py rename to tests/log.py diff --git a/tests/mainwindow/conftest.py b/tests/mainwindow/conftest.py new file mode 100644 index 000000000..47dcc5883 --- /dev/null +++ b/tests/mainwindow/conftest.py @@ -0,0 +1,52 @@ +# 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/qutebrowser/test/__init__.py b/tests/mainwindow/statusbar/test_percentage.py similarity index 52% rename from qutebrowser/test/__init__.py rename to tests/mainwindow/statusbar/test_percentage.py index 938eb6db3..80de6b66f 100644 --- a/qutebrowser/test/__init__.py +++ b/tests/mainwindow/statusbar/test_percentage.py @@ -17,19 +17,32 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see . -"""The qutebrowser test suite.""" -import atexit +"""Test Percentage widget.""" -from PyQt5.QtWidgets import QApplication +import pytest -from qutebrowser.test import log +from qutebrowser.mainwindow.statusbar.percentage import Percentage -# We create a singleton QApplication here. -qApp = QApplication([]) -qApp.setApplicationName('qutebrowser') -qApp.processEvents() -atexit.register(qApp.processEvents) -atexit.register(qApp.quit) -log.init() +@pytest.mark.parametrize('y, expected', [ + (0, '[top]'), + (100, '[bot]'), + (75, '[75%]'), + (25, '[25%]'), + (5, '[ 5%]'), +]) +def test_percentage_text(qtbot, y, expected): + """ + Test text displayed by the widget based on the y position of a page. + + Args: + qtbot: pytestqt.plugin.QtBot fixture + y: y position of the page as an int in the range [0, 100]. + parametrized. + expected: expected text given y position. parametrized. + """ + percentage = Percentage() + qtbot.add_widget(percentage) + percentage.set_perc(None, y=y) + assert percentage.text() == expected diff --git a/tests/mainwindow/statusbar/test_progress.py b/tests/mainwindow/statusbar/test_progress.py new file mode 100644 index 000000000..a3a9f1797 --- /dev/null +++ b/tests/mainwindow/statusbar/test_progress.py @@ -0,0 +1,74 @@ +# 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 Progress widget.""" + +from collections import namedtuple + +import pytest + +from qutebrowser.browser import webview +from qutebrowser.mainwindow.statusbar.progress import Progress + + +@pytest.fixture +def progress_widget(qtbot, default_config): + """Create a Progress widget and checks its initial state.""" + widget = Progress() + qtbot.add_widget(widget) + assert not widget.isVisible() + assert not widget.isTextVisible() + return widget + + +def test_load_started(progress_widget): + """Ensure the Progress widget reacts properly when the page starts loading. + + Args: + progress_widget: Progress widget that will be tested. + """ + progress_widget.on_load_started() + assert progress_widget.value() == 0 + assert progress_widget.isVisible() + + +# mock tab object +Tab = namedtuple('Tab', 'progress load_status') + + +@pytest.mark.parametrize('tab, expected_visible', [ + (Tab(15, webview.LoadStatus.loading), True), + (Tab(100, webview.LoadStatus.success), False), + (Tab(100, webview.LoadStatus.error), False), + (Tab(100, webview.LoadStatus.warn), False), + (Tab(100, webview.LoadStatus.none), False), +]) +def test_tab_changed(progress_widget, tab, expected_visible): + """Test that progress widget value and visibility state match expectations. + + This uses a dummy Tab object. + + Args: + progress_widget: Progress widget that will be tested. + """ + progress_widget.on_tab_changed(tab) + actual = progress_widget.value(), progress_widget.isVisible() + expected = tab.progress, expected_visible + assert actual == expected diff --git a/tests/mainwindow/statusbar/test_textbase.py b/tests/mainwindow/statusbar/test_textbase.py new file mode 100644 index 000000000..eadf9c46a --- /dev/null +++ b/tests/mainwindow/statusbar/test_textbase.py @@ -0,0 +1,53 @@ +# 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 TextBase widget.""" +from PyQt5.QtCore import Qt +import pytest + +from qutebrowser.mainwindow.statusbar.textbase import TextBase + + +@pytest.mark.parametrize('elidemode, check', [ + (Qt.ElideRight, lambda s: s.endswith('…')), + (Qt.ElideLeft, lambda s: s.startswith('…')), + (Qt.ElideMiddle, lambda s: '…' in s), + (Qt.ElideNone, lambda s: '…' not in s), +]) +def test_elided_text(qtbot, elidemode, check): + """Ensure that a widget too small to hold the entire label text will elide. + + It is difficult to check what is actually being drawn in a portable way, so + at least we ensure our customized methods are being called and the elided + string contains the horizontal ellipsis character. + + Args: + qtbot: pytestqt.plugin.QtBot fixture + elidemode: parametrized elide mode + check: function that receives the elided text and must return True + if the elipsis is placed correctly according to elidemode. + """ + label = TextBase(elidemode=elidemode) + qtbot.add_widget(label) + long_string = 'Hello world! ' * 20 + label.setText(long_string) + label.resize(100, 50) + label.show() + assert check(label._elided_text) # pylint: disable=protected-access diff --git a/qutebrowser/test/misc/test_crashdialog.py b/tests/misc/test_crashdialog.py similarity index 100% rename from qutebrowser/test/misc/test_crashdialog.py rename to tests/misc/test_crashdialog.py diff --git a/qutebrowser/test/misc/test_editor.py b/tests/misc/test_editor.py similarity index 60% rename from qutebrowser/test/misc/test_editor.py rename to tests/misc/test_editor.py index 7a707eaed..417253d21 100644 --- a/qutebrowser/test/misc/test_editor.py +++ b/tests/misc/test_editor.py @@ -23,19 +23,16 @@ import os import os.path -import unittest import logging from unittest import mock from PyQt5.QtCore import QProcess +import pytest from qutebrowser.misc import editor -from qutebrowser.test import stubs, helpers -@mock.patch('qutebrowser.misc.editor.QProcess', - new_callable=stubs.FakeQProcess) -class ArgTests(unittest.TestCase): +class TestArg: """Test argument handling. @@ -43,52 +40,48 @@ class ArgTests(unittest.TestCase): editor: The ExternalEditor instance to test. """ - def setUp(self): + @pytest.yield_fixture(autouse=True) + 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 - @mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( - {'general': {'editor': ['bin'], 'editor-encoding': 'utf-8'}})) - def test_simple_start_args(self, _proc_mock): + def test_simple_start_args(self): """Test starting editor without arguments.""" + self.config.data = { + 'general': {'editor': ['bin'], 'editor-encoding': 'utf-8'}} self.editor.edit("") self.editor._proc.start.assert_called_with("bin", []) - @mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( - {'general': {'editor': ['bin', 'foo', 'bar'], - 'editor-encoding': 'utf-8'}})) - def test_start_args(self, _proc_mock): + def test_start_args(self): """Test starting editor with static arguments.""" + self.config.data = {'general': {'editor': ['bin', 'foo', 'bar'], + 'editor-encoding': 'utf-8'}} self.editor.edit("") self.editor._proc.start.assert_called_with("bin", ["foo", "bar"]) - @mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( - {'general': {'editor': ['bin', 'foo', '{}', 'bar'], - 'editor-encoding': 'utf-8'}})) - def test_placeholder(self, _proc_mock): + def test_placeholder(self): """Test starting editor with placeholder argument.""" + self.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"]) - @mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( - {'general': {'editor': ['bin', 'foo{}bar'], - 'editor-encoding': 'utf-8'}})) - def test_in_arg_placeholder(self, _proc_mock): + def test_in_arg_placeholder(self): """Test starting editor with placeholder argument inside argument.""" + self.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 tearDown(self): - self.editor._cleanup() # pylint: disable=protected-access - -@mock.patch('qutebrowser.misc.editor.message', new=helpers.MessageModule()) -@mock.patch('qutebrowser.misc.editor.QProcess', - new_callable=stubs.FakeQProcess) -@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( - {'general': {'editor': [''], 'editor-encoding': 'utf-8'}})) -class FileHandlingTests(unittest.TestCase): +class TestFileHandling: """Test creation/deletion of tempfile. @@ -96,42 +89,49 @@ class FileHandlingTests(unittest.TestCase): editor: The ExternalEditor instance to test. """ - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self, mocker, stubs): + 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'}})) self.editor = editor.ExternalEditor(0) - def test_file_handling_closed_ok(self, _proc_mock): + def test_file_handling_closed_ok(self): """Test file handling when closing with an exit status == 0.""" self.editor.edit("") filename = self.editor._filename - self.assertTrue(os.path.exists(filename)) + assert os.path.exists(filename) self.editor.on_proc_closed(0, QProcess.NormalExit) - self.assertFalse(os.path.exists(filename)) + assert not os.path.exists(filename) - def test_file_handling_closed_error(self, _proc_mock): + def test_file_handling_closed_error(self, caplog): """Test file handling when closing with an exit status != 0.""" self.editor.edit("") filename = self.editor._filename - self.assertTrue(os.path.exists(filename)) - with self.assertLogs('message', logging.ERROR): + assert os.path.exists(filename) + with caplog.atLevel(logging.ERROR): self.editor.on_proc_closed(1, QProcess.NormalExit) - self.assertFalse(os.path.exists(filename)) + assert len(caplog.records()) == 2 + assert not os.path.exists(filename) - def test_file_handling_closed_crash(self, _proc_mock): + def test_file_handling_closed_crash(self, caplog): """Test file handling when closing with a crash.""" self.editor.edit("") filename = self.editor._filename - self.assertTrue(os.path.exists(filename)) - with self.assertLogs('message', logging.ERROR): + assert os.path.exists(filename) + with caplog.atLevel(logging.ERROR): self.editor.on_proc_error(QProcess.Crashed) + assert len(caplog.records()) == 2 self.editor.on_proc_closed(0, QProcess.CrashExit) - self.assertFalse(os.path.exists(filename)) + assert not os.path.exists(filename) -@mock.patch('qutebrowser.misc.editor.QProcess', - new_callable=stubs.FakeQProcess) -@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( - {'general': {'editor': [''], 'editor-encoding': 'utf-8'}})) -class TextModifyTests(unittest.TestCase): +class TestModifyTests: """Tests to test if the text gets saved/loaded correctly. @@ -139,7 +139,12 @@ class TextModifyTests(unittest.TestCase): editor: The ExternalEditor instance to test. """ - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self, mocker, stubs): + 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'}})) self.editor = editor.ExternalEditor(0) self.editor.editing_finished = mock.Mock() @@ -164,45 +169,40 @@ class TextModifyTests(unittest.TestCase): data = f.read() return data - def test_empty_input(self, _proc_mock): + def test_empty_input(self): """Test if an empty input gets modified correctly.""" self.editor.edit("") - self.assertEqual(self._read(), "") + assert self._read() == "" self._write("Hello") self.editor.on_proc_closed(0, QProcess.NormalExit) self.editor.editing_finished.emit.assert_called_with("Hello") - def test_simple_input(self, _proc_mock): + def test_simple_input(self): """Test if an empty input gets modified correctly.""" self.editor.edit("Hello") - self.assertEqual(self._read(), "Hello") + assert self._read() == "Hello" self._write("World") self.editor.on_proc_closed(0, QProcess.NormalExit) self.editor.editing_finished.emit.assert_called_with("World") - def test_umlaut(self, _proc_mock): + def test_umlaut(self): """Test if umlauts works correctly.""" self.editor.edit("Hällö Wörld") - self.assertEqual(self._read(), "Hällö Wörld") + assert self._read() == "Hällö Wörld" self._write("Überprüfung") self.editor.on_proc_closed(0, QProcess.NormalExit) self.editor.editing_finished.emit.assert_called_with("Überprüfung") - def test_unicode(self, _proc_mock): + def test_unicode(self): """Test if other UTF8 chars work correctly.""" self.editor.edit("\u2603") # Unicode snowman - self.assertEqual(self._read(), "\u2603") + assert self._read() == "\u2603" self._write("\u2601") # Cloud self.editor.on_proc_closed(0, QProcess.NormalExit) self.editor.editing_finished.emit.assert_called_with("\u2601") -@mock.patch('qutebrowser.misc.editor.QProcess', - new_callable=stubs.FakeQProcess) -@mock.patch('qutebrowser.misc.editor.message', new=helpers.MessageModule()) -@mock.patch('qutebrowser.misc.editor.config', new=stubs.ConfigStub( - {'general': {'editor': [''], 'editor-encoding': 'utf-8'}})) -class ErrorMessageTests(unittest.TestCase): +class TestErrorMessage: """Test if statusbar error messages get emitted correctly. @@ -210,21 +210,28 @@ class ErrorMessageTests(unittest.TestCase): editor: The ExternalEditor instance to test. """ - def setUp(self): + @pytest.yield_fixture(autouse=True) + def setup(self, mocker, stubs): + 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'}})) self.editor = editor.ExternalEditor(0) + yield + self.editor._cleanup() # pylint: disable=protected-access - def test_proc_error(self, _proc_mock): + def test_proc_error(self, caplog): """Test on_proc_error.""" self.editor.edit("") - with self.assertLogs('message', logging.ERROR): + with caplog.atLevel(logging.ERROR, 'message'): self.editor.on_proc_error(QProcess.Crashed) + assert len(caplog.records()) == 2 - def test_proc_return(self, _proc_mock): + def test_proc_return(self, caplog): """Test on_proc_finished with a bad exit status.""" self.editor.edit("") - with self.assertLogs('message', logging.ERROR): + with caplog.atLevel(logging.ERROR, 'message'): self.editor.on_proc_closed(1, QProcess.NormalExit) - - -if __name__ == '__main__': - unittest.main() + assert len(caplog.records()) == 3 diff --git a/qutebrowser/test/misc/test_lineparser.py b/tests/misc/test_lineparser.py similarity index 100% rename from qutebrowser/test/misc/test_lineparser.py rename to tests/misc/test_lineparser.py diff --git a/qutebrowser/test/misc/test_readline.py b/tests/misc/test_readline.py similarity index 61% rename from qutebrowser/test/misc/test_readline.py rename to tests/misc/test_readline.py index d64549a7a..9ace12b48 100644 --- a/qutebrowser/test/misc/test_readline.py +++ b/tests/misc/test_readline.py @@ -22,40 +22,40 @@ # pylint: disable=protected-access import inspect -import unittest from unittest import mock from PyQt5.QtWidgets import QLineEdit +import pytest from qutebrowser.misc import readline -from qutebrowser.test import stubs -@mock.patch('qutebrowser.misc.readline.QApplication', - new_callable=stubs.FakeQApplication) -class NoneWidgetTests(unittest.TestCase): +@pytest.fixture +def mocked_qapp(mocker, stubs): + """Fixture that mocks readline.QApplication and returns it.""" + return mocker.patch('qutebrowser.misc.readline.QApplication', + new_callable=stubs.FakeQApplication) - """Tests when the focused widget is None.""" - def setUp(self): +class TestNoneWidget: + + """Test if there are no exceptions when the widget is None.""" + + def test_none(self, mocked_qapp): + """Call each rl_* method with a None focusWidget.""" self.bridge = readline.ReadlineBridge() - - def test_none(self, qapp): - """Test if there are no exceptions when the widget is None.""" - qapp.focusWidget = mock.Mock(return_value=None) + mocked_qapp.focusWidget = mock.Mock(return_value=None) for name, method in inspect.getmembers(self.bridge, inspect.ismethod): - with self.subTest(name=name): - if name.startswith('rl_'): - method() + if name.startswith('rl_'): + method() -@mock.patch('qutebrowser.misc.readline.QApplication', - new_callable=stubs.FakeQApplication) -class ReadlineBridgeTest(unittest.TestCase): +class TestReadlineBridgeTest: """Tests for readline bridge.""" - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.qle = mock.Mock() self.qle.__class__ = QLineEdit self.bridge = readline.ReadlineBridge() @@ -64,104 +64,100 @@ class ReadlineBridgeTest(unittest.TestCase): """Set the value the fake QLineEdit should return for selectedText.""" self.qle.configure_mock(**{'selectedText.return_value': text}) - def test_rl_backward_char(self, qapp): + def test_rl_backward_char(self, mocked_qapp): """Test rl_backward_char.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self.bridge.rl_backward_char() self.qle.cursorBackward.assert_called_with(False) - def test_rl_forward_char(self, qapp): + def test_rl_forward_char(self, mocked_qapp): """Test rl_forward_char.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self.bridge.rl_forward_char() self.qle.cursorForward.assert_called_with(False) - def test_rl_backward_word(self, qapp): + def test_rl_backward_word(self, mocked_qapp): """Test rl_backward_word.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self.bridge.rl_backward_word() self.qle.cursorWordBackward.assert_called_with(False) - def test_rl_forward_word(self, qapp): + def test_rl_forward_word(self, mocked_qapp): """Test rl_forward_word.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self.bridge.rl_forward_word() self.qle.cursorWordForward.assert_called_with(False) - def test_rl_beginning_of_line(self, qapp): + def test_rl_beginning_of_line(self, mocked_qapp): """Test rl_beginning_of_line.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self.bridge.rl_beginning_of_line() self.qle.home.assert_called_with(False) - def test_rl_end_of_line(self, qapp): + def test_rl_end_of_line(self, mocked_qapp): """Test rl_end_of_line.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self.bridge.rl_end_of_line() self.qle.end.assert_called_with(False) - def test_rl_delete_char(self, qapp): + def test_rl_delete_char(self, mocked_qapp): """Test rl_delete_char.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self.bridge.rl_delete_char() self.qle.del_.assert_called_with() - def test_rl_backward_delete_char(self, qapp): + def test_rl_backward_delete_char(self, mocked_qapp): """Test rl_backward_delete_char.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self.bridge.rl_backward_delete_char() self.qle.backspace.assert_called_with() - def test_rl_unix_line_discard(self, qapp): + def test_rl_unix_line_discard(self, mocked_qapp): """Set a selected text, delete it, see if it comes back with yank.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self._set_selected_text("delete test") self.bridge.rl_unix_line_discard() self.qle.home.assert_called_with(True) - self.assertEqual(self.bridge._deleted[self.qle], "delete test") + assert self.bridge._deleted[self.qle] == "delete test" self.qle.del_.assert_called_with() self.bridge.rl_yank() self.qle.insert.assert_called_with("delete test") - def test_rl_kill_line(self, qapp): + def test_rl_kill_line(self, mocked_qapp): """Set a selected text, delete it, see if it comes back with yank.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self._set_selected_text("delete test") self.bridge.rl_kill_line() self.qle.end.assert_called_with(True) - self.assertEqual(self.bridge._deleted[self.qle], "delete test") + assert self.bridge._deleted[self.qle] == "delete test" self.qle.del_.assert_called_with() self.bridge.rl_yank() self.qle.insert.assert_called_with("delete test") - def test_rl_unix_word_rubout(self, qapp): + def test_rl_unix_word_rubout(self, mocked_qapp): """Set a selected text, delete it, see if it comes back with yank.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self._set_selected_text("delete test") self.bridge.rl_unix_word_rubout() self.qle.cursorWordBackward.assert_called_with(True) - self.assertEqual(self.bridge._deleted[self.qle], "delete test") + assert self.bridge._deleted[self.qle] == "delete test" self.qle.del_.assert_called_with() self.bridge.rl_yank() self.qle.insert.assert_called_with("delete test") - def test_rl_kill_word(self, qapp): + def test_rl_kill_word(self, mocked_qapp): """Set a selected text, delete it, see if it comes back with yank.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self._set_selected_text("delete test") self.bridge.rl_kill_word() self.qle.cursorWordForward.assert_called_with(True) - self.assertEqual(self.bridge._deleted[self.qle], "delete test") + assert self.bridge._deleted[self.qle] == "delete test" self.qle.del_.assert_called_with() self.bridge.rl_yank() self.qle.insert.assert_called_with("delete test") - def test_rl_yank_no_text(self, qapp): + def test_rl_yank_no_text(self, mocked_qapp): """Test yank without having deleted anything.""" - qapp.focusWidget = mock.Mock(return_value=self.qle) + mocked_qapp.focusWidget = mock.Mock(return_value=self.qle) self.bridge.rl_yank() - self.assertFalse(self.qle.insert.called) - - -if __name__ == '__main__': - unittest.main() + assert not self.qle.insert.called diff --git a/qutebrowser/test/misc/test_split.py b/tests/misc/test_split.py similarity index 100% rename from qutebrowser/test/misc/test_split.py rename to tests/misc/test_split.py diff --git a/qutebrowser/test/stubs.py b/tests/stubs.py similarity index 90% rename from qutebrowser/test/stubs.py rename to tests/stubs.py index 8b5419e6a..95c6833b8 100644 --- a/qutebrowser/test/stubs.py +++ b/tests/stubs.py @@ -21,6 +21,8 @@ """Fake objects/stubs.""" +import logging + from unittest import mock from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject @@ -37,8 +39,8 @@ class ConfigStub: data: The config data to return. """ - def __init__(self, data): - self.data = data + def __init__(self, data=None): + self.data = data or {} def section(self, name): """Get a section from the config. @@ -257,7 +259,7 @@ class FakeTimer(QObject): def setSingleShot(self, singleshot): self._singleshot = singleshot - def singleShot(self): + def isSingleShot(self): return self._singleshot def start(self): @@ -268,3 +270,20 @@ class FakeTimer(QObject): def isActive(self): return self._started + + +class MessageModule: + + """A drop-in replacement for qutebrowser.utils.message.""" + + def error(self, _win_id, message, _immediately=False): + """Log an error to the message logger.""" + logging.getLogger('message').error(message) + + def warning(self, _win_id, message, _immediately=False): + """Log a warning to the message logger.""" + logging.getLogger('message').warning(message) + + def info(self, _win_id, message, _immediately=True): + """Log an info message to the message logger.""" + logging.getLogger('message').info(message) diff --git a/tests/test_stubs.py b/tests/test_stubs.py new file mode 100644 index 000000000..aa7816f91 --- /dev/null +++ b/tests/test_stubs.py @@ -0,0 +1,107 @@ +# 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 . + + +"""Test test stubs.""" + +from unittest import mock + +import pytest + + +@pytest.fixture +def timer(stubs): + return stubs.FakeTimer() + + +def test_timeout(timer): + """Test whether timeout calls the functions.""" + func = mock.Mock() + func2 = mock.Mock() + timer.timeout.connect(func) + timer.timeout.connect(func2) + assert not func.called + assert not func2.called + timer.timeout.emit() + func.assert_called_once_with() + func2.assert_called_once_with() + + +def test_disconnect_all(timer): + """Test disconnect without arguments.""" + func = mock.Mock() + timer.timeout.connect(func) + timer.timeout.disconnect() + timer.timeout.emit() + assert not func.called + + +def test_disconnect_one(timer): + """Test disconnect with a single argument.""" + func = mock.Mock() + timer.timeout.connect(func) + timer.timeout.disconnect(func) + timer.timeout.emit() + assert not func.called + + +def test_disconnect_all_invalid(timer): + """Test disconnecting with no connections.""" + with pytest.raises(TypeError): + timer.timeout.disconnect() + + +def test_disconnect_one_invalid(timer): + """Test disconnecting with an invalid connection.""" + func1 = mock.Mock() + func2 = mock.Mock() + timer.timeout.connect(func1) + with pytest.raises(TypeError): + timer.timeout.disconnect(func2) + assert not func1.called + assert not func2.called + timer.timeout.emit() + func1.assert_called_once_with() + + +def test_singleshot(timer): + """Test setting singleShot.""" + assert not timer.isSingleShot() + timer.setSingleShot(True) + assert timer.isSingleShot() + timer.start() + assert timer.isActive() + timer.timeout.emit() + assert not timer.isActive() + + +def test_active(timer): + """Test isActive.""" + assert not timer.isActive() + timer.start() + assert timer.isActive() + timer.stop() + assert not timer.isActive() + + +def test_interval(timer): + """Test setting an interval.""" + assert timer.interval() == 0 + timer.setInterval(1000) + assert timer.interval() == 1000 diff --git a/qutebrowser/test/utils/debug/test_log_time.py b/tests/utils/debug/test_log_time.py similarity index 97% rename from qutebrowser/test/utils/debug/test_log_time.py rename to tests/utils/debug/test_log_time.py index f7da1a220..6ac475bd0 100644 --- a/qutebrowser/test/utils/debug/test_log_time.py +++ b/tests/utils/debug/test_log_time.py @@ -42,4 +42,4 @@ def test_log_time(caplog): assert match duration = float(match.group(1)) - assert 0.09 <= duration <= 0.11 + assert 0.08 <= duration <= 0.12 diff --git a/qutebrowser/test/utils/debug/test_qenum_key.py b/tests/utils/debug/test_qenum_key.py similarity index 98% rename from qutebrowser/test/utils/debug/test_qenum_key.py rename to tests/utils/debug/test_qenum_key.py index c111ff311..c43279a4a 100644 --- a/qutebrowser/test/utils/debug/test_qenum_key.py +++ b/tests/utils/debug/test_qenum_key.py @@ -21,7 +21,6 @@ import pytest -from PyQt5.QtCore import Qt from PyQt5.QtWidgets import QStyle, QFrame from qutebrowser.utils import debug diff --git a/qutebrowser/test/utils/debug/test_qflags_key.py b/tests/utils/debug/test_qflags_key.py similarity index 100% rename from qutebrowser/test/utils/debug/test_qflags_key.py rename to tests/utils/debug/test_qflags_key.py diff --git a/qutebrowser/test/utils/debug/test_signal.py b/tests/utils/debug/test_signal.py similarity index 97% rename from qutebrowser/test/utils/debug/test_signal.py rename to tests/utils/debug/test_signal.py index 32d994e78..58b1c13d9 100644 --- a/qutebrowser/test/utils/debug/test_signal.py +++ b/tests/utils/debug/test_signal.py @@ -21,12 +21,11 @@ import pytest -from qutebrowser.test import stubs from qutebrowser.utils import debug @pytest.fixture -def signal(): +def signal(stubs): """Fixture to provide a faked pyqtSignal.""" return stubs.FakeSignal() diff --git a/qutebrowser/test/utils/test_jinja.py b/tests/utils/test_jinja.py similarity index 100% rename from qutebrowser/test/utils/test_jinja.py rename to tests/utils/test_jinja.py diff --git a/qutebrowser/test/utils/test_log.py b/tests/utils/test_log.py similarity index 100% rename from qutebrowser/test/utils/test_log.py rename to tests/utils/test_log.py diff --git a/qutebrowser/test/utils/test_qtutils.py b/tests/utils/test_qtutils.py similarity index 100% rename from qutebrowser/test/utils/test_qtutils.py rename to tests/utils/test_qtutils.py diff --git a/tests/utils/test_standarddir.py b/tests/utils/test_standarddir.py new file mode 100644 index 000000000..e98ab80f9 --- /dev/null +++ b/tests/utils/test_standarddir.py @@ -0,0 +1,116 @@ +# 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 . + +"""Tests for qutebrowser.utils.standarddir.""" + +import os +import os.path +import sys + +from PyQt5.QtWidgets import QApplication +import pytest + +from qutebrowser.utils import standarddir + + +@pytest.yield_fixture(autouse=True) +def change_qapp_name(): + """Change the name of the QApplication instance. + + This changes the applicationName for all tests in this module to + "qutebrowser_test". + """ + old_name = QApplication.instance().applicationName() + QApplication.instance().setApplicationName('qutebrowser_test') + yield + QApplication.instance().setApplicationName(old_name) + + +@pytest.mark.skipif(not sys.platform.startswith("linux"), + reason="requires Linux") +class TestGetStandardDirLinux: + + """Tests for standarddir under Linux.""" + + def test_data_explicit(self, monkeypatch, tmpdir): + """Test data dir with XDG_DATA_HOME explicitly set.""" + monkeypatch.setenv('XDG_DATA_HOME', str(tmpdir)) + standarddir.init(None) + assert standarddir.data() == str(tmpdir / 'qutebrowser_test') + + def test_config_explicit(self, monkeypatch, tmpdir): + """Test config dir with XDG_CONFIG_HOME explicitly set.""" + monkeypatch.setenv('XDG_CONFIG_HOME', str(tmpdir)) + standarddir.init(None) + assert standarddir.config() == str(tmpdir / 'qutebrowser_test') + + def test_cache_explicit(self, monkeypatch, tmpdir): + """Test cache dir with XDG_CACHE_HOME explicitly set.""" + monkeypatch.setenv('XDG_CACHE_HOME', str(tmpdir)) + standarddir.init(None) + assert standarddir.cache() == str(tmpdir / 'qutebrowser_test') + + def test_data(self, monkeypatch, tmpdir): + """Test data dir with XDG_DATA_HOME not set.""" + monkeypatch.setenv('HOME', str(tmpdir)) + monkeypatch.delenv('XDG_DATA_HOME', raising=False) + standarddir.init(None) + expected = tmpdir / '.local' / 'share' / 'qutebrowser_test' + assert standarddir.data() == str(expected) + + def test_config(self, monkeypatch, tmpdir): + """Test config dir with XDG_CONFIG_HOME not set.""" + monkeypatch.setenv('HOME', str(tmpdir)) + monkeypatch.delenv('XDG_CONFIG_HOME', raising=False) + standarddir.init(None) + expected = tmpdir / '.config' / 'qutebrowser_test' + assert standarddir.config() == str(expected) + + def test_cache(self, monkeypatch, tmpdir): + """Test cache dir with XDG_CACHE_HOME not set.""" + monkeypatch.setenv('HOME', str(tmpdir)) + monkeypatch.delenv('XDG_CACHE_HOME', raising=False) + standarddir.init(None) + expected = tmpdir / '.cache' / 'qutebrowser_test' + assert standarddir.cache() == expected + + +@pytest.mark.skipif(not sys.platform.startswith("win"), + reason="requires Windows") +class TestGetStandardDirWindows: + + """Tests for standarddir under Windows.""" + + @pytest.fixture(autouse=True) + def reset_standarddir(self): + standarddir.init(None) + + def test_data(self): + """Test data dir.""" + expected = ['qutebrowser_test', 'data'] + assert standarddir.data().split(os.sep)[-2:] == expected + + def test_config(self): + """Test config dir.""" + assert standarddir.config().split(os.sep)[-1] == 'qutebrowser_test' + + def test_cache(self): + """Test cache dir.""" + expected = ['qutebrowser_test', 'cache'] + assert standarddir.cache().split(os.sep)[-2:] == expected diff --git a/qutebrowser/test/utils/test_urlutils.py b/tests/utils/test_urlutils.py similarity index 55% rename from qutebrowser/test/utils/test_urlutils.py rename to tests/utils/test_urlutils.py index 58868acb7..501ce2dd1 100644 --- a/qutebrowser/test/utils/test_urlutils.py +++ b/tests/utils/test_urlutils.py @@ -21,13 +21,10 @@ """Tests for qutebrowser.utils.urlutils.""" -import unittest -from unittest import mock - from PyQt5.QtCore import QUrl +import pytest from qutebrowser.utils import urlutils -from qutebrowser.test import stubs def get_config_stub(auto_search=True): @@ -45,7 +42,7 @@ def get_config_stub(auto_search=True): } -class SpecialURLTests(unittest.TestCase): +class TestSpecialURL: """Test is_special_url. @@ -65,65 +62,67 @@ class SpecialURLTests(unittest.TestCase): 'www.qutebrowser.org' ) - def test_special_urls(self): + @pytest.mark.parametrize('url', SPECIAL_URLS) + def test_special_urls(self, url): """Test special URLs.""" - for url in self.SPECIAL_URLS: - with self.subTest(url=url): - u = QUrl(url) - self.assertTrue(urlutils.is_special_url(u)) + u = QUrl(url) + assert urlutils.is_special_url(u) - def test_normal_urls(self): + @pytest.mark.parametrize('url', NORMAL_URLS) + def test_normal_urls(self, url): """Test non-special URLs.""" - for url in self.NORMAL_URLS: - with self.subTest(url=url): - u = QUrl(url) - self.assertFalse(urlutils.is_special_url(u)) + u = QUrl(url) + assert not urlutils.is_special_url(u) -@mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( - get_config_stub())) -class SearchUrlTests(unittest.TestCase): +class TestSearchUrl: """Test _get_search_url.""" + @pytest.fixture(autouse=True) + def mock_config(self, stubs, mocker): + """Fixture to patch urlutils.config with a stub.""" + mocker.patch('qutebrowser.utils.urlutils.config', + new=stubs.ConfigStub(get_config_stub())) + def test_default_engine(self): """Test default search engine.""" url = urlutils._get_search_url('testfoo') - self.assertEqual(url.host(), 'www.example.com') - self.assertEqual(url.query(), 'q=testfoo') + assert url.host() == 'www.example.com' + assert url.query() == 'q=testfoo' def test_engine_pre(self): """Test search engine name with one word.""" url = urlutils._get_search_url('test testfoo') - self.assertEqual(url.host(), 'www.qutebrowser.org') - self.assertEqual(url.query(), 'q=testfoo') + assert url.host() == 'www.qutebrowser.org' + assert url.query() == 'q=testfoo' def test_engine_pre_multiple_words(self): """Test search engine name with multiple words.""" url = urlutils._get_search_url('test testfoo bar foo') - self.assertEqual(url.host(), 'www.qutebrowser.org') - self.assertEqual(url.query(), 'q=testfoo bar foo') + assert url.host() == 'www.qutebrowser.org' + assert url.query() == 'q=testfoo bar foo' def test_engine_pre_whitespace_at_end(self): """Test search engine name with one word and whitespace.""" url = urlutils._get_search_url('test testfoo ') - self.assertEqual(url.host(), 'www.qutebrowser.org') - self.assertEqual(url.query(), 'q=testfoo') + assert url.host() == 'www.qutebrowser.org' + assert url.query() == 'q=testfoo' def test_engine_with_bang_pre(self): """Test search engine with a prepended !bang.""" url = urlutils._get_search_url('!python testfoo') - self.assertEqual(url.host(), 'www.example.com') - self.assertEqual(url.query(), 'q=%21python testfoo') + assert url.host() == 'www.example.com' + assert url.query() == 'q=%21python testfoo' def test_engine_wrong(self): """Test with wrong search engine.""" url = urlutils._get_search_url('blub testfoo') - self.assertEqual(url.host(), 'www.example.com') - self.assertEqual(url.query(), 'q=blub testfoo') + assert url.host() == 'www.example.com' + assert url.query() == 'q=blub testfoo' -class IsUrlTests(unittest.TestCase): +class TestIsUrl: """Tests for is_url. @@ -158,87 +157,72 @@ class IsUrlTests(unittest.TestCase): 'foo::bar', ) - @mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( - get_config_stub('naive'))) - def test_urls(self): + @pytest.mark.parametrize('url', URLS) + def test_urls(self, mocker, stubs, url): """Test things which are URLs.""" - for url in self.URLS: - with self.subTest(url=url): - self.assertTrue(urlutils.is_url(url), url) + mocker.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( + get_config_stub('naive'))) + assert urlutils.is_url(url), url - @mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( - get_config_stub('naive'))) - def test_not_urls(self): + @pytest.mark.parametrize('url', NOT_URLS) + def test_not_urls(self, mocker, stubs, url): """Test things which are not URLs.""" - for url in self.NOT_URLS: - with self.subTest(url=url): - self.assertFalse(urlutils.is_url(url), url) + mocker.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( + get_config_stub('naive'))) + assert not urlutils.is_url(url), url - @mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( - get_config_stub(True))) - def test_search_autosearch(self): + @pytest.mark.parametrize('autosearch', [True, False]) + def test_search_autosearch(self, mocker, stubs, autosearch): """Test explicit search with auto-search=True.""" - self.assertFalse(urlutils.is_url('test foo')) - - @mock.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( - get_config_stub(False))) - def test_search_no_autosearch(self): - """Test explicit search with auto-search=False.""" - self.assertFalse(urlutils.is_url('test foo')) + mocker.patch('qutebrowser.utils.urlutils.config', new=stubs.ConfigStub( + get_config_stub(autosearch))) + assert not urlutils.is_url('test foo') -class QurlFromUserInputTests(unittest.TestCase): +class TestQurlFromUserInput: """Tests for qurl_from_user_input.""" def test_url(self): """Test a normal URL.""" - self.assertEqual( - urlutils.qurl_from_user_input('qutebrowser.org').toString(), - 'http://qutebrowser.org') + url = urlutils.qurl_from_user_input('qutebrowser.org') + assert url.toString() == 'http://qutebrowser.org' def test_url_http(self): """Test a normal URL with http://.""" - self.assertEqual( - urlutils.qurl_from_user_input('http://qutebrowser.org').toString(), - 'http://qutebrowser.org') + url = urlutils.qurl_from_user_input('http://qutebrowser.org') + assert url.toString() == 'http://qutebrowser.org' def test_ipv6_bare(self): """Test an IPv6 without brackets.""" - self.assertEqual(urlutils.qurl_from_user_input('::1/foo').toString(), - 'http://[::1]/foo') + url = urlutils.qurl_from_user_input('::1/foo') + assert url.toString() == 'http://[::1]/foo' def test_ipv6(self): """Test an IPv6 with brackets.""" - self.assertEqual(urlutils.qurl_from_user_input('[::1]/foo').toString(), - 'http://[::1]/foo') + url = urlutils.qurl_from_user_input('[::1]/foo') + assert url.toString() == 'http://[::1]/foo' def test_ipv6_http(self): """Test an IPv6 with http:// and brackets.""" - self.assertEqual( - urlutils.qurl_from_user_input('http://[::1]').toString(), - 'http://[::1]') + url = urlutils.qurl_from_user_input('http://[::1]') + assert url.toString() == 'http://[::1]' -class FilenameFromUrlTests(unittest.TestCase): +class TestFilenameFromUrl: """Tests for filename_from_url.""" def test_invalid_url(self): """Test with an invalid QUrl.""" - self.assertEqual(urlutils.filename_from_url(QUrl()), None) + assert urlutils.filename_from_url(QUrl()) is None def test_url_path(self): """Test with an URL with path.""" url = QUrl('http://qutebrowser.org/test.html') - self.assertEqual(urlutils.filename_from_url(url), 'test.html') + assert urlutils.filename_from_url(url) == 'test.html' def test_url_host(self): """Test with an URL with no path.""" url = QUrl('http://qutebrowser.org/') - self.assertEqual(urlutils.filename_from_url(url), - 'qutebrowser.org.html') - - -if __name__ == '__main__': - unittest.main() + assert urlutils.filename_from_url(url) == 'qutebrowser.org.html' diff --git a/qutebrowser/test/utils/test_utils.py b/tests/utils/test_utils.py similarity index 62% rename from qutebrowser/test/utils/test_utils.py rename to tests/utils/test_utils.py index 96bc8a4fa..26dc1cab7 100644 --- a/qutebrowser/test/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -21,15 +21,14 @@ import sys import enum -import unittest import datetime import os.path from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor +import pytest from qutebrowser.utils import utils, qtutils -from qutebrowser.test import helpers class Color(QColor): @@ -42,7 +41,7 @@ class Color(QColor): alpha=self.alpha()) -class ElidingTests(unittest.TestCase): +class TestEliding: """Test elide.""" @@ -50,33 +49,33 @@ class ElidingTests(unittest.TestCase): def test_too_small(self): """Test eliding to 0 chars which should fail.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): utils.elide('foo', 0) def test_length_one(self): """Test eliding to 1 char which should yield ...""" - self.assertEqual(utils.elide('foo', 1), self.ELLIPSIS) + assert utils.elide('foo', 1) == self.ELLIPSIS def test_fits(self): """Test eliding with a string which fits exactly.""" - self.assertEqual(utils.elide('foo', 3), 'foo') + assert utils.elide('foo', 3) == 'foo' def test_elided(self): """Test eliding with a string which should get elided.""" - self.assertEqual(utils.elide('foobar', 3), 'fo' + self.ELLIPSIS) + assert utils.elide('foobar', 3) == 'fo' + self.ELLIPSIS -class ReadFileTests(unittest.TestCase): +class TestReadFile: """Test read_file.""" def test_readfile(self): """Read a test file.""" - content = utils.read_file(os.path.join('test', 'testfile')) - self.assertEqual(content.splitlines()[0], "Hello World!") + content = utils.read_file(os.path.join('utils', 'testfile')) + assert content.splitlines()[0] == "Hello World!" -class InterpolateColorTests(unittest.TestCase): +class TestInterpolateColor: """Tests for interpolate_color. @@ -85,30 +84,31 @@ class InterpolateColorTests(unittest.TestCase): white: The Color black as a valid Color for tests. """ - def setUp(self): + @pytest.fixture(autouse=True) + def setup(self): self.white = Color('white') self.black = Color('black') def test_invalid_start(self): """Test an invalid start color.""" - with self.assertRaises(qtutils.QtValueError): + with pytest.raises(qtutils.QtValueError): utils.interpolate_color(Color(), self.white, 0) def test_invalid_end(self): """Test an invalid end color.""" - with self.assertRaises(qtutils.QtValueError): + with pytest.raises(qtutils.QtValueError): utils.interpolate_color(self.white, Color(), 0) def test_invalid_percentage(self): """Test an invalid percentage.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): utils.interpolate_color(self.white, self.white, -1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): utils.interpolate_color(self.white, self.white, 101) def test_invalid_colorspace(self): """Test an invalid colorspace.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): utils.interpolate_color(self.white, self.black, 10, QColor.Cmyk) def test_valid_percentages_rgb(self): @@ -116,30 +116,30 @@ class InterpolateColorTests(unittest.TestCase): white = utils.interpolate_color(self.white, self.black, 0, QColor.Rgb) black = utils.interpolate_color(self.white, self.black, 100, QColor.Rgb) - self.assertEqual(Color(white), self.white) - self.assertEqual(Color(black), self.black) + assert Color(white) == self.white + assert Color(black) == self.black def test_valid_percentages_hsv(self): """Test 0% and 100% in the HSV colorspace.""" white = utils.interpolate_color(self.white, self.black, 0, QColor.Hsv) black = utils.interpolate_color(self.white, self.black, 100, QColor.Hsv) - self.assertEqual(Color(white), self.white) - self.assertEqual(Color(black), self.black) + assert Color(white) == self.white + assert Color(black) == self.black def test_valid_percentages_hsl(self): """Test 0% and 100% in the HSL colorspace.""" white = utils.interpolate_color(self.white, self.black, 0, QColor.Hsl) black = utils.interpolate_color(self.white, self.black, 100, QColor.Hsl) - self.assertEqual(Color(white), self.white) - self.assertEqual(Color(black), self.black) + assert Color(white) == self.white + assert Color(black) == self.black def test_interpolation_rgb(self): """Test an interpolation in the RGB colorspace.""" color = utils.interpolate_color(Color(0, 40, 100), Color(0, 20, 200), 50, QColor.Rgb) - self.assertEqual(Color(color), Color(0, 30, 150)) + assert Color(color) == Color(0, 30, 150) def test_interpolation_hsv(self): """Test an interpolation in the HSV colorspace.""" @@ -150,7 +150,7 @@ class InterpolateColorTests(unittest.TestCase): color = utils.interpolate_color(start, stop, 50, QColor.Hsv) expected = Color() expected.setHsv(0, 30, 150) - self.assertEqual(Color(color), expected) + assert Color(color) == expected def test_interpolation_hsl(self): """Test an interpolation in the HSL colorspace.""" @@ -161,10 +161,10 @@ class InterpolateColorTests(unittest.TestCase): color = utils.interpolate_color(start, stop, 50, QColor.Hsl) expected = Color() expected.setHsl(0, 30, 150) - self.assertEqual(Color(color), expected) + assert Color(color) == expected -class FormatSecondsTests(unittest.TestCase): +class TestFormatSeconds: """Tests for format_seconds. @@ -186,14 +186,13 @@ class FormatSecondsTests(unittest.TestCase): (36000, '10:00:00'), ] - def test_format_seconds(self): + @pytest.mark.parametrize('seconds, out', TESTS) + def test_format_seconds(self, seconds, out): """Test format_seconds with several tests.""" - for seconds, out in self.TESTS: - with self.subTest(seconds=seconds): - self.assertEqual(utils.format_seconds(seconds), out) + assert utils.format_seconds(seconds) == out -class FormatTimedeltaTests(unittest.TestCase): +class TestFormatTimedelta: """Tests for format_timedelta. @@ -217,14 +216,13 @@ class FormatTimedeltaTests(unittest.TestCase): (datetime.timedelta(seconds=36000), '10h'), ] - def test_format_seconds(self): + @pytest.mark.parametrize('td, out', TESTS) + def test_format_seconds(self, td, out): """Test format_seconds with several tests.""" - for td, out in self.TESTS: - with self.subTest(td=td): - self.assertEqual(utils.format_timedelta(td), out) + assert utils.format_timedelta(td) == out -class FormatSizeTests(unittest.TestCase): +class TestFormatSize: """Tests for format_size. @@ -244,122 +242,117 @@ class FormatSizeTests(unittest.TestCase): (None, '?.??'), ] - def test_format_size(self): + KILO_TESTS = [(999, '999.00'), (1000, '1.00k'), (1010, '1.01k')] + + @pytest.mark.parametrize('size, out', TESTS) + def test_format_size(self, size, out): """Test format_size with several tests.""" - for size, out in self.TESTS: - with self.subTest(size=size): - self.assertEqual(utils.format_size(size), out) + assert utils.format_size(size) == out - def test_suffix(self): + @pytest.mark.parametrize('size, out', TESTS) + def test_suffix(self, size, out): """Test the suffix option.""" - for size, out in self.TESTS: - with self.subTest(size=size): - self.assertEqual(utils.format_size(size, suffix='B'), - out + 'B') + assert utils.format_size(size, suffix='B') == out + 'B' - def test_base(self): + @pytest.mark.parametrize('size, out', KILO_TESTS) + def test_base(self, size, out): """Test with an alternative base.""" - kilo_tests = [(999, '999.00'), (1000, '1.00k'), (1010, '1.01k')] - for size, out in kilo_tests: - with self.subTest(size=size): - self.assertEqual(utils.format_size(size, base=1000), out) + assert utils.format_size(size, base=1000) == out -class KeyToStringTests(unittest.TestCase): +class TestKeyToString: """Test key_to_string.""" def test_unicode_garbage_keys(self): """Test a special key where QKeyEvent::toString works incorrectly.""" - self.assertEqual(utils.key_to_string(Qt.Key_Blue), 'Blue') + assert utils.key_to_string(Qt.Key_Blue) == 'Blue' def test_backtab(self): """Test if backtab is normalized to tab correctly.""" - self.assertEqual(utils.key_to_string(Qt.Key_Backtab), 'Tab') + assert utils.key_to_string(Qt.Key_Backtab) == 'Tab' def test_escape(self): """Test if escape is normalized to escape correctly.""" - self.assertEqual(utils.key_to_string(Qt.Key_Escape), 'Escape') + assert utils.key_to_string(Qt.Key_Escape) == 'Escape' def test_letter(self): """Test a simple letter key.""" - self.assertEqual(utils.key_to_string(Qt.Key_A), 'A') + assert utils.key_to_string(Qt.Key_A) == 'A' def test_unicode(self): """Test a printable unicode key.""" - self.assertEqual(utils.key_to_string(Qt.Key_degree), '°') + assert utils.key_to_string(Qt.Key_degree) == '°' def test_special(self): """Test a non-printable key handled by QKeyEvent::toString.""" - self.assertEqual(utils.key_to_string(Qt.Key_F1), 'F1') + assert utils.key_to_string(Qt.Key_F1) == 'F1' -class KeyEventToStringTests(unittest.TestCase): +class TestKeyEventToString: """Test keyevent_to_string.""" - def test_only_control(self): - """Test keyevent when only control is pressed.""" - evt = helpers.fake_keyevent(key=Qt.Key_Control, + def test_only_control(self, fake_keyevent_factory): + """Test keyeevent when only control is pressed.""" + evt = fake_keyevent_factory(key=Qt.Key_Control, modifiers=Qt.ControlModifier) - self.assertIsNone(utils.keyevent_to_string(evt)) + assert utils.keyevent_to_string(evt) is None - def test_only_hyper_l(self): - """Test keyevent when only Hyper_L is pressed.""" - evt = helpers.fake_keyevent(key=Qt.Key_Hyper_L, + def test_only_hyper_l(self, fake_keyevent_factory): + """Test keyeevent when only Hyper_L is pressed.""" + evt = fake_keyevent_factory(key=Qt.Key_Hyper_L, modifiers=Qt.MetaModifier) - self.assertIsNone(utils.keyevent_to_string(evt)) + assert utils.keyevent_to_string(evt) is None - def test_only_key(self): + def test_only_key(self, fake_keyevent_factory): """Test with a simple key pressed.""" - evt = helpers.fake_keyevent(key=Qt.Key_A) - self.assertEqual(utils.keyevent_to_string(evt), 'A') + evt = fake_keyevent_factory(key=Qt.Key_A) + assert utils.keyevent_to_string(evt) == 'A' - def test_key_and_modifier(self): + def test_key_and_modifier(self, fake_keyevent_factory): """Test with key and modifier pressed.""" - evt = helpers.fake_keyevent(key=Qt.Key_A, modifiers=Qt.ControlModifier) - self.assertEqual(utils.keyevent_to_string(evt), 'Ctrl+A') + evt = fake_keyevent_factory(key=Qt.Key_A, modifiers=Qt.ControlModifier) + assert utils.keyevent_to_string(evt) == 'Ctrl+A' - def test_key_and_modifiers(self): + def test_key_and_modifiers(self, fake_keyevent_factory): """Test with key and multiple modifier pressed.""" - evt = helpers.fake_keyevent( + evt = fake_keyevent_factory( key=Qt.Key_A, modifiers=(Qt.ControlModifier | Qt.AltModifier | Qt.MetaModifier | Qt.ShiftModifier)) if sys.platform == 'darwin': - self.assertEqual(utils.keyevent_to_string(evt), - 'Ctrl+Alt+Shift+A') + assert utils.keyevent_to_string(evt) == 'Ctrl+Alt+Shift+A' else: - self.assertEqual(utils.keyevent_to_string(evt), - 'Ctrl+Alt+Meta+Shift+A') + assert utils.keyevent_to_string(evt) == 'Ctrl+Alt+Meta+Shift+A' -class NormalizeTests(unittest.TestCase): +class TestNormalize: """Test normalize_keystr.""" - def test_normalize(self): + STRINGS = ( + ('Control+x', 'ctrl+x'), + ('Windows+x', 'meta+x'), + ('Mod1+x', 'alt+x'), + ('Mod4+x', 'meta+x'), + ('Control--', 'ctrl+-'), + ('Windows++', 'meta++'), + ) + + @pytest.mark.parametrize('orig, repl', STRINGS) + def test_normalize(self, orig, repl): """Test normalize with some strings.""" - strings = ( - ('Control+x', 'ctrl+x'), - ('Windows+x', 'meta+x'), - ('Mod1+x', 'alt+x'), - ('Mod4+x', 'meta+x'), - ('Control--', 'ctrl+-'), - ('Windows++', 'meta++'), - ) - for orig, repl in strings: - with self.subTest(orig=orig): - self.assertEqual(utils.normalize_keystr(orig), repl) + assert utils.normalize_keystr(orig) == repl -class IsEnumTests(unittest.TestCase): +class TestIsEnum: """Test is_enum.""" def test_enum(self): """Test is_enum with an enum.""" e = enum.Enum('Foo', 'bar, baz') - self.assertTrue(utils.is_enum(e)) + assert utils.is_enum(e) def test_class(self): """Test is_enum with a non-enum class.""" @@ -368,14 +361,15 @@ class IsEnumTests(unittest.TestCase): """Test class for is_enum.""" pass - self.assertFalse(utils.is_enum(Test)) + + assert not utils.is_enum(Test) def test_object(self): """Test is_enum with a non-enum object.""" - self.assertFalse(utils.is_enum(23)) + assert not utils.is_enum(23) -class RaisesTests(unittest.TestCase): +class TestRaises: """Test raises.""" @@ -389,106 +383,98 @@ class RaisesTests(unittest.TestCase): def test_raises_single_exc_true(self): """Test raises with a single exception which gets raised.""" - self.assertTrue(utils.raises(ValueError, int, 'a')) + assert utils.raises(ValueError, int, 'a') def test_raises_single_exc_false(self): """Test raises with a single exception which does not get raised.""" - self.assertFalse(utils.raises(ValueError, int, '1')) + assert not utils.raises(ValueError, int, '1') def test_raises_multiple_exc_true(self): """Test raises with multiple exceptions which get raised.""" - self.assertTrue(utils.raises((ValueError, TypeError), int, 'a')) - self.assertTrue(utils.raises((ValueError, TypeError), int, None)) + assert utils.raises((ValueError, TypeError), int, 'a') + assert utils.raises((ValueError, TypeError), int, None) def test_raises_multiple_exc_false(self): """Test raises with multiple exceptions which do not get raised.""" - self.assertFalse(utils.raises((ValueError, TypeError), int, '1')) + assert not utils.raises((ValueError, TypeError), int, '1') def test_no_args_true(self): """Test with no args and an exception which gets raised.""" - self.assertTrue(utils.raises(Exception, self.do_raise)) + assert utils.raises(Exception, self.do_raise) def test_no_args_false(self): """Test with no args and an exception which does not get raised.""" - self.assertFalse(utils.raises(Exception, self.do_nothing)) + assert not utils.raises(Exception, self.do_nothing) def test_unrelated_exception(self): """Test with an unrelated exception.""" - with self.assertRaises(Exception): + with pytest.raises(Exception): utils.raises(ValueError, self.do_raise) -class ForceEncodingTests(unittest.TestCase): +class TestForceEncoding: """Test force_encoding.""" - def test_fitting_ascii(self): - """Test with a text fitting into ASCII.""" - text = 'hello world' - self.assertEqual(utils.force_encoding(text, 'ascii'), text) + TESTS = [ + ('hello world', 'ascii', 'hello world'), + ('hellö wörld', 'utf-8', 'hellö wörld'), + ('hellö wörld', 'ascii', 'hell? w?rld'), + ] - def test_fitting_utf8(self): - """Test with a text fitting into utf-8.""" - text = 'hellö wörld' - self.assertEqual(utils.force_encoding(text, 'utf-8'), text) - - def test_not_fitting_ascii(self): - """Test with a text not fitting into ASCII.""" - text = 'hellö wörld' - self.assertEqual(utils.force_encoding(text, 'ascii'), 'hell? w?rld') + @pytest.mark.parametrize('inp, enc, expected', TESTS) + def test_fitting_ascii(self, inp, enc, expected): + """Test force_encoding will yield expected text.""" + assert utils.force_encoding(inp, enc) == expected -class NewestSliceTests(unittest.TestCase): +class TestNewestSlice: """Test newest_slice.""" def test_count_minus_two(self): """Test with a count of -2.""" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): utils.newest_slice([], -2) def test_count_minus_one(self): """Test with a count of -1 (all elements).""" items = range(20) sliced = utils.newest_slice(items, -1) - self.assertEqual(list(sliced), list(items)) + assert list(sliced) == list(items) def test_count_zero(self): """Test with a count of 0 (no elements).""" items = range(20) sliced = utils.newest_slice(items, 0) - self.assertEqual(list(sliced), []) + assert list(sliced) == [] def test_count_much_smaller(self): """Test with a count which is much smaller than the iterable.""" items = range(20) sliced = utils.newest_slice(items, 5) - self.assertEqual(list(sliced), [15, 16, 17, 18, 19]) + assert list(sliced) == [15, 16, 17, 18, 19] def test_count_smaller(self): """Test with a count which is exactly one smaller.""" items = range(5) sliced = utils.newest_slice(items, 4) - self.assertEqual(list(sliced), [1, 2, 3, 4]) + assert list(sliced) == [1, 2, 3, 4] def test_count_equal(self): """Test with a count which is just as large as the iterable.""" items = range(5) sliced = utils.newest_slice(items, 5) - self.assertEqual(list(sliced), list(items)) + assert list(sliced) == list(items) def test_count_bigger(self): """Test with a count which is one bigger than the iterable.""" items = range(5) sliced = utils.newest_slice(items, 6) - self.assertEqual(list(sliced), list(items)) + assert list(sliced) == list(items) def test_count_much_bigger(self): """Test with a count which is much bigger than the iterable.""" items = range(5) sliced = utils.newest_slice(items, 50) - self.assertEqual(list(sliced), list(items)) - - -if __name__ == '__main__': - unittest.main() + assert list(sliced) == list(items) diff --git a/qutebrowser/test/utils/usertypes/test_enum.py b/tests/utils/usertypes/test_enum.py similarity index 100% rename from qutebrowser/test/utils/usertypes/test_enum.py rename to tests/utils/usertypes/test_enum.py diff --git a/qutebrowser/test/utils/usertypes/test_neighborlist.py b/tests/utils/usertypes/test_neighborlist.py similarity index 100% rename from qutebrowser/test/utils/usertypes/test_neighborlist.py rename to tests/utils/usertypes/test_neighborlist.py diff --git a/tox.ini b/tox.ini index 9377f5bc8..60a480388 100644 --- a/tox.ini +++ b/tox.ini @@ -20,6 +20,8 @@ deps = py==1.4.26 pytest==2.7.0 pytest-capturelog==0.7 + pytest-qt==1.3.0 + pytest-mock==0.4.2 # We don't use {[testenv:mkvenv]commands} here because that seems to be broken # on Ubuntu Trusty. commands = @@ -39,8 +41,8 @@ commands = [testenv:misc] commands = {envpython} scripts/misc_checks.py git - {envpython} scripts/misc_checks.py vcs qutebrowser scripts - {envpython} scripts/misc_checks.py spelling qutebrowser scripts + {envpython} scripts/misc_checks.py vcs qutebrowser scripts tests + {envpython} scripts/misc_checks.py spelling qutebrowser scripts tests [testenv:pylint] skip_install = true @@ -55,6 +57,7 @@ deps = commands = {[testenv:mkvenv]commands} {envdir}/bin/pylint scripts qutebrowser --rcfile=.pylintrc --output-format=colorized --reports=no + {envpython} scripts/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no [testenv:pep257] skip_install = true @@ -63,7 +66,7 @@ deps = pep257==0.5.0 # D102: Docstring missing, will be handled by others # D209: Blank line before closing """ (removed from PEP257) # D402: First line should not be function's signature (false-positives) -commands = {envpython} -m pep257 scripts qutebrowser --ignore=D102,D209,D402 '--match=(?!resources|test_content_disposition).*\.py' +commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D209,D402 '--match=(?!resources|test_content_disposition).*\.py' [testenv:flake8] skip_install = true @@ -74,7 +77,7 @@ deps = flake8==2.4.0 commands = {[testenv:mkvenv]commands} - {envdir}/bin/flake8 scripts qutebrowser --config=.flake8 + {envdir}/bin/flake8 scripts tests qutebrowser --config=.flake8 [testenv:pyroma] skip_install = true @@ -103,3 +106,6 @@ commands = {envpython} scripts/src2asciidoc.py git --no-pager diff --exit-code --stat {envpython} scripts/asciidoc2html.py {posargs} + +[pytest] +norecursedirs = .tox .venv