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*::
Don't show a crash dialog.
*--no-err-windows*::
Don't show any error windows.
*--qt-name* 'NAME'::
Set the window name.

View File

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

View File

@ -33,12 +33,12 @@ import collections
import collections.abc
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl, QSettings
from PyQt5.QtWidgets import QMessageBox
from qutebrowser.config import configdata, configexc, textwrapper
from qutebrowser.config.parsers import ini, keyconf
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
@ -114,9 +114,9 @@ def section(sect):
def _init_main_config():
"""Initialize the main config."""
app = objreg.get('app')
args = objreg.get('args')
try:
app = objreg.get('app')
args = objreg.get('args')
config_obj = ConfigManager(standarddir.config(), 'qutebrowser.conf',
args.relaxed_config, app)
except (configexc.Error, configparser.Error, UnicodeDecodeError) as e:
@ -127,12 +127,11 @@ def _init_main_config():
e.section, e.option) # pylint: disable=no-member
except AttributeError:
pass
errstr += "\n{}".format(e)
msgbox = QMessageBox(QMessageBox.Critical,
"Error while reading config!", errstr)
msgbox.exec_()
errstr += "\n"
error.handle_fatal_exc(e, args, "Error while reading config!",
pre_text=errstr)
# We didn't really initialize much so far, so we just quit hard.
sys.exit(1)
sys.exit(usertypes.Exit.err_config)
else:
objreg.register('config', config_obj)
if standarddir.config() is not None:
@ -152,6 +151,7 @@ def _init_main_config():
def _init_key_config():
"""Initialize the key config."""
args = objreg.get('args')
try:
key_config = keyconf.KeyConfigParser(standarddir.config(), 'keys.conf')
except (keyconf.KeyConfigError, UnicodeDecodeError) as e:
@ -159,12 +159,10 @@ def _init_key_config():
errstr = "Error while reading key config:\n"
if e.lineno is not None:
errstr += "In line {}: ".format(e.lineno)
errstr += str(e)
msgbox = QMessageBox(QMessageBox.Critical,
"Error while reading key config!", errstr)
msgbox.exec_()
error.handle_fatal_exc(e, args, "Error while reading key config!",
pre_text=errstr)
# We didn't really initialize much so far, so we just quit hard.
sys.exit(1)
sys.exit(usertypes.Exit.err_key_config)
else:
objreg.register('key-config', key_config)
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]))
text = ("At least Python 3.4 is required to run qutebrowser, but " +
version_str + " is installed!\n")
if Tk:
if Tk and '--no-err-windows' not in sys.argv:
root = Tk()
root.withdraw()
messagebox.showerror("qutebrowser: Fatal error!", text)

View File

@ -535,3 +535,38 @@ class ReportErrorDialog(QDialog):
btn.clicked.connect(self.close)
hbox.addWidget(btn)
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)
traceback.print_exc()
app = QApplication(sys.argv)
message += '<br/><br/><br/><b>Error:</b><br/>{}'.format(exception)
msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!",
message)
msgbox.setTextFormat(Qt.RichText)
msgbox.resize(msgbox.sizeHint())
msgbox.exec_()
if '--no-err-windows' in sys.argv:
print("Exiting because of --no-err-windows.", file=sys.stderr)
else:
message += '<br/><br/><br/><b>Error:</b><br/>{}'.format(exception)
msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!",
message)
msgbox.setTextFormat(Qt.RichText)
msgbox.resize(msgbox.sizeHint())
msgbox.exec_()
app.quit()
sys.exit(1)
@ -179,13 +182,13 @@ def check_pyqt_core():
text = text.replace('</b>', '')
text = text.replace('<br />', '\n')
text += '\n\nError: {}'.format(e)
if tkinter:
if tkinter and '--no-err-windows' not in sys.argv:
root = tkinter.Tk()
root.withdraw()
tkinter.messagebox.showerror("qutebrowser: Fatal error!", text)
else:
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)
traceback.print_exc()
sys.exit(1)

View File

@ -84,6 +84,8 @@ def get_argparser():
action='store_true')
debug.add_argument('--no-crash-dialog', action='store_true', help="Don't "
"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
# store_true because we want the default to be None, to make
# 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'])
# Exit statuses for errors
Exit = enum('Exit', ['ok', 'reserved', 'exception', 'err_ipc', 'err_init',
'err_config', 'err_key_config'])
class Question(QObject):
"""A question asked to the user, e.g. via the status bar.

View File

@ -98,3 +98,10 @@ commands =
{envpython} scripts/src2asciidoc.py
git --no-pager diff --exit-code --stat
{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"