Merge branch 'smoke'

Conflicts:
      doc/qutebrowser.1.asciidoc
      qutebrowser/app.py
      qutebrowser/config/config.py
      qutebrowser/qutebrowser.py
      tox.ini
This commit is contained in:
Florian Bruhin 2015-05-17 00:28:56 +02:00
commit 3f98bf372e
12 changed files with 162 additions and 56 deletions

View File

@ -99,6 +99,9 @@ show it.
*--temp-basedir*:: *--temp-basedir*::
Use a temporary basedir. Use a temporary basedir.
*--no-err-windows*::
Don't show any error windows (used for tests/smoke.py).
*--qt-name* 'NAME':: *--qt-name* 'NAME'::
Set the window name. Set the window name.

View File

@ -29,7 +29,7 @@ import time
import shutil import shutil
import tempfile import tempfile
from PyQt5.QtWidgets import QApplication, QMessageBox from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QCursor, QWindow from PyQt5.QtGui import QDesktopServices, QPixmap, QIcon, QCursor, QWindow
from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl, from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QTimer, QUrl,
QObject, Qt, QEvent) QObject, Qt, QEvent)
@ -49,7 +49,7 @@ from qutebrowser.mainwindow import mainwindow
from qutebrowser.misc import readline, ipc, savemanager, sessions, crashsignal from qutebrowser.misc import readline, ipc, savemanager, sessions, crashsignal
from qutebrowser.misc import utilcmds # pylint: disable=unused-import from qutebrowser.misc import utilcmds # pylint: disable=unused-import
from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils, from qutebrowser.utils import (log, version, message, utils, qtutils, urlutils,
objreg, usertypes, standarddir) objreg, usertypes, standarddir, error)
# We import utilcmds to run the cmdutils.register decorators. # We import utilcmds to run the cmdutils.register decorators.
@ -91,7 +91,7 @@ def run(args):
try: try:
sent = ipc.send_to_running_instance(args) sent = ipc.send_to_running_instance(args)
if sent: if sent:
sys.exit(0) sys.exit(usertypes.Exit.ok)
log.init.debug("Starting IPC server...") log.init.debug("Starting IPC server...")
server = ipc.IPCServer(args, qApp) server = ipc.IPCServer(args, qApp)
objreg.register('ipc-server', server) objreg.register('ipc-server', server)
@ -103,14 +103,14 @@ def run(args):
time.sleep(500) time.sleep(500)
sent = ipc.send_to_running_instance(args) sent = ipc.send_to_running_instance(args)
if sent: if sent:
sys.exit(0) sys.exit(usertypes.Exit.ok)
else: else:
ipc.display_error(e) ipc.display_error(e, args)
sys.exit(1) sys.exit(usertypes.Exit.err_ipc)
except ipc.Error as e: except ipc.Error as e:
ipc.display_error(e) ipc.display_error(e, args)
# 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)
init(args, crash_handler) init(args, crash_handler)
ret = qt_mainloop() ret = qt_mainloop()
@ -144,11 +144,9 @@ def init(args, crash_handler):
try: try:
_init_modules(args, crash_handler) _init_modules(args, crash_handler)
except (OSError, UnicodeDecodeError) as e: except (OSError, UnicodeDecodeError) as e:
msgbox = QMessageBox( error.handle_fatal_exc(e, 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, functools.partial(_process_args, args)) QTimer.singleShot(0, functools.partial(_process_args, args))
log.init.debug("Initializing eventfilter...") log.init.debug("Initializing eventfilter...")
@ -398,6 +396,7 @@ def _init_modules(args, crash_handler):
log.init.debug("Initializing web history...") log.init.debug("Initializing web history...")
history.init(qApp) history.init(qApp)
log.init.debug("Initializing crashlog...") log.init.debug("Initializing crashlog...")
if not args.no_err_windows:
crash_handler.handle_segfault() crash_handler.handle_segfault()
log.init.debug("Initializing sessions...") log.init.debug("Initializing sessions...")
sessions.init(qApp) sessions.init(qApp)
@ -636,10 +635,9 @@ class Quitter:
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("Deactivating crash log...") log.destroy.debug("Deactivating crash log...")
objreg.get('crash-handler').destroy_crashlogfile() objreg.get('crash-handler').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
@ -137,8 +137,8 @@ def _init_main_config(parent=None):
Args: Args:
parent: The parent to pass to ConfigManager. parent: The parent to pass to ConfigManager.
""" """
try:
args = objreg.get('args') args = objreg.get('args')
try:
config_obj = ConfigManager(standarddir.config(), 'qutebrowser.conf', config_obj = ConfigManager(standarddir.config(), 'qutebrowser.conf',
args.relaxed_config, parent=parent) args.relaxed_config, parent=parent)
except (configexc.Error, configparser.Error, UnicodeDecodeError) as e: except (configexc.Error, configparser.Error, UnicodeDecodeError) as e:
@ -149,12 +149,11 @@ def _init_main_config(parent=None):
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:
@ -178,8 +177,8 @@ def _init_key_config(parent):
Args: Args:
parent: The parent to use for the KeyConfigParser. parent: The parent to use for the KeyConfigParser.
""" """
try:
args = objreg.get('args') args = objreg.get('args')
try:
key_config = keyconf.KeyConfigParser(standarddir.config(), 'keys.conf', key_config = keyconf.KeyConfigParser(standarddir.config(), 'keys.conf',
args.relaxed_config, args.relaxed_config,
parent=parent) parent=parent)
@ -188,12 +187,10 @@ def _init_key_config(parent):
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

@ -584,3 +584,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

@ -121,6 +121,7 @@ class CrashHandler(QObject):
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
data_dir = standarddir.data() data_dir = standarddir.data()
if data_dir is None: if data_dir is None:
return return
@ -224,6 +225,9 @@ class CrashHandler(QObject):
except TypeError: except TypeError:
log.destroy.exception("Error while preventing shutdown") log.destroy.exception("Error while preventing shutdown")
self._app.closeAllWindows() self._app.closeAllWindows()
if self._args.no_err_windows:
crashdialog.dump_exception_info(exc, pages, cmd_history, objects)
else:
self._crash_dialog = crashdialog.ExceptionCrashDialog( self._crash_dialog = crashdialog.ExceptionCrashDialog(
self._args.debug, pages, cmd_history, exc, objects) self._args.debug, pages, cmd_history, exc, objects)
ret = self._crash_dialog.exec_() ret = self._crash_dialog.exec_()
@ -235,7 +239,7 @@ class CrashHandler(QObject):
# here. # here.
qInstallMessageHandler(None) qInstallMessageHandler(None)
self.destroy_crashlogfile() self.destroy_crashlogfile()
sys.exit(1) sys.exit(usertypes.Exit.exception)
def raise_crashdlg(self): def raise_crashdlg(self):
"""Raise the crash dialog if one exists.""" """Raise the crash dialog if one exists."""

View File

@ -84,6 +84,9 @@ 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)
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) message += '<br/><br/><br/><b>Error:</b><br/>{}'.format(exception)
msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!", msgbox = QMessageBox(QMessageBox.Critical, "qutebrowser: Fatal error!",
message) message)
@ -186,13 +189,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

@ -27,9 +27,8 @@ import hashlib
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject
from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QAbstractSocket from PyQt5.QtNetwork import QLocalSocket, QLocalServer, QAbstractSocket
from PyQt5.QtWidgets import QMessageBox
from qutebrowser.utils import log, usertypes from qutebrowser.utils import log, usertypes, error
CONNECT_TIMEOUT = 100 CONNECT_TIMEOUT = 100
@ -130,12 +129,12 @@ class IPCServer(QObject):
self._socketname)) self._socketname))
@pyqtSlot(int) @pyqtSlot(int)
def on_error(self, error): def on_error(self, err):
"""Convenience method which calls _socket_error on an error.""" """Convenience method which calls _socket_error on an error."""
self._timer.stop() self._timer.stop()
log.ipc.debug("Socket error {}: {}".format( log.ipc.debug("Socket error {}: {}".format(
self._socket.error(), self._socket.errorString())) self._socket.error(), self._socket.errorString()))
if error != QLocalSocket.PeerClosedError: if err != QLocalSocket.PeerClosedError:
_socket_error("handling IPC connection", self._socket) _socket_error("handling IPC connection", self._socket)
@pyqtSlot() @pyqtSlot()
@ -282,9 +281,8 @@ def send_to_running_instance(args):
return False return False
def display_error(exc): def display_error(exc, args):
"""Display a message box with an IPC error.""" """Display a message box with an IPC error."""
text = '{}\n\nMaybe another instance is running but frozen?'.format(exc) error.handle_fatal_exc(
msgbox = QMessageBox(QMessageBox.Critical, "Error while connecting to " exc, args, "Error while connecting to running instance!",
"running instance!", text) post_text="Maybe another instance is running but frozen?")
msgbox.exec_()

View File

@ -96,6 +96,8 @@ def get_argparser():
help="Drop into pdb on exceptions.") help="Drop into pdb on exceptions.")
debug.add_argument('--temp-basedir', action='store_true', help="Use a " debug.add_argument('--temp-basedir', action='store_true', help="Use a "
"temporary basedir.") "temporary basedir.")
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

@ -112,6 +112,13 @@ commands =
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"
[pytest] [pytest]
norecursedirs = .tox .venv norecursedirs = .tox .venv
markers = markers =