Smoke test WIP

This commit is contained in:
Florian Bruhin 2015-03-27 07:59:13 +01:00
parent 1e18ce94cf
commit 9be5992a9a
10 changed files with 156 additions and 49 deletions

View File

@ -80,6 +80,9 @@ It was inspired by other browsers/addons like dwb and Vimperator/Pentadactyl.
*--no-crash-dialog*:: *--no-crash-dialog*::
Don't show a crash dialog. Don't show a crash dialog.
*--no-err-windows*::
Don't show any error windows.
*--qt-name* 'NAME':: *--qt-name* 'NAME'::
Set the window name. Set the window name.

View File

@ -30,7 +30,7 @@ import functools
import traceback import traceback
import faulthandler import faulthandler
from PyQt5.QtWidgets import QApplication, QDialog, QMessageBox from PyQt5.QtWidgets import QApplication, QDialog
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl, from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
QObject, Qt, QSocketNotifier) QObject, Qt, QSocketNotifier)
@ -48,7 +48,7 @@ from qutebrowser.misc import (crashdialog, readline, ipc, earlyinit,
from qutebrowser.misc import utilcmds # pylint: disable=unused-import from qutebrowser.misc import utilcmds # pylint: disable=unused-import
from qutebrowser.keyinput import modeman from qutebrowser.keyinput import modeman
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils, from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
debug, objreg, usertypes, standarddir) debug, objreg, usertypes, standarddir, error)
# We import utilcmds to run the cmdutils.register decorators. # We import utilcmds to run the cmdutils.register decorators.
@ -102,22 +102,20 @@ class Application(QApplication):
print(qutebrowser.__copyright__) print(qutebrowser.__copyright__)
print() print()
print(version.GPL_BOILERPLATE.strip()) print(version.GPL_BOILERPLATE.strip())
sys.exit(0) sys.exit(usertypes.Exit.ok)
try: try:
sent = ipc.send_to_running_instance(self._args.command) sent = ipc.send_to_running_instance(self._args.command)
if sent: if sent:
sys.exit(0) sys.exit(usertypes.Exit.ok)
log.init.debug("Starting IPC server...") log.init.debug("Starting IPC server...")
ipc.init() ipc.init()
except ipc.IPCError as e: except ipc.IPCError as e:
text = ('{}\n\nMaybe another instance is running but ' error.handle_fatal_exc(
'frozen?'.format(e)) e, self._args, "Error while connecting to running instance!",
msgbox = QMessageBox(QMessageBox.Critical, "Error while " post_text="Maybe another instance is running but frozen?")
"connecting to running instance!", text)
msgbox.exec_()
# We didn't really initialize much so far, so we just quit hard. # We didn't really initialize much so far, so we just quit hard.
sys.exit(1) sys.exit(usertypes.Exit.err_ipc)
log.init.debug("Starting init...") log.init.debug("Starting init...")
self.setQuitOnLastWindowClosed(False) self.setQuitOnLastWindowClosed(False)
@ -129,11 +127,9 @@ class Application(QApplication):
try: try:
self._init_modules() self._init_modules()
except (OSError, UnicodeDecodeError) as e: except (OSError, UnicodeDecodeError) as e:
msgbox = QMessageBox( error.handle_fatal_exc(e, self._args, "Error while initializing!",
QMessageBox.Critical, "Error while initializing!", pre_text="Error while initializing")
"Error while initializing: {}".format(e)) sys.exit(usertypes.Exit.err_init)
msgbox.exec_()
sys.exit(1)
QTimer.singleShot(0, self._process_args) QTimer.singleShot(0, self._process_args)
log.init.debug("Initializing eventfilter...") log.init.debug("Initializing eventfilter...")
@ -179,7 +175,8 @@ class Application(QApplication):
log.init.debug("Initializing web history...") log.init.debug("Initializing web history...")
history.init() history.init()
log.init.debug("Initializing crashlog...") log.init.debug("Initializing crashlog...")
self._handle_segfault() if not self._args.no_err_windows:
self._handle_segfault()
log.init.debug("Initializing sessions...") log.init.debug("Initializing sessions...")
session_manager = sessions.SessionManager(self) session_manager = sessions.SessionManager(self)
objreg.register('session-manager', session_manager) objreg.register('session-manager', session_manager)
@ -243,6 +240,7 @@ class Application(QApplication):
def _init_crashlogfile(self): def _init_crashlogfile(self):
"""Start a new logfile and redirect faulthandler to it.""" """Start a new logfile and redirect faulthandler to it."""
assert not self._args.no_err_windows
logname = os.path.join(standarddir.data(), 'crash.log') logname = os.path.join(standarddir.data(), 'crash.log')
try: try:
self._crashlogfile = open(logname, 'w', encoding='ascii') self._crashlogfile = open(logname, 'w', encoding='ascii')
@ -637,17 +635,20 @@ class Application(QApplication):
except TypeError: except TypeError:
log.destroy.exception("Error while preventing shutdown") log.destroy.exception("Error while preventing shutdown")
QApplication.closeAllWindows() QApplication.closeAllWindows()
self._crashdlg = crashdialog.ExceptionCrashDialog( if self._args.no_err_windows:
self._args.debug, pages, cmd_history, exc, objects) crashdialog.dump_exception_info(exc, pages, cmd_history, objects)
ret = self._crashdlg.exec_() else:
if ret == QDialog.Accepted: # restore self._crashdlg = crashdialog.ExceptionCrashDialog(
self.restart(shutdown=False, pages=pages) self._args.debug, pages, cmd_history, exc, objects)
ret = self._crashdlg.exec_()
if ret == QDialog.Accepted: # restore
self.restart(shutdown=False, pages=pages)
# We might risk a segfault here, but that's better than continuing to # We might risk a segfault here, but that's better than continuing to
# run in some undefined state, so we only do the most needed shutdown # run in some undefined state, so we only do the most needed shutdown
# here. # here.
qInstallMessageHandler(None) qInstallMessageHandler(None)
self._destroy_crashlogfile() self._destroy_crashlogfile()
sys.exit(1) sys.exit(usertypes.Exit.exception)
def _get_restart_args(self, pages): def _get_restart_args(self, pages):
"""Get the current working directory and args to relaunch qutebrowser. """Get the current working directory and args to relaunch qutebrowser.
@ -867,10 +868,9 @@ class Application(QApplication):
try: try:
save_manager.save(key, is_exit=True) save_manager.save(key, is_exit=True)
except OSError as e: except OSError as e:
msgbox = QMessageBox( error.handle_fatal_exc(
QMessageBox.Critical, "Error while saving!", e, self._args, "Error while saving!",
"Error while saving {}: {}".format(key, e)) pre_text="Error while saving {}".format(key))
msgbox.exec_()
# Re-enable faulthandler to stdout, then remove crash log # Re-enable faulthandler to stdout, then remove crash log
log.destroy.debug("Deactiving crash log...") log.destroy.debug("Deactiving crash log...")
self._destroy_crashlogfile() self._destroy_crashlogfile()

View File

@ -33,12 +33,12 @@ import collections
import collections.abc import collections.abc
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl, QSettings from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl, QSettings
from PyQt5.QtWidgets import QMessageBox
from qutebrowser.config import configdata, configexc, textwrapper from qutebrowser.config import configdata, configexc, textwrapper
from qutebrowser.config.parsers import ini, keyconf from qutebrowser.config.parsers import ini, keyconf
from qutebrowser.commands import cmdexc, cmdutils from qutebrowser.commands import cmdexc, cmdutils
from qutebrowser.utils import message, objreg, utils, standarddir, log, qtutils from qutebrowser.utils import (message, objreg, utils, standarddir, log,
qtutils, error, usertypes)
from qutebrowser.utils.usertypes import Completion from qutebrowser.utils.usertypes import Completion
@ -114,9 +114,9 @@ def section(sect):
def _init_main_config(): def _init_main_config():
"""Initialize the main config.""" """Initialize the main config."""
app = objreg.get('app')
args = objreg.get('args')
try: try:
app = objreg.get('app')
args = objreg.get('args')
config_obj = ConfigManager(standarddir.config(), 'qutebrowser.conf', config_obj = ConfigManager(standarddir.config(), 'qutebrowser.conf',
args.relaxed_config, app) args.relaxed_config, app)
except (configexc.Error, configparser.Error, UnicodeDecodeError) as e: except (configexc.Error, configparser.Error, UnicodeDecodeError) as e:
@ -127,12 +127,11 @@ def _init_main_config():
e.section, e.option) # pylint: disable=no-member e.section, e.option) # pylint: disable=no-member
except AttributeError: except AttributeError:
pass pass
errstr += "\n{}".format(e) errstr += "\n"
msgbox = QMessageBox(QMessageBox.Critical, error.handle_fatal_exc(e, args, "Error while reading config!",
"Error while reading config!", errstr) pre_text=errstr)
msgbox.exec_()
# We didn't really initialize much so far, so we just quit hard. # We didn't really initialize much so far, so we just quit hard.
sys.exit(1) sys.exit(usertypes.Exit.err_config)
else: else:
objreg.register('config', config_obj) objreg.register('config', config_obj)
if standarddir.config() is not None: if standarddir.config() is not None:
@ -152,6 +151,7 @@ def _init_main_config():
def _init_key_config(): def _init_key_config():
"""Initialize the key config.""" """Initialize the key config."""
args = objreg.get('args')
try: try:
key_config = keyconf.KeyConfigParser(standarddir.config(), 'keys.conf') key_config = keyconf.KeyConfigParser(standarddir.config(), 'keys.conf')
except (keyconf.KeyConfigError, UnicodeDecodeError) as e: except (keyconf.KeyConfigError, UnicodeDecodeError) as e:
@ -159,12 +159,10 @@ def _init_key_config():
errstr = "Error while reading key config:\n" errstr = "Error while reading key config:\n"
if e.lineno is not None: if e.lineno is not None:
errstr += "In line {}: ".format(e.lineno) errstr += "In line {}: ".format(e.lineno)
errstr += str(e) error.handle_fatal_exc(e, args, "Error while reading key config!",
msgbox = QMessageBox(QMessageBox.Critical, pre_text=errstr)
"Error while reading key config!", errstr)
msgbox.exec_()
# We didn't really initialize much so far, so we just quit hard. # We didn't really initialize much so far, so we just quit hard.
sys.exit(1) sys.exit(usertypes.Exit.err_key_config)
else: else:
objreg.register('key-config', key_config) objreg.register('key-config', key_config)
if standarddir.config() is not None: if standarddir.config() is not None:

View File

@ -47,7 +47,7 @@ def check_python_version():
version_str = '.'.join(map(str, sys.version_info[:3])) version_str = '.'.join(map(str, sys.version_info[:3]))
text = ("At least Python 3.4 is required to run qutebrowser, but " + text = ("At least Python 3.4 is required to run qutebrowser, but " +
version_str + " is installed!\n") version_str + " is installed!\n")
if Tk: if Tk and '--no-err-windows' not in sys.argv:
root = Tk() root = Tk()
root.withdraw() root.withdraw()
messagebox.showerror("qutebrowser: Fatal error!", text) messagebox.showerror("qutebrowser: Fatal error!", text)

View File

@ -535,3 +535,38 @@ class ReportErrorDialog(QDialog):
btn.clicked.connect(self.close) btn.clicked.connect(self.close)
hbox.addWidget(btn) hbox.addWidget(btn)
vbox.addLayout(hbox) vbox.addLayout(hbox)
def dump_exception_info(exc, pages, cmdhist, objects):
"""Dump exception info to stderr.
Args:
exc: An exception tuple (type, value, traceback)
pages: A list of lists of the open pages (URLs as strings)
cmdhist: A list with the command history (as strings)
objects: A list of all QObjects as string.
"""
print(file=sys.stderr)
print("\n\n===== Handling exception with --no-err-windows... =====\n\n",
file=sys.stderr)
print("\n---- Exceptions ----", file=sys.stderr)
print(''.join(traceback.format_exception(*exc)), file=sys.stderr)
print("\n---- Version info ----", file=sys.stderr)
try:
print(version.version(), file=sys.stderr)
except Exception:
traceback.print_exc()
print("\n---- Config ----", file=sys.stderr)
try:
conf = objreg.get('config')
print(conf.dump_userconfig(), file=sys.stderr)
except Exception:
traceback.print_exc()
print("\n---- Commandline args ----", file=sys.stderr)
print(' '.join(sys.argv[1:]), file=sys.stderr)
print("\n---- Open pages ----", file=sys.stderr)
print('\n\n'.join('\n'.join(e) for e in pages), file=sys.stderr)
print("\n---- Command history ----", file=sys.stderr)
print('\n'.join(cmdhist), file=sys.stderr)
print("\n---- Objects ----", file=sys.stderr)
print(objects, file=sys.stderr)

View File

@ -77,12 +77,15 @@ def _die(message, exception=None):
print(file=sys.stderr) print(file=sys.stderr)
traceback.print_exc() traceback.print_exc()
app = QApplication(sys.argv) app = QApplication(sys.argv)
message += '<br/><br/><br/><b>Error:</b><br/>{}'.format(exception) if '--no-err-windows' in sys.argv:
msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!", print("Exiting because of --no-err-windows.", file=sys.stderr)
message) else:
msgbox.setTextFormat(Qt.RichText) message += '<br/><br/><br/><b>Error:</b><br/>{}'.format(exception)
msgbox.resize(msgbox.sizeHint()) msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!",
msgbox.exec_() message)
msgbox.setTextFormat(Qt.RichText)
msgbox.resize(msgbox.sizeHint())
msgbox.exec_()
app.quit() app.quit()
sys.exit(1) sys.exit(1)
@ -179,13 +182,13 @@ def check_pyqt_core():
text = text.replace('</b>', '') text = text.replace('</b>', '')
text = text.replace('<br />', '\n') text = text.replace('<br />', '\n')
text += '\n\nError: {}'.format(e) text += '\n\nError: {}'.format(e)
if tkinter: if tkinter and '--no-err-windows' not in sys.argv:
root = tkinter.Tk() root = tkinter.Tk()
root.withdraw() root.withdraw()
tkinter.messagebox.showerror("qutebrowser: Fatal error!", text) tkinter.messagebox.showerror("qutebrowser: Fatal error!", text)
else: else:
print(text, file=sys.stderr) print(text, file=sys.stderr)
if '--debug' in sys.argv: if '--debug' in sys.argv or '--no-err-windows' in sys.argv:
print(file=sys.stderr) print(file=sys.stderr)
traceback.print_exc() traceback.print_exc()
sys.exit(1) sys.exit(1)

View File

@ -84,6 +84,8 @@ def get_argparser():
action='store_true') action='store_true')
debug.add_argument('--no-crash-dialog', action='store_true', help="Don't " debug.add_argument('--no-crash-dialog', action='store_true', help="Don't "
"show a crash dialog.") "show a crash dialog.")
debug.add_argument('--no-err-windows', action='store_true', help="Don't "
"show any error windows (used for tests/smoke.py).")
# For the Qt args, we use store_const with const=True rather than # For the Qt args, we use store_const with const=True rather than
# store_true because we want the default to be None, to make # store_true because we want the default to be None, to make
# utils.qt:get_args easier. # utils.qt:get_args easier.

View File

@ -0,0 +1,54 @@
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 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/>.
"""Tools related to error printing/displaying."""
from PyQt5.QtWidgets import QMessageBox
from qutebrowser.utils import log
def handle_fatal_exc(exc, args, title, *, pre_text='', post_text=''):
"""Handle a fatal "expected" exception by displaying an error box.
If --no-err-windows is given as argument, the text is logged to the error
logger instead.
Args:
exc: The Exception object being handled.
args: The argparser namespace.
title: The title to be used for the error message.
pre_text: The text to be displayed before the exception text.
post_text: The text to be displayed after the exception text.
"""
if args.no_err_windows:
log.misc.exception("Handling fatal {} with --no-err-windows!".format(
exc.__class__.__name__))
log.misc.error("title: {}".format(title))
log.misc.error("pre_text: {}".format(pre_text))
log.misc.error("post_text: {}".format(post_text))
else:
if pre_text:
msg_text = '{}: {}'.format(pre_text, exc)
else:
msg_text = str(exc)
if post_text:
msg_text += '\n\n{}'.format(post_text)
msgbox = QMessageBox(QMessageBox.Critical, title, msg_text)
msgbox.exec_()

View File

@ -240,6 +240,11 @@ Completion = enum('Completion', ['command', 'section', 'option', 'value',
'quickmark_by_name', 'url', 'sessions']) 'quickmark_by_name', 'url', 'sessions'])
# Exit statuses for errors
Exit = enum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', 'err_init',
'err_config', 'err_key_config'])
class Question(QObject): class Question(QObject):
"""A question asked to the user, e.g. via the status bar. """A question asked to the user, e.g. via the status bar.

View File

@ -98,3 +98,10 @@ commands =
{envpython} scripts/src2asciidoc.py {envpython} scripts/src2asciidoc.py
git --no-pager diff --exit-code --stat git --no-pager diff --exit-code --stat
{envpython} scripts/asciidoc2html.py {posargs} {envpython} scripts/asciidoc2html.py {posargs}
[testenv:smoke]
deps =
-rrequirements.txt
commands =
{[testenv:mkvenv]commands}
{envpython} -m qutebrowser --debug --no-err-windows --nowindow --temp-basedir about:blank ":later 500 quit"