summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <git@the-compiler.org>2015-01-24 14:21:07 +0100
committerFlorian Bruhin <git@the-compiler.org>2015-02-11 23:11:34 +0100
commit5f310b43850964257afdf0dfd3b3854aceede120 (patch)
treedd6910d5a2bdc443e45ca5d0b95f2c9630e5bb83
parentb2a01934b682235529b4d8d98626f0e71bb0e203 (diff)
downloadqutebrowser-5f310b43850964257afdf0dfd3b3854aceede120.tar.gz
qutebrowser-5f310b43850964257afdf0dfd3b3854aceede120.zip
Produce better titles for crash logs.
See #483 and #447.
-rw-r--r--qutebrowser/misc/crashdialog.py80
-rw-r--r--qutebrowser/test/misc/test_crashdialog.py75
2 files changed, 142 insertions, 13 deletions
diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py
index 62753b57c..27932d18c 100644
--- a/qutebrowser/misc/crashdialog.py
+++ b/qutebrowser/misc/crashdialog.py
@@ -21,29 +21,52 @@
"""The dialog which gets shown when qutebrowser crashes."""
+import re
import sys
import html
import getpass
import traceback
import functools
-from PyQt5.QtCore import pyqtSlot, Qt, QSize
+from PyQt5.QtCore import pyqtSlot, Qt, QSize, QT_VERSION_STR
from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton,
QVBoxLayout, QHBoxLayout, QCheckBox)
+import qutebrowser
from qutebrowser.utils import version, log, utils, objreg
from qutebrowser.misc import miscwidgets
from qutebrowser.browser.network import pastebin
from qutebrowser.config import config
+def parse_fatal_stacktrace(text):
+ """Get useful information from a fatal faulthandler stacktrace.
+
+ Args:
+ text: The text to parse.
+
+ Return:
+ A tuple with the first element being the error type, and the second
+ element being the first stacktrace frame.
+ """
+ lines = [
+ r'Fatal Python error: (.*)',
+ r' *',
+ r'Current thread [^ ]* \(most recent call first\): *',
+ r' File ".*", line \d+ in (.*)',
+ ]
+ m = re.match('\n'.join(lines), text)
+ if m is None:
+ # We got some invalid text.
+ return ('', '')
+ else:
+ return (m.group(1), m.group(2))
+
+
class _CrashDialog(QDialog):
"""Dialog which gets shown after there was a crash.
- Class attributes:
- NAME: The kind of condition we report.
-
Attributes:
These are just here to have a static reference to avoid GCing.
_vbox: The main QVBoxLayout
@@ -57,8 +80,6 @@ class _CrashDialog(QDialog):
_resolution: Whether the dialog should be accepted on close.
"""
- NAME = None
-
def __init__(self, debug, parent=None):
"""Constructor for CrashDialog.
@@ -189,6 +210,23 @@ class _CrashDialog(QDialog):
text = '\n\n'.join(chunks)
self._debug_log.setText(text)
+ def _get_error_type(self):
+ """Get the type of the error we're reporting."""
+ raise NotImplementedError
+
+ def _get_paste_title_desc(self):
+ """Get a short description of the paste."""
+ return ''
+
+ def _get_paste_title(self):
+ """Get a title for the paste."""
+ desc = self._get_paste_title_desc()
+ title = "qutebrowser {} (Qt {}) {}".format(
+ qutebrowser.__version__, QT_VERSION_STR, self._get_error_type())
+ if desc:
+ title += ' - {}'.format(desc)
+ return title
+
def report(self):
"""Paste the crash info into the pastebin."""
lines = []
@@ -206,7 +244,7 @@ class _CrashDialog(QDialog):
user = 'unknown'
try:
# parent: http://p.cmpl.cc/90286958
- self._paste_client.paste(user, "qutebrowser {}".format(self.NAME),
+ self._paste_client.paste(user, self._get_paste_title(),
self._paste_text, parent='90286958')
except Exception as e:
log.misc.exception("Error while paste-binning")
@@ -276,8 +314,6 @@ class ExceptionCrashDialog(_CrashDialog):
_objects: A list of all QObjects as string.
"""
- NAME = 'exception'
-
def __init__(self, debug, pages, cmdhist, exc, objects, parent=None):
self._chk_log = None
super().__init__(debug, parent)
@@ -329,6 +365,13 @@ class ExceptionCrashDialog(_CrashDialog):
self._vbox.addWidget(info_label)
self._chk_report.toggled.connect(self.on_chk_report_toggled)
+ def _get_error_type(self):
+ return 'exception'
+
+ def _get_paste_title_desc(self):
+ desc = traceback.format_exception_only(self._exc[0], self._exc[1])
+ return desc[0].rstrip()
+
def _gather_crash_info(self):
self._crash_info += [
("Exception", ''.join(traceback.format_exception(*self._exc))),
@@ -363,15 +406,25 @@ class FatalCrashDialog(_CrashDialog):
Attributes:
_log: The log text to display.
+ _type: The type of error which occured.
+ _func: The function (top of the stack) in which the error occured.
"""
- NAME = 'segfault'
-
def __init__(self, debug, text, parent=None):
super().__init__(debug, parent)
self._log = text
self.setAttribute(Qt.WA_DeleteOnClose)
self._set_crash_info()
+ self._type, self._func = parse_fatal_stacktrace(self._log)
+
+ def _get_error_type(self):
+ return self._type
+
+ def _get_paste_title_desc(self):
+ if self._func:
+ return 'in {}'.format(self._func)
+ else:
+ return ''
def _init_text(self):
super()._init_text()
@@ -408,8 +461,6 @@ class ReportDialog(_CrashDialog):
_objects: A list of all QObjects as string.
"""
- NAME = 'report'
-
def __init__(self, pages, cmdhist, objects, parent=None):
super().__init__(False, parent)
self.setAttribute(Qt.WA_DeleteOnClose)
@@ -436,6 +487,9 @@ class ReportDialog(_CrashDialog):
"""We don't want any checkboxes as the user wanted to report."""
pass
+ def _get_error_type(self):
+ return 'report'
+
def _gather_crash_info(self):
super()._gather_crash_info()
self._crash_info += [
diff --git a/qutebrowser/test/misc/test_crashdialog.py b/qutebrowser/test/misc/test_crashdialog.py
new file mode 100644
index 000000000..c16dac44d
--- /dev/null
+++ b/qutebrowser/test/misc/test_crashdialog.py
@@ -0,0 +1,75 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+"""Tests for qutebrowser.misc.crashdialog."""
+
+import unittest
+
+from qutebrowser.misc import crashdialog
+
+
+VALID_CRASH_TEXT = """
+Fatal Python error: Segmentation fault
+_
+Current thread 0x00007f09b538d700 (most recent call first):
+ File "", line 1 in testfunc
+ File "filename", line 88 in func
+"""
+
+VALID_CRASH_TEXT_EMPTY = """
+Fatal Python error: Aborted
+_
+Current thread 0x00007f09b538d700 (most recent call first):
+ File "", line 1 in_
+ File "filename", line 88 in func
+"""
+
+INVALID_CRASH_TEXT = """
+Hello world!
+"""
+
+
+class ParseFatalStacktraceTests(unittest.TestCase):
+
+ """Tests for parse_fatal_stacktrace."""
+
+ def test_valid_text(self):
+ """Test parse_fatal_stacktrace with a valid text."""
+ text = VALID_CRASH_TEXT.strip().replace('_', ' ')
+ typ, func = crashdialog.parse_fatal_stacktrace(text)
+ self.assertEqual(typ, "Segmentation fault")
+ self.assertEqual(func, 'testfunc')
+
+ def test_valid_text(self):
+ """Test parse_fatal_stacktrace with a valid text but empty function."""
+ text = VALID_CRASH_TEXT_EMPTY.strip().replace('_', ' ')
+ typ, func = crashdialog.parse_fatal_stacktrace(text)
+ self.assertEqual(typ, 'Aborted')
+ self.assertEqual(func, '')
+
+ def test_invalid_text(self):
+ """Test parse_fatal_stacktrace with an invalid text."""
+ text = INVALID_CRASH_TEXT.strip().replace('_', ' ')
+ typ, func = crashdialog.parse_fatal_stacktrace(text)
+ self.assertEqual(typ, '')
+ self.assertEqual(func, '')
+
+
+if __name__ == '__main__':
+ unittest.main()