parent
b86aa9061a
commit
5b3b324331
@ -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 += [
|
||||
|
75
qutebrowser/test/misc/test_crashdialog.py
Normal file
75
qutebrowser/test/misc/test_crashdialog.py
Normal file
@ -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()
|
Loading…
Reference in New Issue
Block a user