diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc
index 901509fb9..ddfbc0116 100644
--- a/CHANGELOG.asciidoc
+++ b/CHANGELOG.asciidoc
@@ -48,6 +48,8 @@ Added
* `colors -> downloads.bg.system`
- New command `:download-retry` to retry a failed download.
- New command `:download-clear` which replaces `:download-remove --all`.
+- `:set-cmd-text` has a new `--append` argument to append to the current
+ statusbar text.
Changed
~~~~~~~
@@ -82,6 +84,8 @@ Fixed
- Fixed a crash when a website presents a very small favicon.
- Fixed prompting for download directory when
`storage -> prompt-download-directory` was unset.
+- Fixed crash when using `:follow-hint` outside of hint mode.
+- Fixed crash when using `:set foo bar?` with invalid section/option.
v0.4.1
------
diff --git a/README.asciidoc b/README.asciidoc
index 04ca13a35..fb224ac04 100644
--- a/README.asciidoc
+++ b/README.asciidoc
@@ -140,9 +140,9 @@ Contributors, sorted by the number of commits in descending order:
* Florian Bruhin
* Antoni Boucher
* Bruno Oliveira
+* Lamar Pavel
* Alexander Cogneau
* Martin Tournoij
-* Lamar Pavel
* Raphael Pierzina
* Joel Torstensson
* Daniel
diff --git a/doc/help/commands.asciidoc b/doc/help/commands.asciidoc
index 22219a994..bdd881bb1 100644
--- a/doc/help/commands.asciidoc
+++ b/doc/help/commands.asciidoc
@@ -577,7 +577,7 @@ If the option name ends with '?', the value of the option is shown instead. If t
[[set-cmd-text]]
=== set-cmd-text
-Syntax: +:set-cmd-text [*--space*] 'text'+
+Syntax: +:set-cmd-text [*--space*] [*--append*] 'text'+
Preset the statusbar to some text.
@@ -586,6 +586,7 @@ Preset the statusbar to some text.
==== optional arguments
* +*-s*+, +*--space*+: If given, a space is added to the end.
+* +*-a*+, +*--append*+: If given, the text is appended to the current text.
==== note
* This command does not split arguments after the last argument and handles quotes literally.
diff --git a/pytest.ini b/pytest.ini
index 050246538..36bc16868 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -35,3 +35,4 @@ qt_log_ignore =
^Type conversion already registered from type .*
^QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once\.
^QWaitCondition: Destroyed while threads are still waiting
+ ^QXcbXSettings::QXcbXSettings\(QXcbScreen\*\) Failed to get selection owner for XSETTINGS_S atom
diff --git a/qutebrowser/browser/cache.py b/qutebrowser/browser/cache.py
index 3d023d635..c51146a3d 100644
--- a/qutebrowser/browser/cache.py
+++ b/qutebrowser/browser/cache.py
@@ -65,7 +65,8 @@ class DiskCache(QNetworkDiskCache):
"""Update cache size/activated if the config was changed."""
if (section, option) == ('storage', 'cache-size'):
self.setMaximumCacheSize(config.get('storage', 'cache-size'))
- elif (section, option) == ('general', 'private-browsing'):
+ elif (section, option) == ('general', # pragma: no branch
+ 'private-browsing'):
self._maybe_activate()
def cacheSize(self):
diff --git a/qutebrowser/browser/downloads.py b/qutebrowser/browser/downloads.py
index 078fa622f..4bea68fa8 100644
--- a/qutebrowser/browser/downloads.py
+++ b/qutebrowser/browser/downloads.py
@@ -810,10 +810,15 @@ class DownloadManager(QAbstractListModel):
download = DownloadItem(reply, self._win_id, self)
download.cancelled.connect(
functools.partial(self.remove_item, download))
+
delay = config.get('ui', 'remove-finished-downloads')
- if delay > -1 or auto_remove:
+ if delay > -1:
download.finished.connect(
functools.partial(self.remove_item_delayed, download, delay))
+ elif auto_remove:
+ download.finished.connect(
+ functools.partial(self.remove_item, download))
+
download.data_changed.connect(
functools.partial(self.on_data_changed, download))
download.error.connect(self.on_error)
diff --git a/qutebrowser/browser/hints.py b/qutebrowser/browser/hints.py
index 05e21c51c..2caa4312a 100644
--- a/qutebrowser/browser/hints.py
+++ b/qutebrowser/browser/hints.py
@@ -946,7 +946,8 @@ class HintManager(QObject):
elems.label.setInnerXml(string)
handler()
- @cmdutils.register(instance='hintmanager', scope='tab', hide=True)
+ @cmdutils.register(instance='hintmanager', scope='tab', hide=True,
+ modes=[usertypes.KeyMode.hint])
def follow_hint(self, keystring=None):
"""Follow a hint.
@@ -958,6 +959,8 @@ class HintManager(QObject):
raise cmdexc.CommandError("No hint to follow")
else:
keystring = self._context.to_follow
+ elif keystring not in self._context.elems:
+ raise cmdexc.CommandError("No hint {}!".format(keystring))
self.fire(keystring, force=True)
@pyqtSlot('QSize')
diff --git a/qutebrowser/browser/network/networkmanager.py b/qutebrowser/browser/network/networkmanager.py
index 436db914e..66ec7595e 100644
--- a/qutebrowser/browser/network/networkmanager.py
+++ b/qutebrowser/browser/network/networkmanager.py
@@ -360,17 +360,21 @@ class NetworkManager(QNetworkAccessManager):
req.setRawHeader('DNT'.encode('ascii'), dnt)
req.setRawHeader('X-Do-Not-Track'.encode('ascii'), dnt)
- if self._tab_id is None:
- current_url = QUrl() # generic NetworkManager, e.g. for downloads
- else:
+ # There are some scenarios where we can't figure out current_url:
+ # - There's a generic NetworkManager, e.g. for downloads
+ # - The download was in a tab which is now closed.
+ current_url = QUrl()
+
+ if self._tab_id is not None:
try:
webview = objreg.get('webview', scope='tab',
window=self._win_id, tab=self._tab_id)
- except KeyError:
- # https://github.com/The-Compiler/qutebrowser/issues/889
- current_url = QUrl()
- else:
current_url = webview.url()
+ except (KeyError, RuntimeError, TypeError):
+ # https://github.com/The-Compiler/qutebrowser/issues/889
+ # Catching RuntimeError and TypeError because we could be in
+ # the middle of the webpage shutdown here.
+ current_url = QUrl()
self.set_referer(req, current_url)
diff --git a/qutebrowser/browser/webview.py b/qutebrowser/browser/webview.py
index 36a61a7ee..54620a0fd 100644
--- a/qutebrowser/browser/webview.py
+++ b/qutebrowser/browser/webview.py
@@ -292,7 +292,7 @@ class WebView(QWebView):
try:
elem = webelem.focus_elem(self.page().currentFrame())
except (webelem.IsNullError, RuntimeError):
- log.mouse.warning("Element/page vanished!")
+ log.mouse.debug("Element/page vanished!")
return
if elem.is_editable():
log.mouse.debug("Clicked editable element (delayed)!")
diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py
index 55502c5da..09cde457e 100644
--- a/qutebrowser/config/config.py
+++ b/qutebrowser/config/config.py
@@ -29,6 +29,7 @@ import sys
import os.path
import functools
import configparser
+import contextlib
import collections
import collections.abc
@@ -666,6 +667,18 @@ class ConfigManager(QObject):
newval = val.typ.transform(newval)
return newval
+ @contextlib.contextmanager
+ def _handle_config_error(self):
+ """Catch errors in set_command and raise CommandError."""
+ try:
+ yield
+ except (configexc.NoOptionError, configexc.NoSectionError,
+ configexc.ValidationError) as e:
+ raise cmdexc.CommandError("set: {}".format(e))
+ except (configexc.Error, configparser.Error) as e:
+ raise cmdexc.CommandError("set: {} - {}".format(
+ e.__class__.__name__, e))
+
@cmdutils.register(name='set', instance='config', win_id='win_id',
completion=[Completion.section, Completion.option,
Completion.value])
@@ -699,12 +712,12 @@ class ConfigManager(QObject):
tabbed_browser.openurl(QUrl('qute:settings'), newtab=False)
return
- if option.endswith('?'):
+ if option.endswith('?') and option != '?':
option = option[:-1]
print_ = True
else:
- try:
- if option.endswith('!') and value is None:
+ with self._handle_config_error():
+ if option.endswith('!') and option != '!' and value is None:
option = option[:-1]
val = self.get(section_, option)
layer = 'temp' if temp else 'conf'
@@ -719,12 +732,10 @@ class ConfigManager(QObject):
else:
raise cmdexc.CommandError("set: The following arguments "
"are required: value")
- except (configexc.Error, configparser.Error) as e:
- raise cmdexc.CommandError("set: {} - {}".format(
- e.__class__.__name__, e))
if print_:
- val = self.get(section_, option, transformed=False)
+ with self._handle_config_error():
+ val = self.get(section_, option, transformed=False)
message.info(win_id, "{} {} = {}".format(
section_, option, val), immediately=True)
diff --git a/qutebrowser/mainwindow/statusbar/command.py b/qutebrowser/mainwindow/statusbar/command.py
index 1d8105c2f..0a06aed79 100644
--- a/qutebrowser/mainwindow/statusbar/command.py
+++ b/qutebrowser/mainwindow/statusbar/command.py
@@ -92,7 +92,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
@cmdutils.register(instance='status-command', name='set-cmd-text',
scope='window', maxsplit=0)
- def set_cmd_text_command(self, text, space=False):
+ def set_cmd_text_command(self, text, space=False, append=False):
"""Preset the statusbar to some text.
//
@@ -103,6 +103,7 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
Args:
text: The commandline to set.
space: If given, a space is added to the end.
+ append: If given, the text is appended to the current text.
"""
tabbed_browser = objreg.get('tabbed-browser', scope='window',
window=self._win_id)
@@ -122,8 +123,14 @@ class Command(misc.MinimalLineEditMixin, misc.CommandLineEdit):
# I'm not sure what's the best thing to do here
# https://github.com/The-Compiler/qutebrowser/issues/123
text = text.replace('{url}', url)
+
if space:
text += ' '
+ if append:
+ if not self.text():
+ raise cmdexc.CommandError("No current text!")
+ text = self.text() + text
+
if not text or text[0] not in modeparsers.STARTCHARS:
raise cmdexc.CommandError(
"Invalid command text '{}'.".format(text))
diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py
index 4211fe184..9cedf6250 100644
--- a/qutebrowser/misc/editor.py
+++ b/qutebrowser/misc/editor.py
@@ -82,7 +82,7 @@ class ExternalEditor(QObject):
encoding = config.get('general', 'editor-encoding')
try:
with open(self._filename, 'r', encoding=encoding) as f:
- text = ''.join(f.readlines()) # pragma: no branch
+ text = f.read() # pragma: no branch
except OSError as e:
# NOTE: Do not replace this with "raise CommandError" as it's
# executed async.
diff --git a/qutebrowser/misc/lineparser.py b/qutebrowser/misc/lineparser.py
index 4a5e1f3ff..8e6cb4aba 100644
--- a/qutebrowser/misc/lineparser.py
+++ b/qutebrowser/misc/lineparser.py
@@ -213,9 +213,9 @@ class LineParser(BaseLineParser):
"""Read the data from self._configfile."""
with self._open('r') as f:
if self._binary:
- self.data = [line.rstrip(b'\n') for line in f.readlines()]
+ self.data = [line.rstrip(b'\n') for line in f]
else:
- self.data = [line.rstrip('\n') for line in f.readlines()]
+ self.data = [line.rstrip('\n') for line in f]
def save(self):
"""Save the config file."""
diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py
index 2a87cab4c..2d615e4b3 100644
--- a/qutebrowser/utils/log.py
+++ b/qutebrowser/utils/log.py
@@ -320,10 +320,14 @@ def qt_message_handler(msg_type, context, msg):
level = logging.DEBUG
else:
level = qt_to_logging[msg_type]
+
if context.function is None:
func = 'none'
+ elif ':' in context.function:
+ func = '"{}"'.format(context.function)
else:
func = context.function
+
if context.category is None or context.category == 'default':
name = 'qt'
else:
diff --git a/qutebrowser/utils/version.py b/qutebrowser/utils/version.py
index 91366bd28..f5cd767f4 100644
--- a/qutebrowser/utils/version.py
+++ b/qutebrowser/utils/version.py
@@ -112,7 +112,7 @@ def _release_info():
for fn in glob.glob("/etc/*-release"):
try:
with open(fn, 'r', encoding='utf-8') as f:
- data.append((fn, ''.join(f.readlines()))) # pragma: no branch
+ data.append((fn, f.read())) # pragma: no branch
except OSError:
log.misc.exception("Error while reading {}.".format(fn))
return data
diff --git a/scripts/dev/check_coverage.py b/scripts/dev/check_coverage.py
index 65228a10e..3217e4dfd 100644
--- a/scripts/dev/check_coverage.py
+++ b/scripts/dev/check_coverage.py
@@ -48,6 +48,8 @@ PERFECT_FILES = [
('tests/unit/commands/test_argparser.py',
'qutebrowser/commands/argparser.py'),
+ ('tests/unit/browser/test_cache.py',
+ 'qutebrowser/browser/cache.py'),
('tests/unit/browser/test_cookies.py',
'qutebrowser/browser/cookies.py'),
('tests/unit/browser/test_tabhistory.py',
@@ -248,9 +250,9 @@ def main():
"""
utils.change_cwd()
if '--check-all' in sys.argv:
- main_check_all()
+ return main_check_all()
else:
- main_check()
+ return main_check()
if __name__ == '__main__':
diff --git a/tests/helpers/test_helper_utils.py b/tests/helpers/test_helper_utils.py
index 7893d25ab..9512a964f 100644
--- a/tests/helpers/test_helper_utils.py
+++ b/tests/helpers/test_helper_utils.py
@@ -29,6 +29,7 @@ from helpers import utils # pylint: disable=import-error
({'a': [1, 2, 3]}, {'a': [1]}),
({'a': [1, 2, 3]}, {'a': [..., 2]}),
(1.0, 1.00000001),
+ ("foobarbaz", "foo*baz"),
])
def test_partial_compare_equal(val1, val2):
assert utils.partial_compare(val1, val2)
@@ -43,6 +44,7 @@ def test_partial_compare_equal(val1, val2):
([1], {1: 2}),
({1: 1}, {1: [1]}),
({'a': [1, 2, 3]}, {'a': [..., 3]}),
+ ("foo*baz", "foobarbaz"),
])
def test_partial_compare_not_equal(val1, val2):
assert not utils.partial_compare(val1, val2)
diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py
index 7af219d77..775d79bf4 100644
--- a/tests/helpers/utils.py
+++ b/tests/helpers/utils.py
@@ -20,6 +20,9 @@
"""Partial comparison of dicts/lists."""
+import fnmatch
+
+
def _partial_compare_dict(val1, val2):
for key in val2:
if key not in val1:
@@ -71,6 +74,9 @@ def partial_compare(val1, val2):
elif isinstance(val2, float):
print("Doing float comparison")
equal = abs(val1 - val2) < 0.00001
+ elif isinstance(val2, str):
+ print("Doing string comparison")
+ equal = fnmatch.fnmatchcase(val1, val2)
else:
print("Comparing via ==")
equal = val1 == val2
diff --git a/tests/integration/data/downloads/mhtml/simple/requests b/tests/integration/data/downloads/mhtml/simple/requests
new file mode 100644
index 000000000..aff865ec9
--- /dev/null
+++ b/tests/integration/data/downloads/mhtml/simple/requests
@@ -0,0 +1 @@
+simple.html
diff --git a/tests/integration/data/downloads/mhtml/simple/simple.html b/tests/integration/data/downloads/mhtml/simple/simple.html
new file mode 100644
index 000000000..7584c3f91
--- /dev/null
+++ b/tests/integration/data/downloads/mhtml/simple/simple.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Simple MHTML test
+
+
+ normal link to another page
+
+
diff --git a/tests/integration/data/downloads/mhtml/simple/simple.mht b/tests/integration/data/downloads/mhtml/simple/simple.mht
new file mode 100644
index 000000000..d0b7a7c48
--- /dev/null
+++ b/tests/integration/data/downloads/mhtml/simple/simple.mht
@@ -0,0 +1,20 @@
+Content-Type: multipart/related; boundary="---=_qute-6d584056-b1e4-4882-91e6-d4a6d23adb67"
+MIME-Version: 1.0
+
+-----=_qute-6d584056-b1e4-4882-91e6-d4a6d23adb67
+Content-Location: http://localhost:1234/data/downloads/mhtml/simple/simple.html
+MIME-Version: 1.0
+Content-Type: text/html; charset="UTF-8"
+Content-Transfer-Encoding: quoted-printable
+
+
+=20=20=20=20=20=20=20=20
+=20=20=20=20=20=20=20=20Simple=20MHTML=20test
+=20=20=20=20
+=20=20=20=20
+=20=20=20=20=20=20=20=20normal=20link=20to=20another=20page=
+
+=20=20=20=20
+
+
+-----=_qute-6d584056-b1e4-4882-91e6-d4a6d23adb67--
diff --git a/tests/integration/data/hints/link.html b/tests/integration/data/hints/link.html
new file mode 100644
index 000000000..ec4f9f38c
--- /dev/null
+++ b/tests/integration/data/hints/link.html
@@ -0,0 +1,10 @@
+
+
+
+
+ A link to use hints on
+
+
+ Follow me!
+
+
diff --git a/tests/integration/features/backforward.feature b/tests/integration/features/backforward.feature
index 7c25e7337..dba29fec2 100644
--- a/tests/integration/features/backforward.feature
+++ b/tests/integration/features/backforward.feature
@@ -4,6 +4,7 @@ Feature: Going back and forward.
Scenario: Going back/forward
Given I open data/backforward/1.txt
When I open data/backforward/2.txt
+ And I run :tab-only
And I run :back
And I wait until data/backforward/1.txt is loaded
And I reload
@@ -15,13 +16,81 @@ Feature: Going back and forward.
data/backforward/2.txt
data/backforward/1.txt
data/backforward/2.txt
+ And the session should look like:
+ windows:
+ - tabs:
+ - history:
+ - url: http://localhost:*/data/backforward/1.txt
+ - active: true
+ url: http://localhost:*/data/backforward/2.txt
- Scenario: Going back without history
- Given I open data/backforward/1.txt
- When I run :back
- Then the error "At beginning of history." should be shown.
+ Scenario: Going back in a new tab
+ Given I open data/backforward/1.txt
+ When I open data/backforward/2.txt
+ And I run :tab-only
+ And I run :back -t
+ And I wait until data/backforward/1.txt is loaded
+ Then the session should look like:
+ windows:
+ - tabs:
+ - history:
+ - url: http://localhost:*/data/backforward/1.txt
+ - active: true
+ url: http://localhost:*/data/backforward/2.txt
+ - active: true
+ history:
+ - active: true
+ url: http://localhost:*/data/backforward/1.txt
+ - url: http://localhost:*/data/backforward/2.txt
- Scenario: Going forward without history
- Given I open data/backforward/1.txt
- When I run :forward
- Then the error "At end of history." should be shown.
+ Scenario: Going back in a new background tab
+ Given I open data/backforward/1.txt
+ When I open data/backforward/2.txt
+ And I run :tab-only
+ And I run :back -b
+ And I wait until data/backforward/1.txt is loaded
+ Then the session should look like:
+ windows:
+ - tabs:
+ - active: true
+ history:
+ - url: http://localhost:*/data/backforward/1.txt
+ - active: true
+ url: http://localhost:*/data/backforward/2.txt
+ - history:
+ - active: true
+ url: http://localhost:*/data/backforward/1.txt
+ - url: http://localhost:*/data/backforward/2.txt
+
+ Scenario: Going back in a new window
+ Given I have a fresh instance
+ When I open data/backforward/1.txt
+ And I open data/backforward/2.txt
+ And I run :back -w
+ And I wait until data/backforward/1.txt is loaded
+ Then the session should look like:
+ windows:
+ - tabs:
+ - active: true
+ history:
+ - url: about:blank
+ - url: http://localhost:*/data/backforward/1.txt
+ - active: true
+ url: http://localhost:*/data/backforward/2.txt
+ - tabs:
+ - active: true
+ history:
+ - url: about:blank
+ - active: true
+ url: http://localhost:*/data/backforward/1.txt
+ - url: http://localhost:*/data/backforward/2.txt
+
+ Scenario: Going back without history
+ Given I open data/backforward/1.txt
+ When I run :back
+ Then the error "At beginning of history." should be shown.
+
+ Scenario: Going forward without history
+ Given I open data/backforward/1.txt
+ When I run :forward
+ Then the error "At end of history." should be shown.
diff --git a/tests/integration/features/conftest.py b/tests/integration/features/conftest.py
index f6d8e5360..94f7a9367 100644
--- a/tests/integration/features/conftest.py
+++ b/tests/integration/features/conftest.py
@@ -53,6 +53,13 @@ def run_command_given(quteproc, command):
quteproc.send_cmd(command)
+@bdd.given("I have a fresh instance")
+def fresh_instance(quteproc):
+ """Restart qutebrowser instance for tests needing a fresh state."""
+ quteproc.terminate()
+ quteproc.start()
+
+
@bdd.when(bdd.parsers.parse("I run {command}"))
def run_command_when(quteproc, httpbin, command):
command = command.replace('(port)', str(httpbin.port))
diff --git a/tests/integration/features/hints.feature b/tests/integration/features/hints.feature
new file mode 100644
index 000000000..5c074ec73
--- /dev/null
+++ b/tests/integration/features/hints.feature
@@ -0,0 +1,20 @@
+Feature: Using hints
+
+ Scenario: Following a hint.
+ When I open data/hints/link.html
+ And I run :hint links normal
+ And I run :follow-hint a
+ And I wait until data/hello.txt is loaded
+ Then the requests should be:
+ data/hints/link.html
+ data/hello.txt
+
+ Scenario: Using :follow-hint outside of hint mode (issue 1105)
+ When I run :follow-hint
+ Then the error "follow-hint: This command is only allowed in hint mode." should be shown.
+
+ Scenario: Using :follow-hint with an invalid index.
+ When I open data/hints/link.html
+ And I run :hint links normal
+ And I run :follow-hint xyz
+ Then the error "No hint xyz!" should be shown.
diff --git a/tests/integration/features/misc.feature b/tests/integration/features/misc.feature
new file mode 100644
index 000000000..ecb15dd40
--- /dev/null
+++ b/tests/integration/features/misc.feature
@@ -0,0 +1,44 @@
+Feature: Various utility commands.
+
+ Scenario: :set-cmd-text and :command-accept
+ When I run :set-cmd-text :message-info "Hello World"
+ And I run :command-accept
+ Then the message "Hello World" should be shown.
+
+ Scenario: :set-cmd-text with two commands
+ When I run :set-cmd-text :message-info test ;; message-error error
+ And I run :command-accept
+ Then the message "test" should be shown.
+ And the error "error" should be shown.
+
+ Scenario: :set-cmd-text with URL replacement
+ When I open data/hello.txt
+ When I run :set-cmd-text :message-info >{url}<
+ And I run :command-accept
+ Then the message ">http://localhost:*/hello.txt<" should be shown.
+
+ Scenario: :set-cmd-text with -s and -a
+ When I run :set-cmd-text -s :message-info "foo
+ And I run :set-cmd-text -a bar"
+ And I run :command-accept
+ Then the message "foo bar" should be shown.
+
+ Scenario: :set-cmd-text with -a but without text
+ When I run :set-cmd-text -a foo
+ Then the error "No current text!" should be shown.
+
+ Scenario: :set-cmd-text with invalid command
+ When I run :set-cmd-text foo
+ Then the error "Invalid command text 'foo'." should be shown.
+
+ Scenario: :message-error
+ When I run :message-error "Hello World"
+ Then the error "Hello World" should be shown.
+
+ Scenario: :message-info
+ When I run :message-info "Hello World"
+ Then the message "Hello World" should be shown.
+
+ Scenario: :message-warning
+ When I run :message-warning "Hello World"
+ Then the warning "Hello World" should be shown.
diff --git a/tests/integration/features/set.feature b/tests/integration/features/set.feature
new file mode 100644
index 000000000..a0c3a8c55
--- /dev/null
+++ b/tests/integration/features/set.feature
@@ -0,0 +1,101 @@
+Feature: Setting settings.
+
+ Background:
+ Given I set ui -> message-timeout to 100
+
+ Scenario: Using :set
+ When I run :set colors statusbar.bg magenta
+ Then colors -> statusbar.bg should be magenta
+
+ Scenario: Only a section
+ When I run :set colors
+ Then the error "set: Either both section and option have to be given, or neither!" should be shown.
+
+ Scenario: Without value
+ When I run :set colors statusbar.bg
+ Then the error "set: The following arguments are required: value" should be shown.
+
+ Scenario: Invalid section
+ When I run :set blah blub foo
+ Then the error "set: Section 'blah' does not exist!" should be shown.
+
+ Scenario: Invalid option
+ When I run :set general blub foo
+ Then the error "set: No option 'blub' in section 'general'" should be shown.
+
+ Scenario: Toggling an option
+ When I run :set general auto-save-config false
+ And I run :set general auto-save-config!
+ Then general -> auto-save-config should be True
+
+ Scenario: Toggling a non-bool option
+ When I run :set colors statusbar.bg!
+ Then the error "set: Attempted inversion of non-boolean value." should be shown.
+
+ Scenario: Getting an option
+ When I run :set colors statusbar.bg magenta
+ And I run :set colors statusbar.bg?
+ Then the message "colors statusbar.bg = magenta" should be shown.
+
+ Scenario: Using -p
+ When I run :set -p colors statusbar.bg red
+ Then the message "colors statusbar.bg = red" should be shown.
+
+ Scenario: Using ! and -p
+ When I run :set general auto-save-config false
+ And I run :set -p general auto-save-config!
+ Then the message "general auto-save-config = True" should be shown.
+
+ Scenario: Setting an invalid value
+ When I run :set general auto-save-config blah
+ Then the error "set: Invalid value 'blah' - must be a boolean!" should be shown.
+
+ Scenario: Setting a temporary option
+ # We don't actually check if the option is temporary as this isn't easy
+ # to check.
+ When I run :set -t colors statusbar.bg green
+ Then colors -> statusbar.bg should be green
+
+ Scenario: Opening qute:settings
+ When I run :set
+ And I wait for "load status for : LoadStatus.success" in the log
+ Then the session should look like:
+ windows:
+ - tabs:
+ - active: true
+ history:
+ - url: about:blank
+ - active: true
+ url: qute:settings
+
+ Scenario: Empty option with ? (issue 1109)
+ When I run :set general ?
+ Then the error "set: The following arguments are required: value" should be shown.
+
+ Scenario: Invalid section and empty option with ? (issue 1109)
+ When I run :set blah ?
+ Then the error "set: The following arguments are required: value" should be shown.
+
+ Scenario: Invalid option with ? (issue 1109)
+ When I run :set general foo?
+ Then the error "set: No option 'foo' in section 'general'" should be shown.
+
+ Scenario: Invalid section/option with ? (issue 1109)
+ When I run :set blah foo ?
+ Then the error "set: Section 'blah' does not exist!" should be shown.
+
+ Scenario: Empty option with !
+ When I run :set general !
+ Then the error "set: The following arguments are required: value" should be shown.
+
+ Scenario: Invalid section and empty option with !
+ When I run :set blah !
+ Then the error "set: The following arguments are required: value" should be shown.
+
+ Scenario: Invalid option with !
+ When I run :set general foo!
+ Then the error "set: No option 'foo' in section 'general'" should be shown.
+
+ Scenario: Invalid section/option with !
+ When I run :set blah foo !
+ Then the error "set: Section 'blah' does not exist!" should be shown.
diff --git a/tests/integration/features/test_hints.py b/tests/integration/features/test_hints.py
new file mode 100644
index 000000000..dc3905215
--- /dev/null
+++ b/tests/integration/features/test_hints.py
@@ -0,0 +1,21 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2015 Florian Bruhin (The Compiler)
+#
+# 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_bdd as bdd
+bdd.scenarios('hints.feature')
diff --git a/tests/integration/features/test_misc.py b/tests/integration/features/test_misc.py
new file mode 100644
index 000000000..f92d3fc47
--- /dev/null
+++ b/tests/integration/features/test_misc.py
@@ -0,0 +1,21 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2015 Florian Bruhin (The Compiler)
+#
+# 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_bdd as bdd
+bdd.scenarios('misc.feature')
diff --git a/tests/integration/features/test_set.py b/tests/integration/features/test_set.py
new file mode 100644
index 000000000..fdf767640
--- /dev/null
+++ b/tests/integration/features/test_set.py
@@ -0,0 +1,32 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2015 Florian Bruhin (The Compiler)
+#
+# 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 logging
+
+import pytest_bdd as bdd
+bdd.scenarios('set.feature')
+
+
+@bdd.then(bdd.parsers.parse("{section} -> {option} should be {value}"))
+def check_option(quteproc, section, option, value):
+ quteproc.send_cmd(':set {} {}?'.format(section, option))
+ msg = quteproc.wait_for(loglevel=logging.INFO, category='message',
+ message='{} {} = *'.format(section, option))
+ actual_value = msg.message.split(' = ')[1]
+ assert actual_value == value
diff --git a/tests/integration/quteprocess.py b/tests/integration/quteprocess.py
index ab3726d61..622c3511d 100644
--- a/tests/integration/quteprocess.py
+++ b/tests/integration/quteprocess.py
@@ -68,7 +68,9 @@ class LogLine(testprocess.Line):
(?P\d\d:\d\d:\d\d)
\ (?PVDEBUG|DEBUG|INFO|WARNING|ERROR)
\ +(?P\w+)
- \ +(?P(\w+|Unknown\ module)):(?P\w+):(?P\d+)
+ \ +(?P(\w+|Unknown\ module)):
+ (?P[^"][^:]*|"[^"]+"):
+ (?P\d+)
\ (?P.+)
""", re.VERBOSE)
@@ -94,9 +96,22 @@ class LogLine(testprocess.Line):
else:
self.module = module
- self.function = match.group('function')
- self.line = int(match.group('line'))
- self.message = match.group('message')
+ function = match.group('function')
+ if function == 'none':
+ self.function = None
+ else:
+ self.function = function.strip('"')
+
+ line = int(match.group('line'))
+ if self.function is None and line == 0:
+ self.line = None
+ else:
+ self.line = line
+
+ msg_match = re.match(r'^(\[(?P\d+s ago)\] )?(?P.*)',
+ match.group('message'))
+ self.prefix = msg_match.group('prefix')
+ self.message = msg_match.group('message')
self.expected = is_ignored_qt_message(self.message)
@@ -177,7 +192,7 @@ class QuteProc(testprocess.Process):
ipc.send_to_running_instance(self._ipc_socket, [command],
target_arg='')
self.wait_for(category='commands', module='command', function='run',
- message='Calling *')
+ message='command called: *')
def set_setting(self, sect, opt, value):
self.send_cmd(':set "{}" "{}" "{}"'.format(sect, opt, value))
@@ -197,7 +212,7 @@ class QuteProc(testprocess.Process):
message=message)
line.expected = True
- def wait_for(self, timeout=15000, **kwargs):
+ def wait_for(self, timeout=None, **kwargs):
"""Override testprocess.wait_for to check past messages.
self._data is cleared after every test to provide at least some
diff --git a/tests/integration/test_mhtml_e2e.py b/tests/integration/test_mhtml_e2e.py
new file mode 100644
index 000000000..c3185c007
--- /dev/null
+++ b/tests/integration/test_mhtml_e2e.py
@@ -0,0 +1,103 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2015 Florian Bruhin (The Compiler)
+#
+# 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 mhtml downloads based on sample files."""
+
+import os
+import re
+import os.path
+
+import pytest
+
+
+def collect_tests():
+ basedir = os.path.dirname(__file__)
+ datadir = os.path.join(basedir, 'data', 'downloads', 'mhtml')
+ files = os.listdir(datadir)
+ return files
+
+
+def normalize_line(line):
+ line = line.rstrip('\n')
+ line = re.sub('boundary="---=_qute-[0-9a-f-]+"',
+ 'boundary="---=_qute-UUID"', line)
+ line = re.sub('^-----=_qute-[0-9a-f-]+$', '-----=_qute-UUID', line)
+ line = re.sub(r'localhost:\d{1,5}', 'localhost:(port)', line)
+ return line
+
+
+class DownloadDir:
+
+ """Abstraction over a download directory."""
+
+ def __init__(self, tmpdir):
+ self._tmpdir = tmpdir
+ self.location = str(tmpdir)
+
+ def read_file(self):
+ files = self._tmpdir.listdir()
+ assert len(files) == 1
+
+ with open(str(files[0]), 'r', encoding='utf-8') as f:
+ return f.readlines()
+
+ def compare_mhtml(self, filename):
+ with open(filename, 'r', encoding='utf-8') as f:
+ expected_data = [normalize_line(line) for line in f]
+ actual_data = self.read_file()
+ actual_data = [normalize_line(line) for line in actual_data]
+ assert actual_data == expected_data
+
+
+@pytest.fixture
+def download_dir(tmpdir):
+ return DownloadDir(tmpdir)
+
+
+@pytest.mark.parametrize('test_name', collect_tests())
+def test_mhtml(test_name, download_dir, quteproc, httpbin):
+ quteproc.set_setting('storage', 'download-directory',
+ download_dir.location)
+ quteproc.set_setting('storage', 'prompt-download-directory', 'false')
+
+ test_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)),
+ 'data', 'downloads', 'mhtml', test_name)
+ test_path = 'data/downloads/mhtml/{}'.format(test_name)
+
+ quteproc.open_path('{}/{}.html'.format(test_path, test_name))
+ download_dest = os.path.join(download_dir.location,
+ '{}-downloaded.mht'.format(test_name))
+ quteproc.send_cmd(':download --mhtml --dest "{}"'.format(download_dest))
+ quteproc.wait_for(category='downloads', module='mhtml',
+ function='finish_file',
+ message='All assets downloaded, ready to finish off!')
+
+ expected_file = os.path.join(test_dir, '{}.mht'.format(test_name))
+ download_dir.compare_mhtml(expected_file)
+
+ with open(os.path.join(test_dir, 'requests'), encoding='utf-8') as f:
+ expected_requests = []
+ for line in f:
+ if line.startswith('#'):
+ continue
+ path = '/{}/{}'.format(test_path, line.strip())
+ expected_requests.append(httpbin.Request('GET', path))
+
+ actual_requests = httpbin.get_requests()
+ assert sorted(actual_requests) == sorted(expected_requests)
diff --git a/tests/integration/test_quteprocess.py b/tests/integration/test_quteprocess.py
index 8fdfe1593..ad2f3da97 100644
--- a/tests/integration/test_quteprocess.py
+++ b/tests/integration/test_quteprocess.py
@@ -19,8 +19,14 @@
"""Test the quteproc fixture used for tests."""
+import logging
+import datetime
+
import pytest
+import quteprocess
+from qutebrowser.utils import log
+
def test_quteproc_error_message(qtbot, quteproc):
"""Make sure the test fails with an unexpected error message."""
@@ -36,3 +42,76 @@ def test_qt_log_ignore(qtbot, quteproc):
"""Make sure the test passes when logging a qt_log_ignore message."""
with qtbot.waitSignal(quteproc.got_error, raising=True):
quteproc.send_cmd(':message-error "SpellCheck: test"')
+
+
+@pytest.mark.parametrize('data, attrs', [
+ (
+ # Normal message
+ '01:02:03 DEBUG init earlyinit:init_log:280 Log initialized.',
+ {
+ 'timestamp': datetime.datetime(year=1900, month=1, day=1,
+ hour=1, minute=2, second=3),
+ 'loglevel': logging.DEBUG,
+ 'category': 'init',
+ 'module': 'earlyinit',
+ 'function': 'init_log',
+ 'line': 280,
+ 'message': 'Log initialized.',
+ 'expected': False,
+ }
+ ),
+ (
+ # VDEBUG
+ '00:00:00 VDEBUG foo foo:foo:0 test',
+ {'loglevel': log.VDEBUG_LEVEL}
+ ),
+ (
+ # Unknown module
+ '00:00:00 WARNING qt Unknown module:none:0 test',
+ {'module': None, 'function': None, 'line': None},
+ ),
+ (
+ # Expected message
+ '00:00:00 VDEBUG foo foo:foo:0 SpellCheck: test',
+ {'expected': True},
+ ),
+ (
+ # Weird Qt location
+ '00:00:00 DEBUG qt qnetworkreplyhttpimpl:"void '
+ 'QNetworkReplyHttpImplPrivate::error(QNetworkReply::NetworkError, '
+ 'const QString&)":1929 QNetworkReplyImplPrivate::error: Internal '
+ 'problem, this method must only be called once.',
+ {
+ 'module': 'qnetworkreplyhttpimpl',
+ 'function': 'void QNetworkReplyHttpImplPrivate::error('
+ 'QNetworkReply::NetworkError, const QString&)',
+ 'line': 1929
+ }
+ ),
+ (
+ '00:00:00 WARNING qt qxcbxsettings:"QXcbXSettings::'
+ 'QXcbXSettings(QXcbScreen*)":233 '
+ 'QXcbXSettings::QXcbXSettings(QXcbScreen*) Failed to get selection '
+ 'owner for XSETTINGS_S atom ',
+ {
+ 'module': 'qxcbxsettings',
+ 'function': 'QXcbXSettings::QXcbXSettings(QXcbScreen*)',
+ 'line': 233,
+ }
+ ),
+ (
+ # With [2s ago] marker
+ '00:00:00 DEBUG foo foo:foo:0 [2s ago] test',
+ {'prefix': '2s ago', 'message': 'test'}
+ ),
+])
+def test_log_line_parse(data, attrs):
+ line = quteprocess.LogLine(data)
+ for name, expected in attrs.items():
+ actual = getattr(line, name)
+ assert actual == expected, name
+
+
+def test_log_line_no_match():
+ with pytest.raises(quteprocess.NoLineMatch):
+ quteprocess.LogLine("Hello World!")
diff --git a/tests/integration/testprocess.py b/tests/integration/testprocess.py
index 4b7608464..be098169b 100644
--- a/tests/integration/testprocess.py
+++ b/tests/integration/testprocess.py
@@ -20,6 +20,7 @@
"""Base class for a subprocess run for tests.."""
import re
+import os
import time
import fnmatch
@@ -136,7 +137,9 @@ class Process(QObject):
print("INVALID: {}".format(line))
continue
- if parsed is not None:
+ if parsed is None:
+ print("IGNORED: {}".format(line))
+ else:
self._data.append(parsed)
self.new_data.emit(parsed)
@@ -213,7 +216,7 @@ class Process(QObject):
else:
return value == expected
- def wait_for(self, timeout=15000, **kwargs):
+ def wait_for(self, timeout=None, **kwargs):
"""Wait until a given value is found in the data.
Keyword arguments to this function get interpreted as attributes of the
@@ -223,6 +226,11 @@ class Process(QObject):
Return:
The matched line.
"""
+ if timeout is None:
+ if 'CI' in os.environ:
+ timeout = 15000
+ else:
+ timeout = 5000
# Search existing messages
for line in self._data:
matches = []
diff --git a/tests/unit/browser/test_cache.py b/tests/unit/browser/test_cache.py
index 831b232eb..2a269051b 100644
--- a/tests/unit/browser/test_cache.py
+++ b/tests/unit/browser/test_cache.py
@@ -35,6 +35,55 @@ def preload_cache(cache, url='http://www.example.com/', content=b'foobar'):
cache.insert(device)
+def test_cache_config_change_cache_size(config_stub, tmpdir):
+ """Change cache size and emit signal to trigger on_config_changed."""
+ max_cache_size = 1024
+ config_stub.data = {
+ 'storage': {'cache-size': max_cache_size},
+ 'general': {'private-browsing': False}
+ }
+ disk_cache = cache.DiskCache(str(tmpdir))
+ assert disk_cache.maximumCacheSize() == max_cache_size
+
+ config_stub.set('storage', 'cache-size', max_cache_size * 2)
+ assert disk_cache.maximumCacheSize() == max_cache_size * 2
+
+
+def test_cache_config_enable_private_browsing(config_stub, tmpdir):
+ """Change private-browsing config to True and emit signal."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': False}
+ }
+ disk_cache = cache.DiskCache(str(tmpdir))
+ assert disk_cache.cacheSize() == 0
+ preload_cache(disk_cache)
+ assert disk_cache.cacheSize() > 0
+
+ config_stub.set('general', 'private-browsing', True)
+ assert disk_cache.cacheSize() == 0
+
+
+def test_cache_config_disable_private_browsing(config_stub, tmpdir):
+ """Change private-browsing config to False and emit signal."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': True}
+ }
+ url = 'http://qutebrowser.org'
+ metadata = QNetworkCacheMetaData()
+ metadata.setUrl(QUrl(url))
+ assert metadata.isValid()
+
+ disk_cache = cache.DiskCache(str(tmpdir))
+ assert disk_cache.prepare(metadata) is None
+
+ config_stub.set('general', 'private-browsing', False)
+ content = b'cute'
+ preload_cache(disk_cache, url, content)
+ assert disk_cache.data(QUrl(url)).readAll() == content
+
+
def test_cache_size_leq_max_cache_size(config_stub, tmpdir):
"""Test cacheSize <= MaximumCacheSize when cache is activated."""
limit = 100
@@ -54,6 +103,63 @@ def test_cache_size_leq_max_cache_size(config_stub, tmpdir):
assert disk_cache.cacheSize() < limit+100
+def test_cache_size_deactivated(config_stub, tmpdir):
+ """Confirm that the cache size returns 0 when deactivated."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': True}
+ }
+ disk_cache = cache.DiskCache(str(tmpdir))
+ assert disk_cache.cacheSize() == 0
+
+
+def test_cache_existing_metadata_file(config_stub, tmpdir):
+ """Test querying existing meta data file from activated cache."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': False}
+ }
+ url = 'http://qutebrowser.org'
+ content = b'foobar'
+
+ metadata = QNetworkCacheMetaData()
+ metadata.setUrl(QUrl(url))
+ assert metadata.isValid()
+
+ disk_cache = cache.DiskCache(str(tmpdir))
+ device = disk_cache.prepare(metadata)
+ assert device is not None
+ device.write(content)
+ disk_cache.insert(device)
+ disk_cache.updateMetaData(metadata)
+
+ files = list(tmpdir.visit(fil=lambda path: path.isfile()))
+ assert len(files) == 1
+ assert disk_cache.fileMetaData(str(files[0])) == metadata
+
+
+def test_cache_nonexistent_metadata_file(config_stub, tmpdir):
+ """Test querying nonexistent meta data file from activated cache."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': False}
+ }
+
+ disk_cache = cache.DiskCache(str(tmpdir))
+ cache_file = disk_cache.fileMetaData("nosuchfile")
+ assert cache_file.isValid() == False
+
+
+def test_cache_deactivated_metadata_file(config_stub, tmpdir):
+ """Test querying meta data file when cache is deactivated."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': True}
+ }
+ disk_cache = cache.DiskCache(str(tmpdir))
+ assert disk_cache.fileMetaData("foo") == QNetworkCacheMetaData()
+
+
def test_cache_deactivated_private_browsing(config_stub, tmpdir):
"""Test if cache is deactivated in private-browsing mode."""
config_stub.data = {
@@ -104,12 +210,15 @@ def test_cache_deactivated_remove_data(config_stub, tmpdir):
assert disk_cache.remove(url) == False
-def test_cache_insert_data(tmpdir):
+def test_cache_insert_data(config_stub, tmpdir):
"""Test if entries inserted into the cache are actually there."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': False}
+ }
url = 'http://qutebrowser.org'
content = b'foobar'
- disk_cache = QNetworkDiskCache()
- disk_cache.setCacheDirectory(str(tmpdir))
+ disk_cache = cache.DiskCache(str(tmpdir))
assert disk_cache.cacheSize() == 0
preload_cache(disk_cache, url, content)
@@ -118,11 +227,37 @@ def test_cache_insert_data(tmpdir):
assert disk_cache.data(QUrl(url)).readAll() == content
-def test_cache_remove_data(tmpdir):
- """Test if a previously inserted entry can be removed from the cache."""
+def test_cache_deactivated_insert_data(config_stub, tmpdir):
+ """Insert data when cache is deactivated."""
+ # First create QNetworkDiskCache just to get a valid QIODevice from it
url = 'http://qutebrowser.org'
disk_cache = QNetworkDiskCache()
disk_cache.setCacheDirectory(str(tmpdir))
+ metadata = QNetworkCacheMetaData()
+ metadata.setUrl(QUrl(url))
+ device = disk_cache.prepare(metadata)
+ assert device is not None
+
+ # Now create a deactivated DiskCache and insert the valid device created
+ # above (there probably is a better way to get a valid QIODevice...)
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': True}
+ }
+
+ deactivated_cache = cache.DiskCache(str(tmpdir))
+ assert deactivated_cache.insert(device) is None
+
+
+
+def test_cache_remove_data(config_stub, tmpdir):
+ """Test if a previously inserted entry can be removed from the cache."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': False}
+ }
+ url = 'http://qutebrowser.org'
+ disk_cache = cache.DiskCache(str(tmpdir))
preload_cache(disk_cache, url)
assert disk_cache.cacheSize() > 0
@@ -146,14 +281,27 @@ def test_cache_clear_activated(config_stub, tmpdir):
assert disk_cache.cacheSize() == 0
-def test_cache_metadata(tmpdir):
+def test_cache_clear_deactivated(config_stub, tmpdir):
+ """Test method clear() on deactivated cache."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': True}
+ }
+ disk_cache = cache.DiskCache(str(tmpdir))
+ assert disk_cache.clear() is None
+
+
+def test_cache_metadata(config_stub, tmpdir):
"""Ensure that DiskCache.metaData() returns exactly what was inserted."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': False}
+ }
url = 'http://qutebrowser.org'
metadata = QNetworkCacheMetaData()
metadata.setUrl(QUrl(url))
assert metadata.isValid()
- disk_cache = QNetworkDiskCache()
- disk_cache.setCacheDirectory(str(tmpdir))
+ disk_cache = cache.DiskCache(str(tmpdir))
device = disk_cache.prepare(metadata)
device.write(b'foobar')
disk_cache.insert(device)
@@ -161,11 +309,26 @@ def test_cache_metadata(tmpdir):
assert disk_cache.metaData(QUrl(url)) == metadata
-def test_cache_update_metadata(tmpdir):
- """Test updating the meta data for an existing cache entry."""
+def test_cache_deactivated_metadata(config_stub, tmpdir):
+ """Test querying metaData() on not activated cache."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': True}
+ }
url = 'http://qutebrowser.org'
- disk_cache = QNetworkDiskCache()
- disk_cache.setCacheDirectory(str(tmpdir))
+
+ disk_cache = cache.DiskCache(str(tmpdir))
+ assert disk_cache.metaData(QUrl(url)) == QNetworkCacheMetaData()
+
+
+def test_cache_update_metadata(config_stub, tmpdir):
+ """Test updating the meta data for an existing cache entry."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': False}
+ }
+ url = 'http://qutebrowser.org'
+ disk_cache = cache.DiskCache(str(tmpdir))
preload_cache(disk_cache, url, b'foo')
assert disk_cache.cacheSize() > 0
@@ -176,6 +339,21 @@ def test_cache_update_metadata(tmpdir):
assert disk_cache.metaData(QUrl(url)) == metadata
+def test_cache_deactivated_update_metadata(config_stub, tmpdir):
+ """Test updating the meta data when cache is not activated."""
+ config_stub.data = {
+ 'storage': {'cache-size': 1024},
+ 'general': {'private-browsing': True}
+ }
+ url = 'http://qutebrowser.org'
+ disk_cache = cache.DiskCache(str(tmpdir))
+
+ metadata = QNetworkCacheMetaData()
+ metadata.setUrl(QUrl(url))
+ assert metadata.isValid()
+ assert disk_cache.updateMetaData(metadata) is None
+
+
def test_cache_full(config_stub, tmpdir):
"""Do a sanity test involving everything."""
config_stub.data = {
diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py
index 8791b6779..768ba0601 100644
--- a/tests/unit/utils/test_qtutils.py
+++ b/tests/unit/utils/test_qtutils.py
@@ -520,11 +520,13 @@ def test_unset_organization(qapp, orgname, expected):
assert qapp.organizationName() == expected
-if test_file is not None:
+if test_file is not None and sys.platform != 'darwin':
# If we were able to import Python's test_file module, we run some code
# here which defines unittest TestCases to run the python tests over
# PyQIODevice.
+ # Those are not run on OS X because that seems to cause a hang sometimes.
+
@pytest.yield_fixture(scope='session', autouse=True)
def clean_up_python_testfile():
"""Clean up the python testfile after tests if tests didn't."""
diff --git a/tox.ini b/tox.ini
index ad89b853b..4eb7aaff7 100644
--- a/tox.ini
+++ b/tox.ini
@@ -39,7 +39,7 @@ deps =
six==1.10.0
termcolor==1.1.0
vulture==0.8.1
- Werkzeug==0.11
+ Werkzeug==0.11.1
wheel==0.26.0
xvfbwrapper==0.2.5
commands =