diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index f9f4acfa9..9f5e39793 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -272,7 +272,11 @@ class Config(QObject): try: return configdata.DATA[name] except KeyError: - raise configexc.NoOptionError(name) from None + deleted = name in configdata.MIGRATIONS.deleted + renamed = configdata.MIGRATIONS.renamed.get(name) + exception = configexc.NoOptionError( + name, deleted=deleted, renamed=renamed) + raise exception from None def get(self, name): """Get the given setting converted for Python code.""" diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py index 7b2d0aa04..1199a9864 100644 --- a/qutebrowser/config/configexc.py +++ b/qutebrowser/config/configexc.py @@ -63,8 +63,16 @@ class NoOptionError(Error): """Raised when an option was not found.""" - def __init__(self, option): - super().__init__("No option {!r}".format(option)) + def __init__(self, option, *, deleted=False, renamed=None): + if deleted: + assert renamed is None + suffix = ' (this option was removed from qutebrowser)' + elif renamed is not None: + suffix = ' (this option was renamed to {!r})'.format(renamed) + else: + suffix = '' + + super().__init__("No option {!r}{}".format(option, suffix)) self.option = option diff --git a/tests/unit/config/test_configexc.py b/tests/unit/config/test_configexc.py index 38a74bcef..2eb44372e 100644 --- a/tests/unit/config/test_configexc.py +++ b/tests/unit/config/test_configexc.py @@ -32,10 +32,20 @@ def test_validation_error(): assert str(e) == "Invalid value 'val' - msg" -def test_no_option_error(): - e = configexc.NoOptionError('opt') +@pytest.mark.parametrize('deleted, renamed, expected', [ + (False, None, "No option 'opt'"), + (True, None, "No option 'opt' (this option was removed from qutebrowser)"), + (False, 'new', "No option 'opt' (this option was renamed to 'new')"), +]) +def test_no_option_error(deleted, renamed, expected): + e = configexc.NoOptionError('opt', deleted=deleted, renamed=renamed) assert e.option == 'opt' - assert str(e) == "No option 'opt'" + assert str(e) == expected + + +def test_no_option_error_clash(): + with pytest.raises(AssertionError): + e = configexc.NoOptionError('opt', deleted=True, renamed='foo') def test_backend_error(): diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py index 44b8cde95..27ec47b3c 100644 --- a/tests/unit/config/test_configfiles.py +++ b/tests/unit/config/test_configfiles.py @@ -534,6 +534,18 @@ class TestConfigPy: assert str(error.exception) == "No option 'foo'" assert error.traceback is None + def test_renamed_option_error(self, confpy, monkeypatch): + """Setting an option which has been renamed should show a hint.""" + monkeypatch.setattr(configdata.MIGRATIONS, 'renamed', + {'qt_args': 'qt.args'}) + confpy.write('c.qt_args = ["foo"]') + + error = confpy.read(error=True) + assert isinstance(error.exception, configexc.NoOptionError) + expected = ("No option 'qt_args' (this option was renamed to " + "'qt.args')") + assert str(error.exception) == expected + def test_multiple_errors(self, confpy): confpy.write("c.foo = 42", "config.set('foo', 42)", "1/0")