Repository: qutebrowser/qutebrowser

Issue:
Unhandled exception: 'caret' when reading config.py
When I run `:config-source`, I get this error:
```
ERROR: Errors occurred while reading config.py:
  Unhandled exception: 'caret'
```
What does it mean? Why does the error happen only in `:config-source` and not when starting the browser?

My `config.py` is [here](https://github.com/lahwaacz/dotfiles/blob/a5f2474d5cf297419df88b115072a3e5370c5e8b/.config/qutebrowser/config.py).


Discussion:
My first guess is that what's happening is a `KeyError`, but we only print the exception text and not the type:

```python
>>> d = {}
>>> d['caret']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'caret'
```

qutebrowser should definitely print more there!

As for why it's happening: You read the default bindings in `"caret": c.bindings.default["caret"],` and then later in your config do `c.bindings.default = {}` - so the second time your `config.py` runs, you can't read them anymore as they're gone.
Oh indeed, thanks for the explanation!

Resolving patch:
diff --git a/qutebrowser/config/configexc.py b/qutebrowser/config/configexc.py
--- a/qutebrowser/config/configexc.py
+++ b/qutebrowser/config/configexc.py
@@ -92,6 +92,10 @@ class ConfigErrorDesc:
     traceback = attr.ib(None)
 
     def __str__(self):
+        if self.traceback:
+            return '{} - {}: {}'.format(self.text,
+                                        self.exception.__class__.__name__,
+                                        self.exception)
         return '{}: {}'.format(self.text, self.exception)
 
     def with_text(self, text):
diff --git a/qutebrowser/config/configfiles.py b/qutebrowser/config/configfiles.py
--- a/qutebrowser/config/configfiles.py
+++ b/qutebrowser/config/configfiles.py
@@ -419,7 +419,7 @@ def read_config_py(filename, raising=False):
         desc = configexc.ConfigErrorDesc("Error while compiling", e)
         raise configexc.ConfigFileErrors(basename, [desc])
     except SyntaxError as e:
-        desc = configexc.ConfigErrorDesc("Syntax Error", e,
+        desc = configexc.ConfigErrorDesc("Unhandled exception", e,
                                          traceback=traceback.format_exc())
         raise configexc.ConfigFileErrors(basename, [desc])
 


Test patch:
diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py
--- a/tests/unit/config/test_configcommands.py
+++ b/tests/unit/config/test_configcommands.py
@@ -309,6 +309,18 @@ def test_errors(self, commands, config_tmpdir):
                     "  While setting 'foo': No option 'foo'")
         assert str(excinfo.value) == expected
 
+    def test_invalid_source(self, commands, config_tmpdir):
+        pyfile = config_tmpdir / 'config.py'
+        pyfile.write_text('1/0', encoding='utf-8')
+
+        with pytest.raises(cmdexc.CommandError) as excinfo:
+            commands.config_source()
+
+        expected = ("Errors occurred while reading config.py:\n"
+                    "  Unhandled exception - ZeroDivisionError:"
+                    " division by zero")
+        assert str(excinfo.value) == expected
+
 
 class TestEdit:
 
diff --git a/tests/unit/config/test_configexc.py b/tests/unit/config/test_configexc.py
--- a/tests/unit/config/test_configexc.py
+++ b/tests/unit/config/test_configexc.py
@@ -74,7 +74,7 @@ def test_config_file_errors_str(errors):
     assert str(errors).splitlines() == [
         'Errors occurred while reading config.py:',
         '  Error text 1: Exception 1',
-        '  Error text 2: Exception 2',
+        '  Error text 2 - Exception: Exception 2',
     ]
 
 
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -550,7 +550,7 @@ def test_syntax_error(self, confpy):
         assert len(excinfo.value.errors) == 1
         error = excinfo.value.errors[0]
         assert isinstance(error.exception, SyntaxError)
-        assert error.text == "Syntax Error"
+        assert error.text == "Unhandled exception"
         exception_text = 'invalid syntax (config.py, line 1)'
         assert str(error.exception) == exception_text