2015-10-10 19:21:12 +02:00
|
|
|
# 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/>.
|
|
|
|
|
|
|
|
# pylint doesn't understand the testprocess import
|
|
|
|
# pylint: disable=no-member
|
|
|
|
|
|
|
|
"""Fixtures to run qutebrowser in a QProcess and communicate."""
|
|
|
|
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
import os.path
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
from PyQt5.QtCore import pyqtSignal
|
|
|
|
|
|
|
|
import testprocess # pylint: disable=import-error
|
|
|
|
from qutebrowser.misc import ipc
|
|
|
|
|
|
|
|
|
2015-10-22 06:44:05 +02:00
|
|
|
class NoLineMatch(Exception):
|
2015-10-10 19:21:12 +02:00
|
|
|
|
2015-10-22 06:44:05 +02:00
|
|
|
"""Raised by LogLine on unmatched lines."""
|
2015-10-10 19:21:12 +02:00
|
|
|
|
2015-10-22 06:44:05 +02:00
|
|
|
pass
|
2015-10-10 19:21:12 +02:00
|
|
|
|
|
|
|
|
2015-10-22 06:44:05 +02:00
|
|
|
class LogLine:
|
2015-10-10 19:21:12 +02:00
|
|
|
|
|
|
|
LOG_RE = re.compile(r"""
|
|
|
|
(?P<timestamp>\d\d:\d\d:\d\d)
|
|
|
|
\ (?P<loglevel>VDEBUG|DEBUG|INFO|WARNING|ERROR)
|
|
|
|
\ +(?P<category>\w+)
|
2015-10-14 07:51:58 +02:00
|
|
|
\ +(?P<module>(\w+|Unknown\ module)):(?P<function>\w+):(?P<line>\d+)
|
2015-10-10 19:21:12 +02:00
|
|
|
\ (?P<message>.+)
|
|
|
|
""", re.VERBOSE)
|
|
|
|
|
2015-10-22 06:44:05 +02:00
|
|
|
def __init__(self, line):
|
|
|
|
match = self.LOG_RE.match(line)
|
|
|
|
if match is None:
|
|
|
|
raise NoLineMatch(line)
|
|
|
|
self.__dict__.update(match.groupdict())
|
|
|
|
self.expected = False
|
|
|
|
|
|
|
|
|
|
|
|
class QuteProc(testprocess.Process):
|
|
|
|
|
|
|
|
"""A running qutebrowser process used for tests.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
_ipc_socket: The IPC socket of the started instance.
|
|
|
|
_httpbin: The HTTPBin webserver.
|
|
|
|
"""
|
|
|
|
|
2015-10-10 19:21:12 +02:00
|
|
|
executing_command = pyqtSignal()
|
2015-10-11 13:53:59 +02:00
|
|
|
setting_done = pyqtSignal()
|
|
|
|
url_loaded = pyqtSignal()
|
2015-10-21 22:05:41 +02:00
|
|
|
got_error = pyqtSignal()
|
2015-10-10 19:21:12 +02:00
|
|
|
|
2015-10-11 13:53:59 +02:00
|
|
|
def __init__(self, httpbin, parent=None):
|
2015-10-10 19:21:12 +02:00
|
|
|
super().__init__(parent)
|
2015-10-11 13:53:59 +02:00
|
|
|
self._httpbin = httpbin
|
2015-10-10 19:21:12 +02:00
|
|
|
self._ipc_socket = None
|
|
|
|
|
|
|
|
def _parse_line(self, line):
|
2015-10-22 06:44:05 +02:00
|
|
|
try:
|
|
|
|
log_line = LogLine(line)
|
|
|
|
except NoLineMatch:
|
2015-10-10 19:21:12 +02:00
|
|
|
if line.startswith(' '):
|
|
|
|
# Multiple lines in some log output...
|
|
|
|
return None
|
|
|
|
elif not line.strip():
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
raise testprocess.InvalidLine
|
|
|
|
|
2015-10-13 06:40:12 +02:00
|
|
|
if (log_line.loglevel in ['INFO', 'WARNING', 'ERROR'] or
|
|
|
|
pytest.config.getoption('--verbose')):
|
|
|
|
print(line)
|
|
|
|
|
2015-10-10 19:21:12 +02:00
|
|
|
start_okay_message = ("load status for "
|
|
|
|
"<qutebrowser.browser.webview.WebView tab_id=0 "
|
|
|
|
"url='about:blank'>: LoadStatus.success")
|
|
|
|
|
2015-10-11 13:53:59 +02:00
|
|
|
url_loaded_pattern = re.compile(
|
|
|
|
r"load status for <qutebrowser.browser.webview.WebView tab_id=\d+ "
|
|
|
|
r"url='[^']+'>: LoadStatus.success")
|
|
|
|
|
2015-10-10 19:21:12 +02:00
|
|
|
if (log_line.category == 'ipc' and
|
|
|
|
log_line.message.startswith("Listening as ")):
|
|
|
|
self._ipc_socket = log_line.message.split(' ', maxsplit=2)[2]
|
|
|
|
elif (log_line.category == 'webview' and
|
|
|
|
log_line.message == start_okay_message):
|
|
|
|
self.ready.emit()
|
|
|
|
elif (log_line.category == 'commands' and
|
2015-10-11 13:53:59 +02:00
|
|
|
log_line.module == 'command' and log_line.function == 'run' and
|
2015-10-10 19:21:12 +02:00
|
|
|
log_line.message.startswith('Calling ')):
|
|
|
|
self.executing_command.emit()
|
2015-10-11 13:53:59 +02:00
|
|
|
elif (log_line.category == 'config' and log_line.message.startswith(
|
|
|
|
'Config option changed: ')):
|
|
|
|
self.setting_done.emit()
|
|
|
|
elif (log_line.category == 'webview' and
|
|
|
|
url_loaded_pattern.match(log_line.message)):
|
|
|
|
self.url_loaded.emit()
|
2015-10-21 22:05:41 +02:00
|
|
|
elif log_line.loglevel in ['WARNING', 'ERROR']:
|
2015-10-27 06:38:45 +01:00
|
|
|
self.got_error.emit()
|
2015-10-10 19:21:12 +02:00
|
|
|
|
|
|
|
return log_line
|
|
|
|
|
|
|
|
def _executable_args(self):
|
|
|
|
if hasattr(sys, 'frozen'):
|
|
|
|
executable = os.path.join(os.path.dirname(sys.executable),
|
|
|
|
'qutebrowser')
|
|
|
|
args = []
|
|
|
|
else:
|
|
|
|
executable = sys.executable
|
|
|
|
args = ['-m', 'qutebrowser']
|
|
|
|
args += ['--debug', '--no-err-windows', '--temp-basedir',
|
|
|
|
'about:blank']
|
|
|
|
return executable, args
|
|
|
|
|
|
|
|
def after_test(self):
|
|
|
|
bad_msgs = [msg for msg in self._data
|
2015-10-22 06:44:05 +02:00
|
|
|
if msg.loglevel not in ['VDEBUG', 'DEBUG', 'INFO']
|
|
|
|
and not msg.expected]
|
2015-10-10 19:21:12 +02:00
|
|
|
super().after_test()
|
|
|
|
if bad_msgs:
|
|
|
|
text = 'Logged unexpected errors:\n\n' + '\n'.join(
|
|
|
|
str(e) for e in bad_msgs)
|
|
|
|
pytest.fail(text, pytrace=False)
|
|
|
|
|
|
|
|
def send_cmd(self, command):
|
|
|
|
assert self._ipc_socket is not None
|
|
|
|
with self._wait_signal(self.executing_command):
|
2015-10-11 13:53:59 +02:00
|
|
|
ipc.send_to_running_instance(self._ipc_socket, [command],
|
2015-10-10 19:21:12 +02:00
|
|
|
target_arg='')
|
|
|
|
# Wait a bit in cause the command triggers any error.
|
|
|
|
time.sleep(0.5)
|
|
|
|
|
2015-10-11 13:53:59 +02:00
|
|
|
def set_setting(self, sect, opt, value):
|
|
|
|
with self._wait_signal(self.setting_done):
|
|
|
|
self.send_cmd(':set "{}" "{}" "{}"'.format(sect, opt, value))
|
|
|
|
|
2015-10-13 07:14:32 +02:00
|
|
|
def open_path(self, path, new_tab=False):
|
2015-10-11 13:53:59 +02:00
|
|
|
url = 'http://localhost:{}/{}'.format(self._httpbin.port, path)
|
|
|
|
with self._wait_signal(self.url_loaded):
|
2015-10-13 07:14:32 +02:00
|
|
|
if new_tab:
|
|
|
|
self.send_cmd(':open -t ' + url)
|
|
|
|
else:
|
|
|
|
self.send_cmd(':open ' + url)
|
2015-10-11 13:53:59 +02:00
|
|
|
|
2015-10-22 06:44:05 +02:00
|
|
|
def mark_expected(self, category=None, loglevel=None, msg=None):
|
|
|
|
"""Mark a given logging message as expected."""
|
|
|
|
for item in self._data:
|
|
|
|
if category is not None and item.category != category:
|
|
|
|
continue
|
|
|
|
elif loglevel is not None and item.loglevel != loglevel:
|
|
|
|
continue
|
|
|
|
elif msg is not None and item.message != msg:
|
|
|
|
continue
|
|
|
|
item.expected = True
|
|
|
|
|
2015-10-10 19:21:12 +02:00
|
|
|
|
2015-10-13 19:38:25 +02:00
|
|
|
@pytest.yield_fixture
|
2015-10-11 13:53:59 +02:00
|
|
|
def quteproc(qapp, httpbin):
|
2015-10-10 19:21:12 +02:00
|
|
|
"""Fixture for qutebrowser process."""
|
2015-10-11 13:53:59 +02:00
|
|
|
proc = QuteProc(httpbin)
|
2015-10-10 19:21:12 +02:00
|
|
|
proc.start()
|
|
|
|
yield proc
|
2015-10-27 07:11:23 +01:00
|
|
|
proc.terminate()
|
2015-10-27 07:14:40 +01:00
|
|
|
proc.after_test()
|