diff options
author | Florian Bruhin <me@the-compiler.org> | 2019-09-14 20:42:03 +0200 |
---|---|---|
committer | Florian Bruhin <me@the-compiler.org> | 2019-09-14 20:42:03 +0200 |
commit | b1a8feb6ba2346642aef78b4440550912cd1c63b (patch) | |
tree | 25eb16a8263ad0108b8953c2301dd3772900c053 | |
parent | e5e2195b3d7289f469cb407df40a0ffef60f7cbf (diff) | |
parent | d3e3991c94d192f66c902dd824c89f73cd8fe1d8 (diff) | |
download | qutebrowser-b1a8feb6ba2346642aef78b4440550912cd1c63b.tar.gz qutebrowser-b1a8feb6ba2346642aef78b4440550912cd1c63b.zip |
Merge branch 'scroll-performance'
33 files changed, 354 insertions, 74 deletions
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc index c5099e28c..22df0e1e5 100644 --- a/doc/changelog.asciidoc +++ b/doc/changelog.asciidoc @@ -33,10 +33,11 @@ Changed * qute-pass: Don't run `pass` if only a username is requested. * qute-pass: Support private domains like `myrouter.local`. * readability: Improved CSS styling -- Various performance improvements: - * When loading config files - * When typing without any completion matches - * For keyboard handling in general +- Performance improvements in various areas: + * Loading config files + * Typing without any completion matches + * General keyboard handling + * Scrolling - `:version` now shows details about the loaded autoconfig.yml/config.py. - Hosts are now additionally looked up including their ports in netrc files. - With Qt 5.10 or newer, qutebrowser now doesn't force software rendering with diff --git a/qutebrowser/app.py b/qutebrowser/app.py index eb468d6d1..cf35f918f 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -829,7 +829,6 @@ class Application(QApplication): log.init.debug("Initializing application...") - self._args = args objreg.register('args', args) objreg.register('app', self) @@ -864,7 +863,7 @@ class Application(QApplication): def exit(self, status): """Extend QApplication::exit to log the event.""" log.destroy.debug("Now calling QApplication::exit.") - if 'debug-exit' in self._args.debug_flags: + if 'debug-exit' in objects.debug_flags: if hunter is None: print("Not logging late shutdown because hunter could not be " "imported!", file=sys.stderr) diff --git a/qutebrowser/browser/browsertab.py b/qutebrowser/browser/browsertab.py index 4bd76a16a..10f430e34 100644 --- a/qutebrowser/browser/browsertab.py +++ b/qutebrowser/browser/browsertab.py @@ -528,7 +528,8 @@ class AbstractScroller(QObject): super().__init__(parent) self._tab = tab self._widget = None # type: typing.Optional[QWidget] - self.perc_changed.connect(self._log_scroll_pos_change) + if 'log-scroll-pos' in objects.debug_flags: + self.perc_changed.connect(self._log_scroll_pos_change) @pyqtSlot() def _log_scroll_pos_change(self) -> None: diff --git a/qutebrowser/browser/history.py b/qutebrowser/browser/history.py index d49000d29..3cdce4853 100644 --- a/qutebrowser/browser/history.py +++ b/qutebrowser/browser/history.py @@ -328,7 +328,7 @@ class WebHistory(sql.SqlTable): log.misc.warning("Ignoring invalid URL being added to history") return - if 'no-sql-history' in objreg.get('args').debug_flags: + if 'no-sql-history' in objects.debug_flags: return atime = int(atime) if (atime is not None) else int(time.time()) diff --git a/qutebrowser/browser/signalfilter.py b/qutebrowser/browser/signalfilter.py index 356cd27a6..7ea9f8628 100644 --- a/qutebrowser/browser/signalfilter.py +++ b/qutebrowser/browser/signalfilter.py @@ -40,7 +40,7 @@ class SignalFilter(QObject): BLACKLIST: List of signal names which should not be logged. """ - BLACKLIST = ['cur_scroll_perc_changed', 'cur_progress', 'cur_link_hovered'] + BLACKLIST = {'cur_scroll_perc_changed', 'cur_progress', 'cur_link_hovered'} def __init__(self, win_id, parent=None): super().__init__(parent) @@ -56,9 +56,12 @@ class SignalFilter(QObject): Return: A partial function calling _filter_signals with a signal. """ - return functools.partial(self._filter_signals, signal, tab) + log_signal = debug.signal_name(signal) not in self.BLACKLIST + return functools.partial( + self._filter_signals, signal=signal, + log_signal=log_signal, tab=tab) - def _filter_signals(self, signal, tab, *args): + def _filter_signals(self, *args, signal, log_signal, tab): """Filter signals and trigger TabbedBrowser signals if needed. Triggers signal if the original signal was sent from the _current_ tab @@ -72,7 +75,6 @@ class SignalFilter(QObject): tab: The WebView which the filter belongs to. *args: The args to pass to the signal. """ - log_signal = debug.signal_name(signal) not in self.BLACKLIST tabbed_browser = objreg.get('tabbed-browser', scope='window', window=self._win_id) try: diff --git a/qutebrowser/browser/webengine/interceptor.py b/qutebrowser/browser/webengine/interceptor.py index 0729188b7..47edabda8 100644 --- a/qutebrowser/browser/webengine/interceptor.py +++ b/qutebrowser/browser/webengine/interceptor.py @@ -29,6 +29,7 @@ from qutebrowser.config import config from qutebrowser.browser import shared from qutebrowser.utils import utils, log, debug, qtutils from qutebrowser.extensions import interceptors +from qutebrowser.misc import objects @attr.s @@ -106,10 +107,6 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor): interceptors.ResourceType.unknown, } - def __init__(self, args, parent=None): - super().__init__(parent) - self._args = args - def install(self, profile): """Install the interceptor on the given QWebEngineProfile.""" try: @@ -136,7 +133,7 @@ class RequestInterceptor(QWebEngineUrlRequestInterceptor): Args: info: QWebEngineUrlRequestInfo &info """ - if 'log-requests' in self._args.debug_flags: + if 'log-requests' in objects.debug_flags: resource_type = debug.qenum_key(QWebEngineUrlRequestInfo, info.resourceType()) navigation_type = debug.qenum_key(QWebEngineUrlRequestInfo, diff --git a/qutebrowser/browser/webengine/webenginetab.py b/qutebrowser/browser/webengine/webenginetab.py index cee16db9f..fc11b75a5 100644 --- a/qutebrowser/browser/webengine/webenginetab.py +++ b/qutebrowser/browser/webengine/webenginetab.py @@ -36,7 +36,7 @@ from qutebrowser.browser.webengine import (webview, webengineelem, tabhistory, interceptor, webenginequtescheme, cookies, webenginedownloads, webenginesettings, certificateerror) -from qutebrowser.misc import miscwidgets +from qutebrowser.misc import miscwidgets, objects from qutebrowser.utils import (usertypes, qtutils, log, javascript, utils, message, objreg, jinja, debug) from qutebrowser.qt import sip @@ -60,8 +60,7 @@ def init(): _qute_scheme_handler.install(webenginesettings.private_profile) log.init.debug("Initializing request interceptor...") - args = objreg.get('args') - req_interceptor = interceptor.RequestInterceptor(args=args, parent=app) + req_interceptor = interceptor.RequestInterceptor(parent=app) req_interceptor.install(webenginesettings.default_profile) if webenginesettings.private_profile: req_interceptor.install(webenginesettings.private_profile) @@ -419,7 +418,6 @@ class WebEngineScroller(browsertab.AbstractScroller): def __init__(self, tab, parent=None): super().__init__(tab, parent) - self._args = objreg.get('args') self._pos_perc = (0, 0) self._pos_px = QPoint() self._at_bottom = False @@ -477,7 +475,7 @@ class WebEngineScroller(browsertab.AbstractScroller): self._at_bottom = math.ceil(pos.y()) >= scrollable_y if (self._pos_perc != (perc_x, perc_y) or - 'no-scroll-filtering' in self._args.debug_flags): + 'no-scroll-filtering' in objects.debug_flags): self._pos_perc = perc_x, perc_y self.perc_changed.emit(*self._pos_perc) diff --git a/qutebrowser/browser/webengine/webview.py b/qutebrowser/browser/webengine/webview.py index f71f11087..a9be81ab1 100644 --- a/qutebrowser/browser/webengine/webview.py +++ b/qutebrowser/browser/webengine/webview.py @@ -27,8 +27,8 @@ from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage from qutebrowser.browser import shared from qutebrowser.browser.webengine import webenginesettings, certificateerror from qutebrowser.config import config -from qutebrowser.utils import log, debug, usertypes, objreg, qtutils -from qutebrowser.misc import miscwidgets +from qutebrowser.utils import log, debug, usertypes, qtutils +from qutebrowser.misc import miscwidgets, objects from qutebrowser.qt import sip @@ -68,7 +68,7 @@ class WebEngineView(QWebEngineView): This got introduced in Qt 5.11.0 and fixed in 5.12.0. """ - if 'lost-focusproxy' not in objreg.get('args').debug_flags: + if 'lost-focusproxy' not in objects.debug_flags: proxy = self.focusProxy() if proxy is not None: return proxy diff --git a/qutebrowser/browser/webkit/network/networkmanager.py b/qutebrowser/browser/webkit/network/networkmanager.py index e4c8da66c..46abd5cf2 100644 --- a/qutebrowser/browser/webkit/network/networkmanager.py +++ b/qutebrowser/browser/webkit/network/networkmanager.py @@ -36,6 +36,7 @@ from qutebrowser.extensions import interceptors from qutebrowser.browser.webkit import certificateerror from qutebrowser.browser.webkit.network import (webkitqutescheme, networkreply, filescheme) +from qutebrowser.misc import objects if typing.TYPE_CHECKING: # pylint: disable=unused-import,useless-suppression @@ -154,7 +155,6 @@ class NetworkManager(QNetworkAccessManager): super().__init__(parent) log.init.debug("NetworkManager init done") self.adopted_downloads = 0 - self._args = objreg.get('args') self._win_id = win_id self._tab_id = tab_id self._private = private @@ -412,7 +412,7 @@ class NetworkManager(QNetworkAccessManager): req, HOSTBLOCK_ERROR_STRING, QNetworkReply.ContentAccessDenied, self) - if 'log-requests' in self._args.debug_flags: + if 'log-requests' in objects.debug_flags: operation = debug.qenum_key(QNetworkAccessManager, op) operation = operation.replace('Operation', '').upper() log.webview.debug("{} {}, first-party {}".format( diff --git a/qutebrowser/commands/runners.py b/qutebrowser/commands/runners.py index b5f2ecd06..d8070a350 100644 --- a/qutebrowser/commands/runners.py +++ b/qutebrowser/commands/runners.py @@ -34,7 +34,7 @@ from qutebrowser.utils import message, objreg, qtutils, usertypes, utils from qutebrowser.misc import split, objects if typing.TYPE_CHECKING: - # pylint: disable=unused-import + # pylint: disable=unused-import,useless-suppression from qutebrowser.mainwindow import tabbedbrowser _ReplacementFunction = typing.Callable[['tabbedbrowser.TabbedBrowser'], str] diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py index 9dc15d6e9..74c07e3ab 100644 --- a/qutebrowser/config/configinit.py +++ b/qutebrowser/config/configinit.py @@ -80,6 +80,7 @@ def early_init(args: argparse.Namespace) -> None: message.error("set: {} - {}".format(e.__class__.__name__, e)) objects.backend = get_backend(args) + objects.debug_flags = set(args.debug_flags) configtypes.Font.monospace_fonts = config.val.fonts.monospace config.instance.changed.connect(_update_monospace_fonts) diff --git a/qutebrowser/mainwindow/statusbar/bar.py b/qutebrowser/mainwindow/statusbar/bar.py index 82e67a3e2..52da06572 100644 --- a/qutebrowser/mainwindow/statusbar/bar.py +++ b/qutebrowser/mainwindow/statusbar/bar.py @@ -232,7 +232,7 @@ class StatusBar(QWidget): self.percentage.show() elif segment == 'scroll_raw': self._hbox.addWidget(self.percentage) - self.percentage.raw = True + self.percentage.set_raw() self.percentage.show() elif segment == 'history': self._hbox.addWidget(self.backforward) diff --git a/qutebrowser/mainwindow/statusbar/percentage.py b/qutebrowser/mainwindow/statusbar/percentage.py index 90aea7910..5a6b66479 100644 --- a/qutebrowser/mainwindow/statusbar/percentage.py +++ b/qutebrowser/mainwindow/statusbar/percentage.py @@ -19,9 +19,10 @@ """Scroll percentage displayed in the statusbar.""" -from PyQt5.QtCore import pyqtSlot +from PyQt5.QtCore import pyqtSlot, Qt from qutebrowser.mainwindow.statusbar import textbase +from qutebrowser.misc import throttle class Percentage(textbase.TextBase): @@ -30,9 +31,20 @@ class Percentage(textbase.TextBase): def __init__(self, parent=None): """Constructor. Set percentage to 0%.""" - super().__init__(parent) + super().__init__(parent, elidemode=Qt.ElideNone) + self._strings = self._calc_strings() + self._set_text = throttle.Throttle(self.setText, 100, parent=self) self.set_perc(0, 0) - self.raw = False + + def set_raw(self): + self._strings = self._calc_strings(raw=True) + + def _calc_strings(self, raw=False): + """Pre-calculate strings for the statusbar.""" + fmt = '[{:02}]' if raw else '[{:02}%]' + strings = {i: fmt.format(i) for i in range(1, 100)} + strings.update({0: '[top]', 100: '[bot]'}) + return strings @pyqtSlot(int, int) def set_perc(self, x, y): # pylint: disable=unused-argument @@ -42,15 +54,7 @@ class Percentage(textbase.TextBase): x: The x percentage (int), currently ignored. y: The y percentage (int) """ - if y == 0: - self.setText('[top]') - elif y == 100: - self.setText('[bot]') - elif y is None: - self.setText('[???]') - else: - text = '[{:02}]' if self.raw else '[{:02}%]' - self.setText(text.format(y)) + self._set_text(self._strings.get(y, '[???]')) def on_tab_changed(self, tab): """Update scroll position when tab changed.""" diff --git a/qutebrowser/mainwindow/statusbar/textbase.py b/qutebrowser/mainwindow/statusbar/textbase.py index 8cfe0a846..2ee36a940 100644 --- a/qutebrowser/mainwindow/statusbar/textbase.py +++ b/qutebrowser/mainwindow/statusbar/textbase.py @@ -68,7 +68,8 @@ class TextBase(QLabel): txt: The text to set (string). """ super().setText(txt) - self._update_elided_text(self.geometry().width()) + if self._elidemode != Qt.ElideNone: + self._update_elided_text(self.geometry().width()) def resizeEvent(self, e): """Extend QLabel::resizeEvent to update the elided text afterwards.""" diff --git a/qutebrowser/mainwindow/tabbedbrowser.py b/qutebrowser/mainwindow/tabbedbrowser.py index 9f697f498..b72984229 100644 --- a/qutebrowser/mainwindow/tabbedbrowser.py +++ b/qutebrowser/mainwindow/tabbedbrowser.py @@ -205,7 +205,7 @@ class TabbedBrowser(QWidget): field: A field name which was updated. If given, the title is only set if the given field is in the template. """ - title_format = config.val.window.title_format + title_format = config.cache['window.title_format'] if field is not None and ('{' + field + '}') not in title_format: return diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index 8dae7cfb5..286a4c339 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -38,7 +38,7 @@ from PyQt5.QtCore import (pyqtSlot, qInstallMessageHandler, QObject, QSocketNotifier, QTimer, QUrl) from qutebrowser.api import cmdutils -from qutebrowser.misc import earlyinit, crashdialog, ipc +from qutebrowser.misc import earlyinit, crashdialog, ipc, objects from qutebrowser.utils import usertypes, standarddir, log, objreg, debug, utils @@ -157,9 +157,9 @@ class CrashHandler(QObject): """Report a bug in qutebrowser.""" pages = self._recover_pages() cmd_history = objreg.get('command-history')[-5:] - objects = debug.get_all_objects() + all_objects = debug.get_all_objects() self._crash_dialog = crashdialog.ReportDialog(pages, cmd_history, - objects) + all_objects) self._crash_dialog.show() def destroy_crashlogfile(self): @@ -198,11 +198,11 @@ class CrashHandler(QObject): cmd_history = [] try: - objects = debug.get_all_objects() + all_objects = debug.get_all_objects() except Exception: log.destroy.exception("Error while getting objects") - objects = "" - return ExceptionInfo(pages, cmd_history, objects) + all_objects = "" + return ExceptionInfo(pages, cmd_history, all_objects) def exception_hook(self, exctype, excvalue, tb): """Handle uncaught python exceptions. @@ -222,10 +222,10 @@ class CrashHandler(QObject): is_ignored_exception = (exctype is bdb.BdbQuit or not issubclass(exctype, Exception)) - if 'pdb-postmortem' in self._args.debug_flags: + if 'pdb-postmortem' in objects.debug_flags: pdb.post_mortem(tb) - if is_ignored_exception or 'pdb-postmortem' in self._args.debug_flags: + if is_ignored_exception or 'pdb-postmortem' in objects.debug_flags: # pdb exit, KeyboardInterrupt, ... sys.exit(usertypes.Exit.exception) diff --git a/qutebrowser/misc/objects.py b/qutebrowser/misc/objects.py index b903d8e5d..e2885ca22 100644 --- a/qutebrowser/misc/objects.py +++ b/qutebrowser/misc/objects.py @@ -39,3 +39,4 @@ class NoBackend: backend = NoBackend() # type: typing.Union[usertypes.Backend, NoBackend] commands = {} # type: typing.Dict[str, command.Command] +debug_flags = set() # type: typing.Set[str] diff --git a/qutebrowser/misc/throttle.py b/qutebrowser/misc/throttle.py new file mode 100644 index 000000000..9bc8c620c --- /dev/null +++ b/qutebrowser/misc/throttle.py @@ -0,0 +1,102 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2018-2019 Jay Kamat <jaygkamat@gmail.com> +# +# 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/>. + +"""A throttle for throttling function calls.""" + +import typing +import time + +import attr +from PyQt5.QtCore import QObject + +from qutebrowser.utils import usertypes + + +@attr.s +class _CallArgs: + + args = attr.ib() # type: typing.Sequence[typing.Any] + kwargs = attr.ib() # type: typing.Mapping[str, typing.Any] + + +class Throttle(QObject): + + """A throttle to throttle calls. + + If a request comes in, it will be processed immediately. If another request + comes in too soon, it is ignored, but will be processed when a timeout + ends. If another request comes in, it will update the pending request. + """ + + def __init__(self, + func: typing.Callable, + delay_ms: int, + parent: QObject = None) -> None: + """Constructor. + + Args: + delay_ms: The time to wait before allowing another call of the + function. -1 disables the wrapper. + func: The function/method to call on __call__. + parent: The parent object. + """ + super().__init__(parent) + self._delay_ms = delay_ms + self._func = func + self._pending_call = None # type: typing.Optional[_CallArgs] + self._last_call_ms = None # type: typing.Optional[int] + self._timer = usertypes.Timer(self, 'throttle-timer') + self._timer.setSingleShot(True) + + def _call_pending(self): + """Start a pending call.""" + self._func(*self._pending_call.args, **self._pending_call.kwargs) + self._pending_call = None + self._last_call_ms = int(time.monotonic() * 1000) + + def __call__(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Any: + cur_time_ms = int(time.monotonic() * 1000) + if self._pending_call is None: + if (self._last_call_ms is None or + cur_time_ms - self._last_call_ms > self._delay_ms): + # Call right now + self._last_call_ms = cur_time_ms + self._func(*args, **kwargs) + return + + self._timer.setInterval(self._delay_ms - + (cur_time_ms - self._last_call_ms)) + # Disconnect any existing calls, continue if no connections. + try: + self._timer.timeout.disconnect() + except TypeError: + pass + self._timer.timeout.connect(self._call_pending) + self._timer.start() + + # Update arguments for an existing pending call + self._pending_call = _CallArgs(args=args, kwargs=kwargs) + + def set_delay(self, delay_ms: int) -> None: + """Set the delay to wait between invocation of this function.""" + self._delay_ms = delay_ms + + def cancel(self) -> None: + """Cancel any pending instance of this timer.""" + self._timer.stop() diff --git a/qutebrowser/misc/utilcmds.py b/qutebrowser/misc/utilcmds.py index a76826d80..93cf20be8 100644 --- a/qutebrowser/misc/utilcmds.py +++ b/qutebrowser/misc/utilcmds.py @@ -19,19 +19,21 @@ """Misc. utility commands exposed to the user.""" +# QApplication and objects are imported so they're usable in :debug-pyeval + import functools import os import traceback from PyQt5.QtCore import QUrl -# so it's available for :debug-pyeval from PyQt5.QtWidgets import QApplication # pylint: disable=unused-import from qutebrowser.browser import qutescheme from qutebrowser.utils import log, objreg, usertypes, message, debug, utils from qutebrowser.commands import runners from qutebrowser.api import cmdutils -from qutebrowser.misc import consolewidget, debugcachestats +from qutebrowser.misc import ( # pylint: disable=unused-import + consolewidget, debugcachestats, objects) from qutebrowser.utils.version import pastebin_version from qutebrowser.qt import sip diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py index b76b966e7..05fdd8878 100644 --- a/qutebrowser/qutebrowser.py +++ b/qutebrowser/qutebrowser.py @@ -167,12 +167,13 @@ def debug_flag_error(flag): no-sql-history: Don't store history items. no-scroll-filtering: Process all scrolling updates. log-requests: Log all network requests. + log-scroll-pos: Log all scrolling changes. stack: Enable Chromium stack logging. chromium: Enable Chromium logging. """ valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history', 'no-scroll-filtering', 'log-requests', 'lost-focusproxy', - 'stack', 'chromium'] + 'log-scroll-pos', 'stack', 'chromium'] if flag in valid_flags: return flag diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index 9048e282c..afdb686da 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -124,6 +124,8 @@ PERFECT_FILES = [ 'misc/pastebin.py'), ('tests/unit/misc/test_objects.py', 'misc/objects.py'), + ('tests/unit/misc/test_throttle.py', + 'misc/throttle.py'), (None, 'mainwindow/statusbar/keystring.py'), diff --git a/scripts/dev/run_vulture.py b/scripts/dev/run_vulture.py index c320d4325..ab38bdf09 100755 --- a/scripts/dev/run_vulture.py +++ b/scripts/dev/run_vulture.py @@ -68,6 +68,7 @@ def whitelist_generator(): # noqa yield 'qutebrowser.utils.qtutils.QtOSError.qt_errno' yield 'scripts.utils.bg_colors' yield 'qutebrowser.misc.sql.SqliteErrorCode.CONSTRAINT' + yield 'qutebrowser.misc.throttle.Throttle.set_delay' # Qt attributes yield 'PyQt5.QtWebKit.QWebPage.ErrorPageExtensionReturn().baseUrl' diff --git a/tests/end2end/features/test_history_bdd.py b/tests/end2end/features/test_history_bdd.py index 6b0265cd6..44f64991e 100644 --- a/tests/end2end/features/test_history_bdd.py +++ b/tests/end2end/features/test_history_bdd.py @@ -30,8 +30,8 @@ bdd.scenarios('history.feature') @pytest.fixture(autouse=True) def turn_on_sql_history(quteproc): """Make sure SQL writing is enabled for tests in this module.""" - quteproc.send_cmd(":debug-pyeval objreg.get('args')." - "debug_flags.remove('no-sql-history')") + cmd = ":debug-pyeval objects.debug_flags.remove('no-sql-history')" + quteproc.send_cmd(cmd) quteproc.wait_for_load_finished_url('qute://pyeval') diff --git a/tests/end2end/features/test_marks_bdd.py b/tests/end2end/features/test_marks_bdd.py index 7dd3c3d2b..7495bfd88 100644 --- a/tests/end2end/features/test_marks_bdd.py +++ b/tests/end2end/features/test_marks_bdd.py @@ -25,9 +25,7 @@ bdd.scenarios('marks.feature') @pytest.fixture(autouse=True) def turn_on_scroll_logging(quteproc): - """Make sure all scrolling changes are logged.""" - quteproc.send_cmd(":debug-pyeval -q objreg.get('args')." - "debug_flags.append('no-scroll-filtering')") + quteproc.turn_on_scroll_logging(no_scroll_filtering=True) @bdd.then(bdd.parsers.parse("the page should be scrolled to {x} {y}")) diff --git a/tests/end2end/features/test_scroll_bdd.py b/tests/end2end/features/test_scroll_bdd.py index 2358aa8a6..b45362928 100644 --- a/tests/end2end/features/test_scroll_bdd.py +++ b/tests/end2end/features/test_scroll_bdd.py @@ -25,6 +25,4 @@ bdd.scenarios('scroll.feature') @pytest.fixture(autouse=True) def turn_on_scroll_logging(quteproc): - """Make sure all scrolling changes are logged.""" - quteproc.send_cmd(":debug-pyeval -q objreg.get('args')." - "debug_flags.append('no-scroll-filtering')") + quteproc.turn_on_scroll_logging(no_scroll_filtering=True) diff --git a/tests/end2end/features/test_sessions_bdd.py b/tests/end2end/features/test_sessions_bdd.py index 0b5a32359..52e7c5c86 100644 --- a/tests/end2end/features/test_sessions_bdd.py +++ b/tests/end2end/features/test_sessions_bdd.py @@ -20,10 +20,16 @@ import os.path import logging +import pytest import pytest_bdd as bdd bdd.scenarios('sessions.feature') +@pytest.fixture(autouse=True) +def turn_on_scroll_logging(quteproc): + quteproc.turn_on_scroll_logging() + + @bdd.when(bdd.parsers.parse('I have a "{name}" session file:\n{contents}')) def create_session_file(quteproc, name, contents): filename = os.path.join(quteproc.basedir, 'data', 'sessions', diff --git a/tests/end2end/features/test_utilcmds_bdd.py b/tests/end2end/features/test_utilcmds_bdd.py index 16be24d1b..69b7d7213 100644 --- a/tests/end2end/features/test_utilcmds_bdd.py +++ b/tests/end2end/features/test_utilcmds_bdd.py @@ -17,6 +17,12 @@ # You should have received a copy of the GNU General Public License # along with qutebrowser. If not, see <http://www.gnu.org/licenses/>. +import pytest import pytest_bdd as bdd bdd.scenarios('utilcmds.feature') + + +@pytest.fixture(autouse=True) +def turn_on_scroll_logging(quteproc): + quteproc.turn_on_scroll_logging() diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index c6c6bbcb9..c0a00624a 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -872,6 +872,13 @@ class QuteProc(testprocess.Process): msg += '\nsee stdout for details' pytest.fail(msg) + def turn_on_scroll_logging(self, no_scroll_filtering=False): + """Make sure all scrolling changes are logged.""" + cmd = ":debug-pyeval -q objects.debug_flags.add('{}')" + if no_scroll_filtering: + self.send_cmd(cmd.format('no-scroll-filtering')) + self.send_cmd(cmd.format('log-scroll-pos')) + class YamlLoader(yaml.SafeLoader): diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py index 297fd4c2d..87984f1a5 100644 --- a/tests/unit/browser/test_history.py +++ b/tests/unit/browser/test_history.py @@ -27,13 +27,12 @@ from PyQt5.QtCore import QUrl from qutebrowser.browser import history from qutebrowser.utils import objreg, urlutils, usertypes from qutebrowser.api import cmdutils -from qutebrowser.misc import sql +from qutebrowser.misc import sql, objects @pytest.fixture(autouse=True) def prerequisites(config_stub, fake_save_manager, init_sql, fake_args): """Make sure everything is ready to initialize a WebHistory.""" - fake_args.debug_flags = [] config_stub.data = {'general': {'private-browsing': False}} @@ -171,8 +170,8 @@ class TestAdd: expected = [(completion_url, title, atime)] assert list(web_history.completion) == expected - def test_no_sql_web_history(self, web_history, fake_args): - fake_args.debug_flags = 'no-sql-history' + def test_no_sql_web_history(self, web_history, monkeypatch): + monkeypatch.setattr(objects, 'debug_flags', {'no-sql-history'}) web_history.add_url(QUrl('https://www.example.com/'), atime=12346, title='Hello World', redirect=False) assert not list(web_history) diff --git a/tests/unit/browser/test_qutescheme.py b/tests/unit/browser/test_qutescheme.py index 0be63fa62..932c394a4 100644 --- a/tests/unit/browser/test_qutescheme.py +++ b/tests/unit/browser/test_qutescheme.py @@ -101,7 +101,6 @@ class TestHistoryHandler: @pytest.fixture(autouse=True) def fake_history(self, web_history, fake_args, entries): """Create fake history.""" - fake_args.debug_flags = [] for item in entries: web_history.add_url(**item) diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py index 24e53648e..3d3ca0bf3 100644 --- a/tests/unit/completion/test_models.py +++ b/tests/unit/completion/test_models.py @@ -430,12 +430,6 @@ def test_bookmark_completion_delete(qtmodeltester, bookmarks, row, removed): assert before.difference(after) == {removed} -@pytest.fixture(autouse=True) -def url_args(fake_args): - """Prepare arguments needed to test the URL completion.""" - fake_args.debug_flags = [] - - def test_url_completion(qtmodeltester, config_stub, web_history_populated, quickmarks, bookmarks, info): """Test the results of url completion. diff --git a/tests/unit/mainwindow/statusbar/test_percentage.py b/tests/unit/mainwindow/statusbar/test_percentage.py index 1488ab827..45846bd16 100644 --- a/tests/unit/mainwindow/statusbar/test_percentage.py +++ b/tests/unit/mainwindow/statusbar/test_percentage.py @@ -29,6 +29,8 @@ from qutebrowser.mainwindow.statusbar.percentage import Percentage def percentage(qtbot): """Fixture providing a Percentage widget.""" widget = Percentage() + # Force immediate update of percentage widget + widget._set_text.set_delay(-1) qtbot.add_widget(widget) return widget @@ -55,7 +57,8 @@ def test_percentage_text(percentage, y, raw, expected): parametrized. expected: expected text given y position. parametrized. """ - percentage.raw = raw + if raw: + percentage.set_raw() percentage.set_perc(x=None, y=y) assert percentage.text() == expected diff --git a/tests/unit/misc/test_throttle.py b/tests/unit/misc/test_throttle.py new file mode 100644 index 000000000..6dbe3a1f8 --- /dev/null +++ b/tests/unit/misc/test_throttle.py @@ -0,0 +1,157 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2018-2019 Jay Kamat <jaygkamat@gmail.com>: +# +# 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.throttle.""" + +from unittest import mock + +import sip +import pytest +from PyQt5.QtCore import QObject + +from qutebrowser.misc import throttle + + +@pytest.fixture +def func(): + return mock.Mock(spec=[]) + + +@pytest.fixture +def throttled(func): + return throttle.Throttle(func, 100) + + +def test_immediate(throttled, func, qapp): + throttled("foo") + throttled("foo") + func.assert_called_once_with("foo") + + +def test_immediate_kwargs(throttled, func, qapp): + throttled(foo="bar") + throttled(foo="bar") + func.assert_called_once_with(foo="bar") + + +def test_delayed(throttled, func, qtbot): + throttled("foo") + throttled("foo") + throttled("foo") + throttled("bar") + func.assert_called_once_with("foo") + func.reset_mock() + + qtbot.wait(200) + + func.assert_called_once_with("bar") + + +def test_delayed_immediate_delayed(throttled, func, qtbot): + throttled("foo") + throttled("foo") + throttled("foo") + throttled("bar") + func.assert_called_once_with("foo") + func.reset_mock() + + qtbot.wait(400) + + func.assert_called_once_with("bar") + func.reset_mock() + throttled("baz") + throttled("baz") + throttled("bop") + + func.assert_called_once_with("baz") + func.reset_mock() + + qtbot.wait(200) + + func.assert_called_once_with("bop") + + +def test_delayed_delayed(throttled, func, qtbot): + throttled("foo") + throttled("foo") + throttled("foo") + throttled("bar") + func.assert_called_once_with("foo") + func.reset_mock() + + qtbot.wait(150) + + func.assert_called_once_with("bar") + func.reset_mock() + throttled("baz") + throttled("baz") + throttled("bop") + + qtbot.wait(200) + + func.assert_called_once_with("bop") + func.reset_mock() + + +def test_cancel(throttled, func, qtbot): + throttled("foo") + throttled("foo") + throttled("foo") + throttled("bar") + func.assert_called_once_with("foo") + func.reset_mock() + throttled.cancel() + + qtbot.wait(150) + + func.assert_not_called() + func.reset_mock() + + +def test_set(func, qtbot): + throttled = throttle.Throttle(func, 100) + throttled.set_delay(100) + throttled("foo") + throttled("foo") + throttled("foo") + throttled("bar") + func.assert_called_once_with("foo") + func.reset_mock() + + qtbot.wait(150) + + func.assert_called_once_with("bar") + func.reset_mock() + + +def test_deleted_object(qtbot): + class Obj(QObject): + + def func(self): + self.setObjectName("test") + + obj = Obj() + + throttled = throttle.Throttle(obj.func, 100, parent=obj) + throttled() + throttled() + + sip.delete(obj) + + qtbot.wait(150) |