From b7de287e7bf053072eab47551e593d948fa8ced7 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 29 Nov 2018 14:09:06 +0100 Subject: Move CommandError to api.cmdutils --- tests/unit/api/test_cmdutils.py | 428 +++++++++++++++++++++++++ tests/unit/browser/test_history.py | 4 +- tests/unit/commands/test_cmdutils.py | 427 ------------------------ tests/unit/completion/test_completer.py | 3 +- tests/unit/completion/test_completionmodel.py | 4 +- tests/unit/completion/test_completionwidget.py | 4 +- tests/unit/config/test_configcommands.py | 50 +-- tests/unit/misc/test_utilcmds.py | 8 +- tests/unit/utils/test_urlutils.py | 4 +- 9 files changed, 467 insertions(+), 465 deletions(-) create mode 100644 tests/unit/api/test_cmdutils.py delete mode 100644 tests/unit/commands/test_cmdutils.py (limited to 'tests') diff --git a/tests/unit/api/test_cmdutils.py b/tests/unit/api/test_cmdutils.py new file mode 100644 index 000000000..f2318ab46 --- /dev/null +++ b/tests/unit/api/test_cmdutils.py @@ -0,0 +1,428 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2015-2018 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=unused-variable + +"""Tests for qutebrowser.api.cmdutils.""" + +import sys +import logging +import types +import typing +import enum + +import pytest + +from qutebrowser.misc import objects +from qutebrowser.commands import cmdexc, argparser, command +from qutebrowser.api import cmdutils +from qutebrowser.utils import usertypes + + +@pytest.fixture(autouse=True) +def clear_globals(monkeypatch): + monkeypatch.setattr(objects, 'commands', {}) + + +def _get_cmd(*args, **kwargs): + """Get a command object created via @cmdutils.register. + + Args: + Passed to @cmdutils.register decorator + """ + @cmdutils.register(*args, **kwargs) + def fun(): + """Blah.""" + return objects.commands['fun'] + + +class TestCheckOverflow: + + def test_good(self): + cmdutils.check_overflow(1, 'int') + + def test_bad(self): + int32_max = 2 ** 31 - 1 + + with pytest.raises(cmdutils.CommandError, match="Numeric argument is " + "too large for internal int representation."): + cmdutils.check_overflow(int32_max + 1, 'int') + + +class TestCheckExclusive: + + @pytest.mark.parametrize('flags', [[], [False, True], [False, False]]) + def test_good(self, flags): + cmdutils.check_exclusive(flags, []) + + def test_bad(self): + with pytest.raises(cmdutils.CommandError, + match="Only one of -x/-y/-z can be given!"): + cmdutils.check_exclusive([True, True], 'xyz') + + +class TestRegister: + + def test_simple(self): + @cmdutils.register() + def fun(): + """Blah.""" + + cmd = objects.commands['fun'] + assert cmd.handler is fun + assert cmd.name == 'fun' + assert len(objects.commands) == 1 + + def test_underlines(self): + """Make sure the function name is normalized correctly (_ -> -).""" + @cmdutils.register() + def eggs_bacon(): + """Blah.""" + + assert objects.commands['eggs-bacon'].name == 'eggs-bacon' + assert 'eggs_bacon' not in objects.commands + + def test_lowercasing(self): + """Make sure the function name is normalized correctly (uppercase).""" + @cmdutils.register() + def Test(): # noqa: N801,N806 pylint: disable=invalid-name + """Blah.""" + + assert objects.commands['test'].name == 'test' + assert 'Test' not in objects.commands + + def test_explicit_name(self): + """Test register with explicit name.""" + @cmdutils.register(name='foobar') + def fun(): + """Blah.""" + + assert objects.commands['foobar'].name == 'foobar' + assert 'fun' not in objects.commands + assert len(objects.commands) == 1 + + def test_multiple_registrations(self): + """Make sure registering the same name twice raises ValueError.""" + @cmdutils.register(name='foobar') + def fun(): + """Blah.""" + + with pytest.raises(ValueError): + @cmdutils.register(name='foobar') + def fun2(): + """Blah.""" + + def test_instance(self): + """Make sure the instance gets passed to Command.""" + @cmdutils.register(instance='foobar') + def fun(self): + """Blah.""" + assert objects.commands['fun']._instance == 'foobar' + + def test_star_args(self): + """Check handling of *args.""" + @cmdutils.register() + def fun(*args): + """Blah.""" + with pytest.raises(argparser.ArgumentParserError): + objects.commands['fun'].parser.parse_args([]) + + def test_star_args_optional(self): + """Check handling of *args withstar_args_optional.""" + @cmdutils.register(star_args_optional=True) + def fun(*args): + """Blah.""" + assert not args + cmd = objects.commands['fun'] + cmd.namespace = cmd.parser.parse_args([]) + args, kwargs = cmd._get_call_args(win_id=0) + fun(*args, **kwargs) + + @pytest.mark.parametrize('inp, expected', [ + (['--arg'], True), (['-a'], True), ([], False)]) + def test_flag(self, inp, expected): + @cmdutils.register() + def fun(arg=False): + """Blah.""" + assert arg == expected + cmd = objects.commands['fun'] + cmd.namespace = cmd.parser.parse_args(inp) + assert cmd.namespace.arg == expected + + def test_flag_argument(self): + @cmdutils.register() + @cmdutils.argument('arg', flag='b') + def fun(arg=False): + """Blah.""" + assert arg + cmd = objects.commands['fun'] + + with pytest.raises(argparser.ArgumentParserError): + cmd.parser.parse_args(['-a']) + + cmd.namespace = cmd.parser.parse_args(['-b']) + assert cmd.namespace.arg + args, kwargs = cmd._get_call_args(win_id=0) + fun(*args, **kwargs) + + def test_partial_arg(self): + """Test with only some arguments decorated with @cmdutils.argument.""" + @cmdutils.register() + @cmdutils.argument('arg1', flag='b') + def fun(arg1=False, arg2=False): + """Blah.""" + + def test_win_id(self): + @cmdutils.register() + @cmdutils.argument('win_id', win_id=True) + def fun(win_id): + """Blah.""" + assert objects.commands['fun']._get_call_args(42) == ([42], {}) + + def test_count(self): + @cmdutils.register() + @cmdutils.argument('count', count=True) + def fun(count=0): + """Blah.""" + assert objects.commands['fun']._get_call_args(42) == ([0], {}) + + def test_count_without_default(self): + with pytest.raises(TypeError, match="fun: handler has count parameter " + "without default!"): + @cmdutils.register() + @cmdutils.argument('count', count=True) + def fun(count): + """Blah.""" + + @pytest.mark.parametrize('hide', [True, False]) + def test_pos_args(self, hide): + @cmdutils.register() + @cmdutils.argument('arg', hide=hide) + def fun(arg): + """Blah.""" + + pos_args = objects.commands['fun'].pos_args + if hide: + assert pos_args == [] + else: + assert pos_args == [('arg', 'arg')] + + Enum = enum.Enum('Test', ['x', 'y']) + + @pytest.mark.parametrize('typ, inp, choices, expected', [ + (int, '42', None, 42), + (int, 'x', None, cmdexc.ArgumentTypeError), + (str, 'foo', None, 'foo'), + + (typing.Union[str, int], 'foo', None, 'foo'), + (typing.Union[str, int], '42', None, 42), + + # Choices + (str, 'foo', ['foo'], 'foo'), + (str, 'bar', ['foo'], cmdexc.ArgumentTypeError), + + # Choices with Union: only checked when it's a str + (typing.Union[str, int], 'foo', ['foo'], 'foo'), + (typing.Union[str, int], 'bar', ['foo'], cmdexc.ArgumentTypeError), + (typing.Union[str, int], '42', ['foo'], 42), + + (Enum, 'x', None, Enum.x), + (Enum, 'z', None, cmdexc.ArgumentTypeError), + ]) + def test_typed_args(self, typ, inp, choices, expected): + @cmdutils.register() + @cmdutils.argument('arg', choices=choices) + def fun(arg: typ): + """Blah.""" + assert arg == expected + + cmd = objects.commands['fun'] + cmd.namespace = cmd.parser.parse_args([inp]) + + if expected is cmdexc.ArgumentTypeError: + with pytest.raises(cmdexc.ArgumentTypeError): + cmd._get_call_args(win_id=0) + else: + args, kwargs = cmd._get_call_args(win_id=0) + assert args == [expected] + assert kwargs == {} + fun(*args, **kwargs) + + def test_choices_no_annotation(self): + # https://github.com/qutebrowser/qutebrowser/issues/1871 + @cmdutils.register() + @cmdutils.argument('arg', choices=['foo', 'bar']) + def fun(arg): + """Blah.""" + + cmd = objects.commands['fun'] + cmd.namespace = cmd.parser.parse_args(['fish']) + + with pytest.raises(cmdexc.ArgumentTypeError): + cmd._get_call_args(win_id=0) + + def test_choices_no_annotation_kwonly(self): + # https://github.com/qutebrowser/qutebrowser/issues/1871 + @cmdutils.register() + @cmdutils.argument('arg', choices=['foo', 'bar']) + def fun(*, arg='foo'): + """Blah.""" + + cmd = objects.commands['fun'] + cmd.namespace = cmd.parser.parse_args(['--arg=fish']) + + with pytest.raises(cmdexc.ArgumentTypeError): + cmd._get_call_args(win_id=0) + + def test_pos_arg_info(self): + @cmdutils.register() + @cmdutils.argument('foo', choices=('a', 'b')) + @cmdutils.argument('bar', choices=('x', 'y')) + @cmdutils.argument('opt') + def fun(foo, bar, opt=False): + """Blah.""" + + cmd = objects.commands['fun'] + assert cmd.get_pos_arg_info(0) == command.ArgInfo(choices=('a', 'b')) + assert cmd.get_pos_arg_info(1) == command.ArgInfo(choices=('x', 'y')) + with pytest.raises(IndexError): + cmd.get_pos_arg_info(2) + + def test_keyword_only_without_default(self): + # https://github.com/qutebrowser/qutebrowser/issues/1872 + def fun(*, target): + """Blah.""" + + with pytest.raises(TypeError, match="fun: handler has keyword only " + "argument 'target' without default!"): + fun = cmdutils.register()(fun) + + def test_typed_keyword_only_without_default(self): + # https://github.com/qutebrowser/qutebrowser/issues/1872 + def fun(*, target: int): + """Blah.""" + + with pytest.raises(TypeError, match="fun: handler has keyword only " + "argument 'target' without default!"): + fun = cmdutils.register()(fun) + + +class TestArgument: + + """Test the @cmdutils.argument decorator.""" + + def test_invalid_argument(self): + with pytest.raises(ValueError, match="fun has no argument foo!"): + @cmdutils.argument('foo') + def fun(bar): + """Blah.""" + + def test_storage(self): + @cmdutils.argument('foo', flag='x') + @cmdutils.argument('bar', flag='y') + def fun(foo, bar): + """Blah.""" + expected = { + 'foo': command.ArgInfo(flag='x'), + 'bar': command.ArgInfo(flag='y') + } + assert fun.qute_args == expected + + def test_wrong_order(self): + """When @cmdutils.argument is used above (after) @register, fail.""" + with pytest.raises(ValueError, match=r"@cmdutils.argument got called " + r"above \(after\) @cmdutils.register for fun!"): + @cmdutils.argument('bar', flag='y') + @cmdutils.register() + def fun(bar): + """Blah.""" + + def test_count_and_win_id_same_arg(self): + with pytest.raises(TypeError, + match="Argument marked as both count/win_id!"): + @cmdutils.argument('arg', count=True, win_id=True) + def fun(arg=0): + """Blah.""" + + def test_no_docstring(self, caplog): + with caplog.at_level(logging.WARNING): + @cmdutils.register() + def fun(): + # no docstring + pass + + assert len(caplog.records) == 1 + assert caplog.messages[0].endswith('test_cmdutils.py has no docstring') + + def test_no_docstring_with_optimize(self, monkeypatch): + """With -OO we'd get a warning on start, but no warning afterwards.""" + monkeypatch.setattr(sys, 'flags', types.SimpleNamespace(optimize=2)) + + @cmdutils.register() + def fun(): + # no docstring + pass + + +class TestRun: + + @pytest.fixture(autouse=True) + def patch_backend(self, mode_manager, monkeypatch): + monkeypatch.setattr(command.objects, 'backend', + usertypes.Backend.QtWebKit) + + @pytest.mark.parametrize('backend, used, ok', [ + (usertypes.Backend.QtWebEngine, usertypes.Backend.QtWebEngine, True), + (usertypes.Backend.QtWebEngine, usertypes.Backend.QtWebKit, False), + (usertypes.Backend.QtWebKit, usertypes.Backend.QtWebEngine, False), + (usertypes.Backend.QtWebKit, usertypes.Backend.QtWebKit, True), + (None, usertypes.Backend.QtWebEngine, True), + (None, usertypes.Backend.QtWebKit, True), + ]) + def test_backend(self, monkeypatch, backend, used, ok): + monkeypatch.setattr(command.objects, 'backend', used) + cmd = _get_cmd(backend=backend) + if ok: + cmd.run(win_id=0) + else: + with pytest.raises(cmdexc.PrerequisitesError, + match=r'.* backend\.'): + cmd.run(win_id=0) + + def test_no_args(self): + cmd = _get_cmd() + cmd.run(win_id=0) + + def test_instance_unavailable_with_backend(self, monkeypatch): + """Test what happens when a backend doesn't have an objreg object. + + For example, QtWebEngine doesn't have 'hintmanager' registered. We make + sure the backend checking happens before resolving the instance, so we + display an error instead of crashing. + """ + @cmdutils.register(instance='doesnotexist', + backend=usertypes.Backend.QtWebEngine) + def fun(self): + """Blah.""" + + monkeypatch.setattr(command.objects, 'backend', + usertypes.Backend.QtWebKit) + cmd = objects.commands['fun'] + with pytest.raises(cmdexc.PrerequisitesError, match=r'.* backend\.'): + cmd.run(win_id=0) diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 5b84eac4c..715b597b0 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -26,7 +26,7 @@ from PyQt5.QtCore import QUrl from qutebrowser.browser import history from qutebrowser.utils import objreg, urlutils, usertypes -from qutebrowser.commands import cmdexc +from qutebrowser.api import cmdutils from qutebrowser.misc import sql @@ -324,7 +324,7 @@ class TestDump: def test_nonexistent(self, web_history, tmpdir): histfile = tmpdir / 'nonexistent' / 'history' - with pytest.raises(cmdexc.CommandError): + with pytest.raises(cmdutils.CommandError): web_history.debug_dump_history(str(histfile)) diff --git a/tests/unit/commands/test_cmdutils.py b/tests/unit/commands/test_cmdutils.py deleted file mode 100644 index 4d7877e69..000000000 --- a/tests/unit/commands/test_cmdutils.py +++ /dev/null @@ -1,427 +0,0 @@ -# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: - -# Copyright 2015-2018 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=unused-variable - -"""Tests for qutebrowser.commands.cmdutils.""" - -import sys -import logging -import types -import typing -import enum - -import pytest - -from qutebrowser.misc import objects -from qutebrowser.commands import cmdexc, argparser, command, cmdutils -from qutebrowser.utils import usertypes - - -@pytest.fixture(autouse=True) -def clear_globals(monkeypatch): - monkeypatch.setattr(objects, 'commands', {}) - - -def _get_cmd(*args, **kwargs): - """Get a command object created via @cmdutils.register. - - Args: - Passed to @cmdutils.register decorator - """ - @cmdutils.register(*args, **kwargs) - def fun(): - """Blah.""" - return objects.commands['fun'] - - -class TestCheckOverflow: - - def test_good(self): - cmdutils.check_overflow(1, 'int') - - def test_bad(self): - int32_max = 2 ** 31 - 1 - - with pytest.raises(cmdexc.CommandError, match="Numeric argument is " - "too large for internal int representation."): - cmdutils.check_overflow(int32_max + 1, 'int') - - -class TestCheckExclusive: - - @pytest.mark.parametrize('flags', [[], [False, True], [False, False]]) - def test_good(self, flags): - cmdutils.check_exclusive(flags, []) - - def test_bad(self): - with pytest.raises(cmdexc.CommandError, - match="Only one of -x/-y/-z can be given!"): - cmdutils.check_exclusive([True, True], 'xyz') - - -class TestRegister: - - def test_simple(self): - @cmdutils.register() - def fun(): - """Blah.""" - - cmd = objects.commands['fun'] - assert cmd.handler is fun - assert cmd.name == 'fun' - assert len(objects.commands) == 1 - - def test_underlines(self): - """Make sure the function name is normalized correctly (_ -> -).""" - @cmdutils.register() - def eggs_bacon(): - """Blah.""" - - assert objects.commands['eggs-bacon'].name == 'eggs-bacon' - assert 'eggs_bacon' not in objects.commands - - def test_lowercasing(self): - """Make sure the function name is normalized correctly (uppercase).""" - @cmdutils.register() - def Test(): # noqa: N801,N806 pylint: disable=invalid-name - """Blah.""" - - assert objects.commands['test'].name == 'test' - assert 'Test' not in objects.commands - - def test_explicit_name(self): - """Test register with explicit name.""" - @cmdutils.register(name='foobar') - def fun(): - """Blah.""" - - assert objects.commands['foobar'].name == 'foobar' - assert 'fun' not in objects.commands - assert len(objects.commands) == 1 - - def test_multiple_registrations(self): - """Make sure registering the same name twice raises ValueError.""" - @cmdutils.register(name='foobar') - def fun(): - """Blah.""" - - with pytest.raises(ValueError): - @cmdutils.register(name='foobar') - def fun2(): - """Blah.""" - - def test_instance(self): - """Make sure the instance gets passed to Command.""" - @cmdutils.register(instance='foobar') - def fun(self): - """Blah.""" - assert objects.commands['fun']._instance == 'foobar' - - def test_star_args(self): - """Check handling of *args.""" - @cmdutils.register() - def fun(*args): - """Blah.""" - with pytest.raises(argparser.ArgumentParserError): - objects.commands['fun'].parser.parse_args([]) - - def test_star_args_optional(self): - """Check handling of *args withstar_args_optional.""" - @cmdutils.register(star_args_optional=True) - def fun(*args): - """Blah.""" - assert not args - cmd = objects.commands['fun'] - cmd.namespace = cmd.parser.parse_args([]) - args, kwargs = cmd._get_call_args(win_id=0) - fun(*args, **kwargs) - - @pytest.mark.parametrize('inp, expected', [ - (['--arg'], True), (['-a'], True), ([], False)]) - def test_flag(self, inp, expected): - @cmdutils.register() - def fun(arg=False): - """Blah.""" - assert arg == expected - cmd = objects.commands['fun'] - cmd.namespace = cmd.parser.parse_args(inp) - assert cmd.namespace.arg == expected - - def test_flag_argument(self): - @cmdutils.register() - @cmdutils.argument('arg', flag='b') - def fun(arg=False): - """Blah.""" - assert arg - cmd = objects.commands['fun'] - - with pytest.raises(argparser.ArgumentParserError): - cmd.parser.parse_args(['-a']) - - cmd.namespace = cmd.parser.parse_args(['-b']) - assert cmd.namespace.arg - args, kwargs = cmd._get_call_args(win_id=0) - fun(*args, **kwargs) - - def test_partial_arg(self): - """Test with only some arguments decorated with @cmdutils.argument.""" - @cmdutils.register() - @cmdutils.argument('arg1', flag='b') - def fun(arg1=False, arg2=False): - """Blah.""" - - def test_win_id(self): - @cmdutils.register() - @cmdutils.argument('win_id', win_id=True) - def fun(win_id): - """Blah.""" - assert objects.commands['fun']._get_call_args(42) == ([42], {}) - - def test_count(self): - @cmdutils.register() - @cmdutils.argument('count', count=True) - def fun(count=0): - """Blah.""" - assert objects.commands['fun']._get_call_args(42) == ([0], {}) - - def test_count_without_default(self): - with pytest.raises(TypeError, match="fun: handler has count parameter " - "without default!"): - @cmdutils.register() - @cmdutils.argument('count', count=True) - def fun(count): - """Blah.""" - - @pytest.mark.parametrize('hide', [True, False]) - def test_pos_args(self, hide): - @cmdutils.register() - @cmdutils.argument('arg', hide=hide) - def fun(arg): - """Blah.""" - - pos_args = objects.commands['fun'].pos_args - if hide: - assert pos_args == [] - else: - assert pos_args == [('arg', 'arg')] - - Enum = enum.Enum('Test', ['x', 'y']) - - @pytest.mark.parametrize('typ, inp, choices, expected', [ - (int, '42', None, 42), - (int, 'x', None, cmdexc.ArgumentTypeError), - (str, 'foo', None, 'foo'), - - (typing.Union[str, int], 'foo', None, 'foo'), - (typing.Union[str, int], '42', None, 42), - - # Choices - (str, 'foo', ['foo'], 'foo'), - (str, 'bar', ['foo'], cmdexc.ArgumentTypeError), - - # Choices with Union: only checked when it's a str - (typing.Union[str, int], 'foo', ['foo'], 'foo'), - (typing.Union[str, int], 'bar', ['foo'], cmdexc.ArgumentTypeError), - (typing.Union[str, int], '42', ['foo'], 42), - - (Enum, 'x', None, Enum.x), - (Enum, 'z', None, cmdexc.ArgumentTypeError), - ]) - def test_typed_args(self, typ, inp, choices, expected): - @cmdutils.register() - @cmdutils.argument('arg', choices=choices) - def fun(arg: typ): - """Blah.""" - assert arg == expected - - cmd = objects.commands['fun'] - cmd.namespace = cmd.parser.parse_args([inp]) - - if expected is cmdexc.ArgumentTypeError: - with pytest.raises(cmdexc.ArgumentTypeError): - cmd._get_call_args(win_id=0) - else: - args, kwargs = cmd._get_call_args(win_id=0) - assert args == [expected] - assert kwargs == {} - fun(*args, **kwargs) - - def test_choices_no_annotation(self): - # https://github.com/qutebrowser/qutebrowser/issues/1871 - @cmdutils.register() - @cmdutils.argument('arg', choices=['foo', 'bar']) - def fun(arg): - """Blah.""" - - cmd = objects.commands['fun'] - cmd.namespace = cmd.parser.parse_args(['fish']) - - with pytest.raises(cmdexc.ArgumentTypeError): - cmd._get_call_args(win_id=0) - - def test_choices_no_annotation_kwonly(self): - # https://github.com/qutebrowser/qutebrowser/issues/1871 - @cmdutils.register() - @cmdutils.argument('arg', choices=['foo', 'bar']) - def fun(*, arg='foo'): - """Blah.""" - - cmd = objects.commands['fun'] - cmd.namespace = cmd.parser.parse_args(['--arg=fish']) - - with pytest.raises(cmdexc.ArgumentTypeError): - cmd._get_call_args(win_id=0) - - def test_pos_arg_info(self): - @cmdutils.register() - @cmdutils.argument('foo', choices=('a', 'b')) - @cmdutils.argument('bar', choices=('x', 'y')) - @cmdutils.argument('opt') - def fun(foo, bar, opt=False): - """Blah.""" - - cmd = objects.commands['fun'] - assert cmd.get_pos_arg_info(0) == command.ArgInfo(choices=('a', 'b')) - assert cmd.get_pos_arg_info(1) == command.ArgInfo(choices=('x', 'y')) - with pytest.raises(IndexError): - cmd.get_pos_arg_info(2) - - def test_keyword_only_without_default(self): - # https://github.com/qutebrowser/qutebrowser/issues/1872 - def fun(*, target): - """Blah.""" - - with pytest.raises(TypeError, match="fun: handler has keyword only " - "argument 'target' without default!"): - fun = cmdutils.register()(fun) - - def test_typed_keyword_only_without_default(self): - # https://github.com/qutebrowser/qutebrowser/issues/1872 - def fun(*, target: int): - """Blah.""" - - with pytest.raises(TypeError, match="fun: handler has keyword only " - "argument 'target' without default!"): - fun = cmdutils.register()(fun) - - -class TestArgument: - - """Test the @cmdutils.argument decorator.""" - - def test_invalid_argument(self): - with pytest.raises(ValueError, match="fun has no argument foo!"): - @cmdutils.argument('foo') - def fun(bar): - """Blah.""" - - def test_storage(self): - @cmdutils.argument('foo', flag='x') - @cmdutils.argument('bar', flag='y') - def fun(foo, bar): - """Blah.""" - expected = { - 'foo': command.ArgInfo(flag='x'), - 'bar': command.ArgInfo(flag='y') - } - assert fun.qute_args == expected - - def test_wrong_order(self): - """When @cmdutils.argument is used above (after) @register, fail.""" - with pytest.raises(ValueError, match=r"@cmdutils.argument got called " - r"above \(after\) @cmdutils.register for fun!"): - @cmdutils.argument('bar', flag='y') - @cmdutils.register() - def fun(bar): - """Blah.""" - - def test_count_and_win_id_same_arg(self): - with pytest.raises(TypeError, - match="Argument marked as both count/win_id!"): - @cmdutils.argument('arg', count=True, win_id=True) - def fun(arg=0): - """Blah.""" - - def test_no_docstring(self, caplog): - with caplog.at_level(logging.WARNING): - @cmdutils.register() - def fun(): - # no docstring - pass - - assert len(caplog.records) == 1 - assert caplog.messages[0].endswith('test_cmdutils.py has no docstring') - - def test_no_docstring_with_optimize(self, monkeypatch): - """With -OO we'd get a warning on start, but no warning afterwards.""" - monkeypatch.setattr(sys, 'flags', types.SimpleNamespace(optimize=2)) - - @cmdutils.register() - def fun(): - # no docstring - pass - - -class TestRun: - - @pytest.fixture(autouse=True) - def patch_backend(self, mode_manager, monkeypatch): - monkeypatch.setattr(command.objects, 'backend', - usertypes.Backend.QtWebKit) - - @pytest.mark.parametrize('backend, used, ok', [ - (usertypes.Backend.QtWebEngine, usertypes.Backend.QtWebEngine, True), - (usertypes.Backend.QtWebEngine, usertypes.Backend.QtWebKit, False), - (usertypes.Backend.QtWebKit, usertypes.Backend.QtWebEngine, False), - (usertypes.Backend.QtWebKit, usertypes.Backend.QtWebKit, True), - (None, usertypes.Backend.QtWebEngine, True), - (None, usertypes.Backend.QtWebKit, True), - ]) - def test_backend(self, monkeypatch, backend, used, ok): - monkeypatch.setattr(command.objects, 'backend', used) - cmd = _get_cmd(backend=backend) - if ok: - cmd.run(win_id=0) - else: - with pytest.raises(cmdexc.PrerequisitesError, - match=r'.* backend\.'): - cmd.run(win_id=0) - - def test_no_args(self): - cmd = _get_cmd() - cmd.run(win_id=0) - - def test_instance_unavailable_with_backend(self, monkeypatch): - """Test what happens when a backend doesn't have an objreg object. - - For example, QtWebEngine doesn't have 'hintmanager' registered. We make - sure the backend checking happens before resolving the instance, so we - display an error instead of crashing. - """ - @cmdutils.register(instance='doesnotexist', - backend=usertypes.Backend.QtWebEngine) - def fun(self): - """Blah.""" - - monkeypatch.setattr(command.objects, 'backend', - usertypes.Backend.QtWebKit) - cmd = objects.commands['fun'] - with pytest.raises(cmdexc.PrerequisitesError, match=r'.* backend\.'): - cmd.run(win_id=0) diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py index 59291cdd6..16ebb95dc 100644 --- a/tests/unit/completion/test_completer.py +++ b/tests/unit/completion/test_completer.py @@ -26,7 +26,8 @@ from PyQt5.QtCore import QObject from PyQt5.QtGui import QStandardItemModel from qutebrowser.completion import completer -from qutebrowser.commands import command, cmdutils +from qutebrowser.commands import command +from qutebrowser.api import cmdutils class FakeCompletionModel(QStandardItemModel): diff --git a/tests/unit/completion/test_completionmodel.py b/tests/unit/completion/test_completionmodel.py index e0e044ffb..24f5bdf0d 100644 --- a/tests/unit/completion/test_completionmodel.py +++ b/tests/unit/completion/test_completionmodel.py @@ -28,7 +28,7 @@ from PyQt5.QtCore import QModelIndex from qutebrowser.completion.models import completionmodel, listcategory from qutebrowser.utils import qtutils -from qutebrowser.commands import cmdexc +from qutebrowser.api import cmdutils @hypothesis.given(strategies.lists( @@ -102,7 +102,7 @@ def test_delete_cur_item_no_func(): model.rowsRemoved.connect(callback) model.add_category(cat) parent = model.index(0, 0) - with pytest.raises(cmdexc.CommandError): + with pytest.raises(cmdutils.CommandError): model.delete_cur_item(model.index(0, 0, parent)) callback.assert_not_called() diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py index 01bd3ec03..3d66e1145 100644 --- a/tests/unit/completion/test_completionwidget.py +++ b/tests/unit/completion/test_completionwidget.py @@ -25,7 +25,7 @@ import pytest from qutebrowser.completion import completionwidget from qutebrowser.completion.models import completionmodel, listcategory -from qutebrowser.commands import cmdexc +from qutebrowser.api import cmdutils @pytest.fixture @@ -241,7 +241,7 @@ def test_completion_item_del_no_selection(completionview): cat = listcategory.ListCategory('', [('foo',)], delete_func=func) model.add_category(cat) completionview.set_model(model) - with pytest.raises(cmdexc.CommandError, match='No item selected!'): + with pytest.raises(cmdutils.CommandError, match='No item selected!'): completionview.completion_item_del() func.assert_not_called() diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py index 47bcaaa74..001d55899 100644 --- a/tests/unit/config/test_configcommands.py +++ b/tests/unit/config/test_configcommands.py @@ -26,7 +26,7 @@ import pytest from PyQt5.QtCore import QUrl from qutebrowser.config import configcommands, configutils -from qutebrowser.commands import cmdexc +from qutebrowser.api import cmdutils from qutebrowser.utils import usertypes, urlmatch from qutebrowser.keyinput import keyutils from qutebrowser.misc import objects @@ -108,7 +108,7 @@ class TestSet: monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebKit) option = 'content.javascript.enabled' - with pytest.raises(cmdexc.CommandError, + with pytest.raises(cmdutils.CommandError, match=('Error while parsing http://: Pattern ' 'without host')): commands.set(0, option, 'false', pattern='http://') @@ -118,7 +118,7 @@ class TestSet: Should show an error as patterns are unsupported. """ - with pytest.raises(cmdexc.CommandError, + with pytest.raises(cmdutils.CommandError, match='does not support URL patterns'): commands.set(0, 'colors.statusbar.normal.bg', '#abcdef', pattern='*://*') @@ -165,7 +165,7 @@ class TestSet: Should show an error. """ - with pytest.raises(cmdexc.CommandError, match="No option 'foo'"): + with pytest.raises(cmdutils.CommandError, match="No option 'foo'"): commands.set(0, 'foo', 'bar') def test_set_invalid_value(self, commands): @@ -173,13 +173,13 @@ class TestSet: Should show an error. """ - with pytest.raises(cmdexc.CommandError, + with pytest.raises(cmdutils.CommandError, match="Invalid value 'blah' - must be a boolean!"): commands.set(0, 'auto_save.session', 'blah') def test_set_wrong_backend(self, commands, monkeypatch): monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebEngine) - with pytest.raises(cmdexc.CommandError, + with pytest.raises(cmdutils.CommandError, match="The hints.find_implementation setting is " "not available with the QtWebEngine backend!"): commands.set(0, 'hints.find_implementation', 'javascript') @@ -190,7 +190,7 @@ class TestSet: Should show an error. See https://github.com/qutebrowser/qutebrowser/issues/1109 """ - with pytest.raises(cmdexc.CommandError, match="No option '?'"): + with pytest.raises(cmdutils.CommandError, match="No option '?'"): commands.set(win_id=0, option='?') def test_toggle(self, commands): @@ -198,7 +198,7 @@ class TestSet: Should show an nicer error. """ - with pytest.raises(cmdexc.CommandError, + with pytest.raises(cmdutils.CommandError, match="Toggling values was moved to the " ":config-cycle command"): commands.set(win_id=0, option='javascript.enabled!') @@ -208,7 +208,7 @@ class TestSet: Should show an error. """ - with pytest.raises(cmdexc.CommandError, match="No option 'foo'"): + with pytest.raises(cmdutils.CommandError, match="No option 'foo'"): commands.set(win_id=0, option='foo?') @@ -267,7 +267,7 @@ class TestCycle: Should show an error. """ assert config_stub.val.url.auto_search == 'naive' - with pytest.raises(cmdexc.CommandError, match="Need at least " + with pytest.raises(cmdutils.CommandError, match="Need at least " "two values for non-boolean settings."): commands.config_cycle(*args) assert config_stub.val.url.auto_search == 'naive' @@ -301,14 +301,14 @@ class TestAdd: def test_list_add_non_list(self, commands): with pytest.raises( - cmdexc.CommandError, + cmdutils.CommandError, match=":config-list-add can only be used for lists"): commands.config_list_add('history_gap_interval', 'value') @pytest.mark.parametrize('value', ['', None, 42]) def test_list_add_invalid_values(self, commands, value): with pytest.raises( - cmdexc.CommandError, + cmdutils.CommandError, match="Invalid value '{}'".format(value)): commands.config_list_add('content.host_blocking.whitelist', value) @@ -337,20 +337,20 @@ class TestAdd: assert str(config_stub.get(name)[key]) == value else: with pytest.raises( - cmdexc.CommandError, + cmdutils.CommandError, match="w already exists in aliases - use --replace to " "overwrite!"): commands.config_dict_add(name, key, value, replace=False) def test_dict_add_non_dict(self, commands): with pytest.raises( - cmdexc.CommandError, + cmdutils.CommandError, match=":config-dict-add can only be used for dicts"): commands.config_dict_add('history_gap_interval', 'key', 'value') @pytest.mark.parametrize('value', ['', None, 42]) def test_dict_add_invalid_values(self, commands, value): - with pytest.raises(cmdexc.CommandError, + with pytest.raises(cmdutils.CommandError, match="Invalid value '{}'".format(value)): commands.config_dict_add('aliases', 'missingkey', value) @@ -373,14 +373,14 @@ class TestRemove: def test_list_remove_non_list(self, commands): with pytest.raises( - cmdexc.CommandError, + cmdutils.CommandError, match=":config-list-remove can only be used for lists"): commands.config_list_remove('content.javascript.enabled', 'never') def test_list_remove_no_value(self, commands): with pytest.raises( - cmdexc.CommandError, + cmdutils.CommandError, match="never is not in colors.completion.fg!"): commands.config_list_remove('colors.completion.fg', 'never') @@ -398,14 +398,14 @@ class TestRemove: def test_dict_remove_non_dict(self, commands): with pytest.raises( - cmdexc.CommandError, + cmdutils.CommandError, match=":config-dict-remove can only be used for dicts"): commands.config_dict_remove('content.javascript.enabled', 'never') def test_dict_remove_no_value(self, commands): with pytest.raises( - cmdexc.CommandError, + cmdutils.CommandError, match="never is not in aliases!"): commands.config_dict_remove('aliases', 'never') @@ -425,7 +425,7 @@ class TestUnsetAndClear: assert yaml_value(name) == ('never' if temp else configutils.UNSET) def test_unset_unknown_option(self, commands): - with pytest.raises(cmdexc.CommandError, match="No option 'tabs'"): + with pytest.raises(cmdutils.CommandError, match="No option 'tabs'"): commands.config_unset('tabs') @pytest.mark.parametrize('save', [True, False]) @@ -472,7 +472,7 @@ class TestSource: pyfile = config_tmpdir / 'config.py' pyfile.write_text('c.foo = 42', encoding='utf-8') - with pytest.raises(cmdexc.CommandError) as excinfo: + with pytest.raises(cmdutils.CommandError) as excinfo: commands.config_source() expected = ("Errors occurred while reading config.py:\n" @@ -483,7 +483,7 @@ class TestSource: pyfile = config_tmpdir / 'config.py' pyfile.write_text('1/0', encoding='utf-8') - with pytest.raises(cmdexc.CommandError) as excinfo: + with pytest.raises(cmdutils.CommandError) as excinfo: commands.config_source() expected = ("Errors occurred while reading config.py:\n" @@ -582,7 +582,7 @@ class TestWritePy: confpy = tmpdir / 'config.py' confpy.ensure() - with pytest.raises(cmdexc.CommandError) as excinfo: + with pytest.raises(cmdutils.CommandError) as excinfo: commands.config_write_py(str(confpy)) expected = " already exists - use --force to overwrite!" @@ -599,7 +599,7 @@ class TestWritePy: def test_oserror(self, commands, tmpdir): """Test writing to a directory which does not exist.""" - with pytest.raises(cmdexc.CommandError): + with pytest.raises(cmdutils.CommandError): commands.config_write_py(str(tmpdir / 'foo' / 'config.py')) @@ -709,7 +709,7 @@ class TestBind: elif command == 'unbind': func = commands.unbind - with pytest.raises(cmdexc.CommandError, match=expected): + with pytest.raises(cmdutils.CommandError, match=expected): func(*args, **kwargs) @pytest.mark.parametrize('key', ['a', 'b', '']) diff --git a/tests/unit/misc/test_utilcmds.py b/tests/unit/misc/test_utilcmds.py index cfa115412..73f97095f 100644 --- a/tests/unit/misc/test_utilcmds.py +++ b/tests/unit/misc/test_utilcmds.py @@ -28,7 +28,7 @@ import pytest from PyQt5.QtCore import QUrl from qutebrowser.misc import utilcmds -from qutebrowser.commands import cmdexc +from qutebrowser.api import cmdutils from qutebrowser.utils import utils, objreg @@ -83,14 +83,14 @@ def test_debug_trace_exception(mocker): hunter_mock = mocker.patch('qutebrowser.misc.utilcmds.hunter') hunter_mock.trace.side_effect = _mock_exception - with pytest.raises(cmdexc.CommandError, match='Exception: message'): + with pytest.raises(cmdutils.CommandError, match='Exception: message'): utilcmds.debug_trace() def test_debug_trace_no_hunter(monkeypatch): """Test that an error is shown if debug_trace is called without hunter.""" monkeypatch.setattr(utilcmds, 'hunter', None) - with pytest.raises(cmdexc.CommandError, match="You need to install " + with pytest.raises(cmdutils.CommandError, match="You need to install " "'hunter' to use this command!"): utilcmds.debug_trace() @@ -103,7 +103,7 @@ def test_repeat_command_initial(mocker, mode_manager): """ objreg_mock = mocker.patch('qutebrowser.misc.utilcmds.objreg') objreg_mock.get.return_value = mode_manager - with pytest.raises(cmdexc.CommandError, + with pytest.raises(cmdutils.CommandError, match="You didn't do anything yet."): utilcmds.repeat_command(win_id=0) diff --git a/tests/unit/utils/test_urlutils.py b/tests/unit/utils/test_urlutils.py index 26b063456..1c1efffab 100644 --- a/tests/unit/utils/test_urlutils.py +++ b/tests/unit/utils/test_urlutils.py @@ -27,7 +27,7 @@ from PyQt5.QtCore import QUrl from PyQt5.QtNetwork import QNetworkProxy import pytest -from qutebrowser.commands import cmdexc +from qutebrowser.api import cmdutils from qutebrowser.browser.network import pac from qutebrowser.utils import utils, urlutils, qtutils, usertypes from helpers import utils as testutils @@ -495,7 +495,7 @@ def test_raise_cmdexc_if_invalid(url, valid, has_err_string): expected_text = "Invalid URL - " + qurl.errorString() else: expected_text = "Invalid URL" - with pytest.raises(cmdexc.CommandError, match=expected_text): + with pytest.raises(cmdutils.CommandError, match=expected_text): urlutils.raise_cmdexc_if_invalid(qurl) -- cgit v1.2.3-54-g00ecf