diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index b42ec2279..59d28aa08 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -44,6 +44,8 @@ Changed - Improved detection of URLs/search terms when pasting multiple lines. - Don't remove `qutebrowser-editor-*` temporary file if editor subprocess crashed - Userscripts are also searched in `/usr/share/qutebrowser/userscripts`. +- Blocked hosts are now also read from a `blocked-hosts` file in the config dir + (e.g. `~/.config/qutebrowser/blocked-hosts`). Fixed ~~~~~ @@ -62,6 +64,7 @@ Fixed - Fixed very long filenames when downloading `data://`-URLs. - Fixed ugly UI fonts on Windows when Liberation Mono is installed - Fixed crash when unbinding key from a section which doesn't exist in the config +- Fixed report window after a segfault v0.5.1 ------ diff --git a/INSTALL.asciidoc b/INSTALL.asciidoc index 0d143624e..99abe55d1 100644 --- a/INSTALL.asciidoc +++ b/INSTALL.asciidoc @@ -102,6 +102,12 @@ $ rm -r qutebrowser-git or you could use an AUR helper, e.g. `yaourt -S qutebrowser-git`. +If video or sound don't seem to work, try installing the gstreamer plugins: + +---- +# pacman -S gst-plugins-{base,good,bad,ugly} gst-libav +---- + On Gentoo --------- @@ -135,6 +141,21 @@ it with: $ nix-env -i qutebrowser ---- +On openSUSE +----------- + +There are prebuilt RPMs available for Tumbleweed and Leap 42.1: + +http://software.opensuse.org/download.html?project=home%3Aarpraher&package=qutebrowser[One Click Install] + +Or add the repo manually: + +---- +# zypper addrepo http://download.opensuse.org/repositories/home:arpraher/openSUSE_Tumbleweed/home:arpraher.repo +# zypper refresh +# zypper install qutebrowser +---- + On Windows ---------- @@ -214,7 +235,16 @@ it as part of the packaging process. Installing qutebrowser with tox ------------------------------- -Run tox inside the qutebrowser repository to set up a +First of all, clone the repository using http://git-scm.org/[git] and switch +into the repository folder: + +---- +$ git clone https://github.com/The-Compiler/qutebrowser.git +$ cd qutebrowser +---- + + +Then run tox inside the qutebrowser repository to set up a https://docs.python.org/3/library/venv.html[virtual environment]: ---- diff --git a/README.asciidoc b/README.asciidoc index 181bd11aa..8fc09e84e 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -165,10 +165,12 @@ Contributors, sorted by the number of commits in descending order: * Thorsten Wißmann * Austin Anderson * Alexey "Averrin" Nabrodov +* avk * ZDarian * Milan Svoboda * John ShaggyTwoDope Jenkins * Peter Vilim +* Clayton Craft * Oliver Caldwell * Philipp Hansch * Jonas Schürmann @@ -191,7 +193,6 @@ Contributors, sorted by the number of commits in descending order: * Link * Larry Hynes * Johannes Altmanninger -* avk * Samir Benmendil * Regina Hug * Mathias Fussenegger @@ -199,9 +200,9 @@ Contributors, sorted by the number of commits in descending order: * Fritz V155 Reichwald * Franz Fellner * Corentin Jule -* Clayton Craft * zwarag * xd1le +* issue * haxwithaxe * evan * dylan araps diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc index 79e0549dd..d1875c645 100644 --- a/doc/help/commands.asciidoc +++ b/doc/help/commands.asciidoc @@ -72,6 +72,8 @@ === adblock-update Update the adblock block lists. +This updates ~/.local/share/qutebrowser/blocked-hosts with downloaded host lists and re-reads ~/.config/qutebrowser/blocked-hosts. + [[back]] === back Syntax: +:back [*--tab*] [*--bg*] [*--window*]+ diff --git a/misc/docker/archlinux/Dockerfile b/misc/docker/archlinux/Dockerfile index 37038b0a6..273bdb134 100644 --- a/misc/docker/archlinux/Dockerfile +++ b/misc/docker/archlinux/Dockerfile @@ -4,19 +4,15 @@ MAINTAINER Florian Bruhin RUN echo 'Server = http://mirror.de.leaseweb.net/archlinux/$repo/os/$arch' > /etc/pacman.d/mirrorlist RUN pacman-key --init && pacman-key --populate archlinux && pacman -Sy --noconfirm archlinux-keyring -RUN pacman-key -r 0xD6A1C70FE80A0C82 && \ - pacman-key --lsign-key 0xD6A1C70FE80A0C82 && \ - echo -e '[qt-debug]\nServer = http://qutebrowser.org/qt-debug/$arch' >> /etc/pacman.conf - RUN pacman -Suyy --noconfirm RUN pacman-db-upgrade RUN pacman -S --noconfirm \ git \ python-tox \ - qt5-base-debug \ - qt5-webkit-debug \ - python-pyqt5-debug \ + qt5-base \ + qt5-webkit \ + python-pyqt5 \ xorg-xinit \ herbstluftwm \ xorg-server-xvfb diff --git a/qutebrowser/browser/adblock.py b/qutebrowser/browser/adblock.py index 2de66691f..e6d1eab76 100644 --- a/qutebrowser/browser/adblock.py +++ b/qutebrowser/browser/adblock.py @@ -92,9 +92,11 @@ class HostBlocker: Attributes: _blocked_hosts: A set of blocked hosts. + _config_blocked_hosts: A set of blocked hosts from ~/.config. _in_progress: The DownloadItems which are currently downloading. _done_count: How many files have been read successfully. - _hosts_file: The path to the blocked-hosts file. + _local_hosts_file: The path to the blocked-hosts file. + _config_hosts_file: The path to a blocked-hosts in ~/.config Class attributes: WHITELISTED: Hosts which never should be blocked. @@ -105,13 +107,22 @@ class HostBlocker: def __init__(self): self._blocked_hosts = set() + self._config_blocked_hosts = set() self._in_progress = [] self._done_count = 0 + data_dir = standarddir.data() if data_dir is None: - self._hosts_file = None + self._local_hosts_file = None else: - self._hosts_file = os.path.join(data_dir, 'blocked-hosts') + self._local_hosts_file = os.path.join(data_dir, 'blocked-hosts') + + config_dir = standarddir.config() + if config_dir is None: + self._config_hosts_file = None + else: + self._config_hosts_file = os.path.join(config_dir, 'blocked-hosts') + objreg.get('config').changed.connect(self.on_config_changed) def is_blocked(self, url): @@ -119,21 +130,46 @@ class HostBlocker: if not config.get('content', 'host-blocking-enabled'): return False host = url.host() - return host in self._blocked_hosts and not is_whitelisted_host(host) + return ((host in self._blocked_hosts or + host in self._config_blocked_hosts) and + not is_whitelisted_host(host)) + + def _read_hosts_file(self, filename, target): + """Read hosts from the given filename. + + Args: + filename: The file to read. + target: The set to store the hosts in. + + Return: + True if a read was attempted, False otherwise + """ + if filename is None or not os.path.exists(filename): + return False + + try: + with open(filename, 'r', encoding='utf-8') as f: + for line in f: + target.add(line.strip()) + except OSError: + log.misc.exception("Failed to read host blocklist!") + + return True def read_hosts(self): """Read hosts from the existing blocked-hosts file.""" self._blocked_hosts = set() - if self._hosts_file is None: + + if self._local_hosts_file is None: return - if os.path.exists(self._hosts_file): - try: - with open(self._hosts_file, 'r', encoding='utf-8') as f: - for line in f: - self._blocked_hosts.add(line.strip()) - except OSError: - log.misc.exception("Failed to read host blocklist!") - else: + + self._read_hosts_file(self._config_hosts_file, + self._config_blocked_hosts) + + found = self._read_hosts_file(self._local_hosts_file, + self._blocked_hosts) + + if not found: args = objreg.get('args') if (config.get('content', 'host-block-lists') is not None and args.basedir is None): @@ -142,8 +178,14 @@ class HostBlocker: @cmdutils.register(instance='host-blocker', win_id='win_id') def adblock_update(self, win_id): - """Update the adblock block lists.""" - if self._hosts_file is None: + """Update the adblock block lists. + + This updates ~/.local/share/qutebrowser/blocked-hosts with downloaded + host lists and re-reads ~/.config/qutebrowser/blocked-hosts. + """ + self._read_hosts_file(self._config_hosts_file, + self._config_blocked_hosts) + if self._local_hosts_file is None: raise cmdexc.CommandError("No data storage is configured!") self._blocked_hosts = set() self._done_count = 0 @@ -221,7 +263,7 @@ class HostBlocker: def on_lists_downloaded(self): """Install block lists after files have been downloaded.""" - with open(self._hosts_file, 'w', encoding='utf-8') as f: + with open(self._local_hosts_file, 'w', encoding='utf-8') as f: for host in sorted(self._blocked_hosts): f.write(host + '\n') message.info('current', "adblock: Read {} hosts from {} sources." @@ -233,7 +275,7 @@ class HostBlocker: urls = config.get('content', 'host-block-lists') if urls is None: try: - os.remove(self._hosts_file) + os.remove(self._local_hosts_file) except OSError: log.misc.exception("Failed to delete hosts file.") diff --git a/qutebrowser/browser/network/pastebin.py b/qutebrowser/browser/network/pastebin.py index 84976e9a0..977fffe4a 100644 --- a/qutebrowser/browser/network/pastebin.py +++ b/qutebrowser/browser/network/pastebin.py @@ -23,8 +23,6 @@ import urllib.parse from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl -from qutebrowser.misc import httpclient - class PastebinClient(QObject): @@ -47,11 +45,17 @@ class PastebinClient(QObject): success = pyqtSignal(str) error = pyqtSignal(str) - def __init__(self, parent=None): + def __init__(self, client, parent=None): + """Constructor. + + Args: + client: The HTTPClient to use. Will be reparented. + """ super().__init__(parent) - self._client = httpclient.HTTPClient(self) - self._client.error.connect(self.error) - self._client.success.connect(self.on_client_success) + client.setParent(self) + client.error.connect(self.error) + client.success.connect(self.on_client_success) + self._client = client def paste(self, name, title, text, parent=None): """Paste the text into a pastebin and return the URL. diff --git a/qutebrowser/browser/webpage.py b/qutebrowser/browser/webpage.py index 30582579f..a8bce0e07 100644 --- a/qutebrowser/browser/webpage.py +++ b/qutebrowser/browser/webpage.py @@ -219,14 +219,12 @@ class BrowserPage(QWebPage): def _show_pdfjs(self, reply): """Show the reply with pdfjs.""" try: - page = pdfjs.generate_pdfjs_page(reply.url()).encode('utf-8') + page = pdfjs.generate_pdfjs_page(reply.url()) except pdfjs.PDFJSNotFound: - # pylint: disable=no-member - # WORKAROUND for https://bitbucket.org/logilab/pylint/issue/490/ - page = (jinja.env.get_template('no_pdfjs.html') - .render(url=reply.url().toDisplayString()) - .encode('utf-8')) - self.mainFrame().setContent(page, 'text/html', reply.url()) + page = jinja.render('no_pdfjs.html', + url=reply.url().toDisplayString()) + self.mainFrame().setContent(page.encode('utf-8'), 'text/html', + reply.url()) reply.deleteLater() def shutdown(self): diff --git a/qutebrowser/html/undef_error.html b/qutebrowser/html/undef_error.html new file mode 100644 index 000000000..55a47ca95 --- /dev/null +++ b/qutebrowser/html/undef_error.html @@ -0,0 +1,22 @@ + + + + + + + Error while rendering HTML + + +

Error while rendering internal qutebrowser page

+

There was an error while rendering {pagename}.

+ +

This most likely happened because you updated qutebrowser but didn't restart yet.

+ +

If you believe this isn't the case and this is a bug, please do :report.

+ +

Traceback

+
{traceback}
+ + diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index 161ce9308..3c4ab03d4 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -36,7 +36,7 @@ from PyQt5.QtWidgets import (QDialog, QLabel, QTextEdit, QPushButton, import qutebrowser from qutebrowser.utils import version, log, utils, objreg, qtutils -from qutebrowser.misc import miscwidgets, autoupdate, msgbox +from qutebrowser.misc import miscwidgets, autoupdate, msgbox, httpclient from qutebrowser.browser.network import pastebin from qutebrowser.config import config @@ -141,7 +141,8 @@ class _CrashDialog(QDialog): self.setWindowTitle("Whoops!") self.resize(QSize(640, 600)) self._vbox = QVBoxLayout(self) - self._paste_client = pastebin.PastebinClient(self) + http_client = httpclient.HTTPClient() + self._paste_client = pastebin.PastebinClient(http_client, self) self._pypi_client = autoupdate.PyPIVersionClient(self) self._init_text() @@ -508,11 +509,23 @@ class FatalCrashDialog(_CrashDialog): def _init_text(self): super()._init_text() text = ("qutebrowser was restarted after a fatal crash.
" - "
Note: Crash reports for fatal crashes sometimes don't " + "QTWEBENGINE_NOTE" + "
Crash reports for fatal crashes sometimes don't " "contain the information necessary to fix an issue. Please " "follow the steps in " "stacktrace.asciidoc to submit a stacktrace.
") + + if datetime.datetime.now() < datetime.datetime(2016, 4, 23): + note = ("
Fatal crashes like this are often caused by the " + "current QtWebKit backend.
I'm currently running a " + "crowdfunding for the new QtWebEngine backend, based on " + "Chromium: " + "igg.me/at/qutebrowser
") + text = text.replace('QTWEBENGINE_NOTE', note) + else: + text = text.replace('QTWEBENGINE_NOTE', '') + self._lbl.setText(text) def _init_checkboxes(self): diff --git a/qutebrowser/misc/crashsignal.py b/qutebrowser/misc/crashsignal.py index 22ed19d79..8d78b1e9a 100644 --- a/qutebrowser/misc/crashsignal.py +++ b/qutebrowser/misc/crashsignal.py @@ -72,7 +72,7 @@ class CrashHandler(QObject): def handle_segfault(self): """Handle a segfault from a previous run.""" - data_dir = None + data_dir = standarddir.data() if data_dir is None: return logname = os.path.join(data_dir, 'crash.log') diff --git a/qutebrowser/utils/jinja.py b/qutebrowser/utils/jinja.py index 5c9827b21..e6ccd9c6c 100644 --- a/qutebrowser/utils/jinja.py +++ b/qutebrowser/utils/jinja.py @@ -21,10 +21,12 @@ import os import os.path +import traceback import jinja2 +import jinja2.exceptions -from qutebrowser.utils import utils +from qutebrowser.utils import utils, log from PyQt5.QtCore import QUrl @@ -74,8 +76,14 @@ def resource_url(path): def render(template, **kwargs): """Render the given template and pass the given arguments to it.""" - return _env.get_template(template).render(**kwargs) - + try: + return _env.get_template(template).render(**kwargs) + except jinja2.exceptions.UndefinedError: + log.misc.exception("UndefinedError while rendering " + template) + err_path = os.path.join('html', 'undef_error.html') + err_template = utils.read_file(err_path) + tb = traceback.format_exc() + return err_template.format(pagename=template, traceback=tb) _env = jinja2.Environment(loader=Loader('html'), autoescape=_guess_autoescape) _env.globals['resource_url'] = resource_url diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py index 28164a850..40f7ebc4a 100644 --- a/scripts/dev/check_coverage.py +++ b/scripts/dev/check_coverage.py @@ -65,6 +65,8 @@ PERFECT_FILES = [ 'qutebrowser/browser/network/filescheme.py'), ('tests/unit/browser/network/test_networkreply.py', 'qutebrowser/browser/network/networkreply.py'), + ('tests/unit/browser/network/test_pastebin.py', + 'qutebrowser/browser/network/pastebin.py'), ('tests/unit/browser/test_signalfilter.py', 'qutebrowser/browser/signalfilter.py'), @@ -102,6 +104,8 @@ PERFECT_FILES = [ 'qutebrowser/mainwindow/statusbar/textbase.py'), ('tests/unit/mainwindow/statusbar/test_prompt.py', 'qutebrowser/mainwindow/statusbar/prompt.py'), + ('tests/unit/mainwindow/statusbar/test_url.py', + 'qutebrowser/mainwindow/statusbar/url.py'), ('tests/unit/config/test_configtypes.py', 'qutebrowser/config/configtypes.py'), diff --git a/scripts/importer.py b/scripts/importer.py index 44453ea92..5e0883cd2 100755 --- a/scripts/importer.py +++ b/scripts/importer.py @@ -31,23 +31,27 @@ import argparse def main(): args = get_args() if args.browser in ['chromium', 'firefox', 'ie']: - import_netscape_bookmarks(args.bookmarks) + import_netscape_bookmarks(args.bookmarks, args.bookmark_format) def get_args(): """Get the argparse parser.""" parser = argparse.ArgumentParser( epilog="To import bookmarks from Chromium, Firefox or IE, " - "export them to HTML in your browsers bookmark manager.") - parser.add_argument('browser', help="Which browser?", + "export them to HTML in your browsers bookmark manager. " + "By default, this script will output in a quickmarks format.") + parser.add_argument('browser', help="Which browser? (chromium, firefox)", choices=['chromium', 'firefox', 'ie'], metavar='browser') - parser.add_argument('bookmarks', help="Bookmarks file") + parser.add_argument('-b', help="Output in bookmark format.", + dest='bookmark_format', action='store_true', + default=False, required=False) + parser.add_argument('bookmarks', help="Bookmarks file (html format)") args = parser.parse_args() return args -def import_netscape_bookmarks(bookmarks_file): +def import_netscape_bookmarks(bookmarks_file, is_bookmark_format): """Import bookmarks from a NETSCAPE-Bookmark-file v1. Generated by Chromium, Firefox, IE and possibly more browsers @@ -57,11 +61,15 @@ def import_netscape_bookmarks(bookmarks_file): soup = bs4.BeautifulSoup(f, 'html.parser') html_tags = soup.findAll('a') + if is_bookmark_format: + output_template = '{tag[href]} {tag.string}' + else: + output_template = '{tag.string} {tag[href]}' bookmarks = [] for tag in html_tags: if tag['href'] not in bookmarks: - bookmarks.append('{tag.string} {tag[href]}'.format(tag=tag)) + bookmarks.append(output_template.format(tag=tag)) for bookmark in bookmarks: print(bookmark) diff --git a/tests/unit/browser/network/test_pastebin.py b/tests/unit/browser/network/test_pastebin.py new file mode 100644 index 000000000..56f988e94 --- /dev/null +++ b/tests/unit/browser/network/test_pastebin.py @@ -0,0 +1,125 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Anna Kobak (avk) : +# +# 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 . + +import pytest +from PyQt5.QtCore import pyqtSignal, QUrl, QObject + +from qutebrowser.browser.network import pastebin +from qutebrowser.misc import httpclient + + +class HTTPPostStub(QObject): + + """A stub class for HTTPClient. + + Attributes: + url: the last url send by post() + data: the last data send by post() + """ + + success = pyqtSignal(str) + error = pyqtSignal(str) + + def __init__(self, parent=None): + super().__init__(parent) + self.url = None + self.data = None + + def post(self, url, data=None): + self.url = url + self.data = data + + +@pytest.fixture +def pbclient(): + http_stub = HTTPPostStub() + client = pastebin.PastebinClient(http_stub) + return client + + +def test_constructor(qapp): + http_client = httpclient.HTTPClient() + pbclient = pastebin.PastebinClient(http_client) + + +@pytest.mark.parametrize('data', [ + { + "name": "XYZ", + "title": "hello world", + "text": "xyz. 123 \n 172ANB", + "reply": "abc" + }, + { + "name": "the name", + "title": "the title", + "text": "some Text", + "reply": "some parent" + } +]) +def test_paste_with_parent(data, pbclient): + http_stub = pbclient._client + pbclient.paste(data["name"], data["title"], data["text"], data["reply"]) + assert http_stub.data == data + assert http_stub.url == QUrl('http://paste.the-compiler.org/api/create') + + +@pytest.mark.parametrize('data', [ + { + "name": "XYZ", + "title": "hello world", + "text": "xyz. 123 \n 172ANB" + }, + { + "name": "the name", + "title": "the title", + "text": "some Text" + } +]) +def test_paste_without_parent(data, pbclient): + http_stub = pbclient._client + pbclient.paste(data["name"], data["title"], data["text"]) + assert pbclient._client.data == data + assert http_stub.url == QUrl('http://paste.the-compiler.org/api/create') + + +@pytest.mark.parametrize('http', [ + "http://paste.the-compiler.org/view/ges83nt3", + "http://paste.the-compiler.org/view/3gjnwg4" +]) +def test_on_client_success(http, pbclient, qtbot): + with qtbot.assertNotEmitted(pbclient.error): + with qtbot.waitSignal(pbclient.success): + pbclient._client.success.emit(http) + + +@pytest.mark.parametrize('http', [ + "http invalid", + "http:/invalid.org" + "http//invalid.com" +]) +def test_client_success_invalid_http(http, pbclient, qtbot): + with qtbot.assertNotEmitted(pbclient.success): + with qtbot.waitSignal(pbclient.error): + pbclient._client.success.emit(http) + + +def test_client_error(pbclient, qtbot): + with qtbot.assertNotEmitted(pbclient.success): + with qtbot.waitSignal(pbclient.error): + pbclient._client.error.emit("msg") diff --git a/tests/unit/mainwindow/statusbar/test_url.py b/tests/unit/mainwindow/statusbar/test_url.py new file mode 100644 index 000000000..1d5878a01 --- /dev/null +++ b/tests/unit/mainwindow/statusbar/test_url.py @@ -0,0 +1,146 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Clayton Craft (craftyguy) +# +# 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 . + + +"""Test Statusbar url.""" + +import pytest +import collections + +from qutebrowser.browser import webview +from qutebrowser.mainwindow.statusbar import url + + +@pytest.fixture +def tab_widget(): + """Fixture providing a fake tab widget.""" + tab = collections.namedtuple('Tab', 'cur_url load_status') + tab.cur_url = collections.namedtuple('cur_url', 'toDisplayString') + return tab + + +@pytest.fixture +def url_widget(qtbot, monkeypatch, config_stub): + """Fixture providing a Url widget.""" + config_stub.data = { + 'colors': { + 'statusbar.url.bg': 'white', + 'statusbar.url.fg': 'black', + 'statusbar.url.fg.success': 'yellow', + 'statusbar.url.fg.success.https': 'green', + 'statusbar.url.fg.error': 'red', + 'statusbar.url.fg.warn': 'orange', + 'statusbar.url.fg.hover': 'blue' + }, + 'fonts': {}, + } + monkeypatch.setattr( + 'qutebrowser.mainwindow.statusbar.url.style.config', config_stub) + widget = url.UrlText() + qtbot.add_widget(widget) + assert not widget.isVisible() + return widget + + +@pytest.mark.parametrize('url_text', [ + 'http://abc123.com/this/awesome/url.html', + 'https://supersecret.gov/nsa/files.txt', + 'Th1$ i$ n0t @ n0rm@L uRL! P@n1c! <-->', + None +]) +def test_set_url(url_widget, url_text): + """Test text displayed by the widget.""" + url_widget.set_url(url_text) + if url_text is not None: + assert url_widget.text() == url_text + else: + assert url_widget.text() == "" + + +@pytest.mark.parametrize('url_text, title, text', [ + ('http://abc123.com/this/awesome/url.html', 'Awesome site', 'click me!'), + ('https://supersecret.gov/nsa/files.txt', 'Secret area', None), + ('Th1$ i$ n0t @ n0rm@L uRL! P@n1c! <-->', 'Probably spam', 'definitely'), + (None, None, 'did I break?!') +]) +def test_set_hover_url(url_widget, url_text, title, text): + """Test text when hovering over a link.""" + url_widget.set_hover_url(url_text, title, text) + if url_text is not None: + assert url_widget.text() == url_text + assert url_widget._urltype == url.UrlType.hover + else: + assert url_widget.text() == '' + assert url_widget._urltype == url.UrlType.normal + + +@pytest.mark.parametrize('status, expected', [ + (webview.LoadStatus.success, url.UrlType.success), + (webview.LoadStatus.success_https, url.UrlType.success_https), + (webview.LoadStatus.error, url.UrlType.error), + (webview.LoadStatus.warn, url.UrlType.warn), + (webview.LoadStatus.loading, url.UrlType.normal), + (webview.LoadStatus.none, url.UrlType.normal) +]) +def test_on_load_status_changed(url_widget, status, expected): + """Test text when status is changed.""" + url_widget.set_url('www.example.com') + url_widget.on_load_status_changed(status.name) + assert url_widget._urltype == expected + + +@pytest.mark.parametrize('load_status, url_text', [ + (url.UrlType.success, 'http://abc123.com/this/awesome/url.html'), + (url.UrlType.success, 'http://reddit.com/r/linux'), + (url.UrlType.success_https, 'www.google.com'), + (url.UrlType.success_https, 'https://supersecret.gov/nsa/files.txt'), + (url.UrlType.warn, 'www.shadysite.org/some/path/to/file/with/issues.htm'), + (url.UrlType.error, 'Th1$ i$ n0t @ n0rm@L uRL! P@n1c! <-->'), + (url.UrlType.error, None) +]) +def test_on_tab_changed(url_widget, tab_widget, load_status, url_text): + tab_widget.load_status = load_status + tab_widget.cur_url.toDisplayString = lambda: url_text + url_widget.on_tab_changed(tab_widget) + if url_text is not None: + assert url_widget._urltype == load_status + assert url_widget.text() == url_text + else: + assert url_widget._urltype == url.UrlType.normal + assert url_widget.text() == '' + + +@pytest.mark.parametrize('url_text, load_status, expected_status', [ + ('http://abc123.com/this/awesome/url.html', webview.LoadStatus.success, + url.UrlType.success), + ('https://supersecret.gov/nsa/files.txt', webview.LoadStatus.success_https, + url.UrlType.success_https), + ('Th1$ i$ n0t @ n0rm@L uRL! P@n1c! <-->', webview.LoadStatus.error, + url.UrlType.error), + ('http://www.qutebrowser.org/CONTRIBUTING.html', webview.LoadStatus.loading, + url.UrlType.normal), + ('www.whatisthisurl.com', webview.LoadStatus.warn, url.UrlType.warn) +]) +def test_normal_url(url_widget, url_text, load_status, expected_status): + url_widget.set_url(url_text) + url_widget.on_load_status_changed(load_status.name) + url_widget.set_hover_url(url_text, "", "") + url_widget.set_hover_url("", "", "") + assert url_widget.text() == url_text + assert url_widget._urltype == expected_status diff --git a/tests/unit/utils/test_jinja.py b/tests/unit/utils/test_jinja.py index 99aecf551..cea237d22 100644 --- a/tests/unit/utils/test_jinja.py +++ b/tests/unit/utils/test_jinja.py @@ -23,21 +23,28 @@ import os import os.path import pytest +import logging import jinja2 from PyQt5.QtCore import QUrl -from qutebrowser.utils import jinja +from qutebrowser.utils import utils, jinja @pytest.fixture(autouse=True) def patch_read_file(monkeypatch): """pytest fixture to patch utils.read_file.""" + real_read_file = utils.read_file + def _read_file(path): """A read_file which returns a simple template if the path is right.""" if path == os.path.join('html', 'test.html'): return """Hello {{var}}""" elif path == os.path.join('html', 'test2.html'): return """{{ resource_url('utils/testfile') }}""" + elif path == os.path.join('html', 'undef.html'): + return """{{ does_not_exist() }}""" + elif path == os.path.join('html', 'undef_error.html'): + return real_read_file(path) else: raise IOError("Invalid path {}!".format(path)) @@ -87,6 +94,18 @@ def test_utf8(): assert data == "Hello \u2603" +def test_undefined_function(caplog): + """Make sure we don't crash if an undefined function is called.""" + with caplog.at_level(logging.ERROR): + data = jinja.render('undef.html') + assert 'There was an error while rendering undef.html' in data + assert "'does_not_exist' is undefined" in data + assert data.startswith('') + + assert len(caplog.records) == 1 + assert caplog.records[0].msg == "UndefinedError while rendering undef.html" + + @pytest.mark.parametrize('name, expected', [ (None, False), ('foo', False), diff --git a/tox.ini b/tox.ini index 49d59e616..1cf3deb42 100644 --- a/tox.ini +++ b/tox.ini @@ -37,17 +37,17 @@ deps = pytest-instafail==0.3.0 pytest-travis-fold==1.2.0 pytest-repeat==0.2 - pytest-rerunfailures==1.0.1 + pytest-rerunfailures==1.0.2 pytest-xvfb==0.2.0 six==1.10.0 termcolor==1.1.0 vulture==0.8.1 - Werkzeug==0.11.4 + Werkzeug==0.11.5 wheel==0.29.0 cherrypy==5.1.0 commands = {envpython} scripts/link_pyqt.py --tox {envdir} - {envpython} -m py.test {posargs:tests} + {envpython} -m pytest {posargs:tests} [testenv:py35-cov] basepython = python3.5 @@ -56,7 +56,7 @@ passenv = {[testenv]passenv} deps = {[testenv]deps} commands = {envpython} scripts/link_pyqt.py --tox {envdir} - {envpython} -m py.test --cov --cov-report xml --cov-report=html --cov-report= {posargs:tests} + {envpython} -m pytest --cov --cov-report xml --cov-report=html --cov-report= {posargs:tests} {envpython} scripts/dev/check_coverage.py {posargs} [testenv:py34-cov] @@ -66,7 +66,7 @@ passenv = {[testenv]passenv} deps = {[testenv]deps} commands = {envpython} scripts/link_pyqt.py --tox {envdir} - {envpython} -m py.test --cov --cov-report xml --cov-report=html --cov-report= {posargs:tests} + {envpython} -m pytest --cov --cov-report xml --cov-report=html --cov-report= {posargs:tests} {envpython} scripts/dev/check_coverage.py {posargs} [testenv:mkvenv] @@ -99,7 +99,7 @@ setenv = QUTE_NO_DISPLAY=1 commands = {envpython} scripts/link_pyqt.py --tox {envdir} - {envpython} -m py.test {posargs:tests} + {envpython} -m pytest {posargs:tests} [testenv:misc] ignore_errors = true