summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAxel Dahlberg <git@valleymnt.com>2021-03-03 05:07:00 -0800
committerGitHub <noreply@github.com>2021-03-03 05:07:00 -0800
commit7db764f0950f04fe044d5187713738d116fbf702 (patch)
treee97ba568d8bd3d71d830cd97c6fe581a582434ed /tests
parent82ba01647b9000b5422f6f77e874acdf4f5a511d (diff)
parent9909bf0b113b1357bb19c678046a14762b2b6901 (diff)
downloadqutebrowser-7db764f0950f04fe044d5187713738d116fbf702.tar.gz
qutebrowser-7db764f0950f04fe044d5187713738d116fbf702.zip
Merge branch 'master' into feature/6109-file-picker-stdout
Diffstat (limited to 'tests')
-rw-r--r--tests/conftest.py2
-rw-r--r--tests/end2end/data/darkmode/blank.html9
-rw-r--r--tests/end2end/data/darkmode/prefers-color-scheme.html64
-rw-r--r--tests/end2end/data/darkmode/yellow.html10
-rw-r--r--tests/end2end/features/conftest.py4
-rw-r--r--tests/end2end/features/downloads.feature2
-rw-r--r--tests/end2end/features/editor.feature11
-rw-r--r--tests/end2end/features/hints.feature3
-rw-r--r--tests/end2end/features/javascript.feature2
-rw-r--r--tests/end2end/features/misc.feature12
-rw-r--r--tests/end2end/features/qutescheme.feature4
-rw-r--r--tests/end2end/features/sessions.feature5
-rw-r--r--tests/end2end/features/tabs.feature5
-rw-r--r--tests/end2end/features/test_urlmarks_bdd.py4
-rw-r--r--tests/end2end/fixtures/quteprocess.py46
-rw-r--r--tests/end2end/fixtures/test_quteprocess.py8
-rw-r--r--tests/end2end/fixtures/test_testprocess.py4
-rw-r--r--tests/end2end/fixtures/test_webserver.py2
-rw-r--r--tests/end2end/fixtures/testprocess.py14
-rw-r--r--tests/end2end/fixtures/webserver.py1
-rw-r--r--tests/end2end/fixtures/webserver_sub.py10
-rw-r--r--tests/end2end/test_dirbrowser.py2
-rw-r--r--tests/end2end/test_invocations.py240
-rw-r--r--tests/helpers/fixtures.py17
-rw-r--r--tests/helpers/stubs.py73
-rw-r--r--tests/helpers/test_helper_utils.py12
-rw-r--r--tests/helpers/testutils.py (renamed from tests/helpers/utils.py)15
-rw-r--r--tests/unit/browser/test_caret.py2
-rw-r--r--tests/unit/browser/test_history.py44
-rw-r--r--tests/unit/browser/test_inspector.py4
-rw-r--r--tests/unit/browser/webengine/test_darkmode.py235
-rw-r--r--tests/unit/browser/webengine/test_webenginedownloads.py62
-rw-r--r--tests/unit/browser/webkit/network/test_filescheme.py2
-rw-r--r--tests/unit/browser/webkit/network/test_networkreply.py4
-rw-r--r--tests/unit/browser/webkit/test_cookies.py10
-rw-r--r--tests/unit/commands/test_userscripts.py26
-rw-r--r--tests/unit/completion/test_completiondelegate.py2
-rw-r--r--tests/unit/completion/test_completionmodel.py2
-rw-r--r--tests/unit/completion/test_completionwidget.py20
-rw-r--r--tests/unit/completion/test_models.py11
-rw-r--r--tests/unit/components/test_blockutils.py2
-rw-r--r--tests/unit/components/test_braveadblock.py8
-rw-r--r--tests/unit/components/test_hostblock.py4
-rw-r--r--tests/unit/config/test_config.py2
-rw-r--r--tests/unit/config/test_configfiles.py50
-rw-r--r--tests/unit/config/test_configinit.py13
-rw-r--r--tests/unit/config/test_configtypes.py2
-rw-r--r--tests/unit/config/test_configutils.py10
-rw-r--r--tests/unit/config/test_qtargs.py271
-rw-r--r--tests/unit/config/test_stylesheet.py14
-rw-r--r--tests/unit/javascript/conftest.py13
-rw-r--r--tests/unit/javascript/test_js_quirks.py68
-rw-r--r--tests/unit/keyinput/test_basekeyparser.py16
-rw-r--r--tests/unit/keyinput/test_keyutils.py33
-rw-r--r--tests/unit/mainwindow/statusbar/test_textbase.py2
-rw-r--r--tests/unit/mainwindow/test_messageview.py10
-rw-r--r--tests/unit/mainwindow/test_tabwidget.py6
-rw-r--r--tests/unit/misc/test_autoupdate.py12
-rw-r--r--tests/unit/misc/test_editor.py5
-rw-r--r--tests/unit/misc/test_elf.py88
-rw-r--r--tests/unit/misc/test_guiprocess.py28
-rw-r--r--tests/unit/misc/test_ipc.py48
-rw-r--r--tests/unit/misc/test_keyhints.py4
-rw-r--r--tests/unit/misc/test_miscwidgets.py2
-rw-r--r--tests/unit/misc/test_msgbox.py8
-rw-r--r--tests/unit/misc/test_pastebin.py12
-rw-r--r--tests/unit/misc/test_sql.py25
-rw-r--r--tests/unit/misc/test_throttle.py4
-rw-r--r--tests/unit/misc/userscripts/test_qute_lastpass.py4
-rw-r--r--tests/unit/scripts/test_check_coverage.py2
-rw-r--r--tests/unit/scripts/test_run_vulture.py6
-rw-r--r--tests/unit/test_qutebrowser.py15
-rw-r--r--tests/unit/utils/test_javascript.py2
-rw-r--r--tests/unit/utils/test_qtutils.py63
-rw-r--r--tests/unit/utils/test_utils.py68
-rw-r--r--tests/unit/utils/test_version.py390
-rw-r--r--tests/unit/utils/usertypes/test_question.py6
-rw-r--r--tests/unit/utils/usertypes/test_timer.py4
78 files changed, 1724 insertions, 601 deletions
diff --git a/tests/conftest.py b/tests/conftest.py
index 16cd39656..ea7381a2f 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -36,7 +36,7 @@ from helpers import logfail
from helpers.logfail import fail_on_logging
from helpers.messagemock import message_mock
from helpers.fixtures import * # noqa: F403
-from helpers import utils as testutils
+from helpers import testutils
from qutebrowser.utils import qtutils, standarddir, usertypes, utils, version
from qutebrowser.misc import objects, earlyinit
from qutebrowser.qt import sip
diff --git a/tests/end2end/data/darkmode/blank.html b/tests/end2end/data/darkmode/blank.html
new file mode 100644
index 000000000..4d7b9c379
--- /dev/null
+++ b/tests/end2end/data/darkmode/blank.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Blank page</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/tests/end2end/data/darkmode/prefers-color-scheme.html b/tests/end2end/data/darkmode/prefers-color-scheme.html
new file mode 100644
index 000000000..b1feb84d7
--- /dev/null
+++ b/tests/end2end/data/darkmode/prefers-color-scheme.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Prefers colorscheme test</title>
+ <style>
+body {
+ background: #aa0000;
+}
+#dark-text {
+ display: none;
+}
+#light-text {
+ display: none;
+}
+#no-preference-text {
+ display: none;
+}
+
+@media (prefers-color-scheme: dark) {
+ body {
+ background: #222222;
+ color: #ffffff;
+ }
+ #dark-text {
+ display: inline;
+ }
+ #missing-support-text {
+ display: none;
+ }
+}
+
+@media (prefers-color-scheme: light) {
+ body {
+ background: #dddddd;
+ }
+ #light-text {
+ display: inline;
+ }
+ #missing-support-text {
+ display: none;
+ }
+}
+
+@media (prefers-color-scheme: no-preference) {
+ body {
+ background: #00aa00;
+ }
+ #no-preference-text {
+ display: inline;
+ }
+ #missing-support-text {
+ display: none;
+ }
+}
+ </style>
+ </head>
+ <body>
+ <p id="dark-text">Dark preference detected.</p>
+ <p id="light-text">Light preference detected.</p>
+ <p id="no-preference-text">No preference detected.</p>
+ <p id="missing-support-text">Preference support missing.</p>
+ </body>
+</html>
diff --git a/tests/end2end/data/darkmode/yellow.html b/tests/end2end/data/darkmode/yellow.html
new file mode 100644
index 000000000..bfb1d82ba
--- /dev/null
+++ b/tests/end2end/data/darkmode/yellow.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<!-- Is it yellow? Is it green? Who knows! -->
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Yellow page</title>
+ </head>
+ <body bgcolor="#ffff99">
+ </body>
+</html>
diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py
index 2862cc5d6..ce5fc3a01 100644
--- a/tests/end2end/features/conftest.py
+++ b/tests/end2end/features/conftest.py
@@ -37,7 +37,7 @@ import pytest_bdd as bdd
import qutebrowser
from qutebrowser.utils import log, utils, docutils
from qutebrowser.browser import pdfjs
-from helpers import utils as testutils
+from helpers import testutils
def _get_echo_exe_path():
@@ -294,7 +294,7 @@ def run_command(quteproc, server, tmpdir, command):
@bdd.when(bdd.parsers.parse("I reload"))
def reload(qtbot, server, quteproc, command):
"""Reload and wait until a new request is received."""
- with qtbot.waitSignal(server.new_request):
+ with qtbot.wait_signal(server.new_request):
quteproc.send_cmd(':reload')
diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature
index 226e99ffa..1433f4c0a 100644
--- a/tests/end2end/features/downloads.feature
+++ b/tests/end2end/features/downloads.feature
@@ -611,7 +611,7 @@ Feature: Downloading things from a website.
When the unwritable dir is unwritable
And I set downloads.location.prompt to false
And I run :download http://localhost:(port)/data/downloads/download.bin --dest (tmpdir)/downloads/unwritable
- Then the error "Download error: Permission denied" should be shown
+ Then the error "Download error: *" should be shown
Scenario: Downloading 20MB file
When I set downloads.location.prompt to false
diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature
index cb12360bc..47cb1230a 100644
--- a/tests/end2end/features/editor.feature
+++ b/tests/end2end/features/editor.feature
@@ -228,3 +228,14 @@ Feature: Opening external editors
And I open data/fileselect.html
And I run :click-element id multiple_files
Then the javascript message "Files: 1.txt, 2.txt" should be logged
+
+ ## No temporary file created
+
+ Scenario: File selector deleting temporary file
+ When I set fileselect.handler to external
+ And I set fileselect.single_file.command to ['rm', '{}']
+ And I open data/fileselect.html
+ And I run :click-element id single_file
+ Then the javascript message "Files: 1.txt" should not be logged
+ And the error "Failed to open tempfile *" should be shown
+ And "Failed to delete tempfile *" should be logged with level error
diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature
index fb04db01c..cf35c5356 100644
--- a/tests/end2end/features/hints.feature
+++ b/tests/end2end/features/hints.feature
@@ -443,7 +443,8 @@ Feature: Using hints
### hints.leave_on_load
Scenario: Leaving hint mode on reload
- When I open data/hints/html/wrapped.html
+ When I set hints.leave_on_load to true
+ And I open data/hints/html/wrapped.html
And I hint with args "all"
And I run :reload
Then "Leaving mode KeyMode.hint (reason: load started)" should be logged
diff --git a/tests/end2end/features/javascript.feature b/tests/end2end/features/javascript.feature
index 3666d609d..79b9e7d01 100644
--- a/tests/end2end/features/javascript.feature
+++ b/tests/end2end/features/javascript.feature
@@ -167,7 +167,7 @@ Feature: Javascript stuff
Then "Showing error page for* 500" should be logged
And "Load error: *500" should be logged
- @flaky @windows_skip
+ @skip # Too flaky
Scenario: Using JS after window.open
When I open data/hello.txt
And I set content.javascript.can_open_tabs_automatically to true
diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature
index f2e098bc7..351135fab 100644
--- a/tests/end2end/features/misc.feature
+++ b/tests/end2end/features/misc.feature
@@ -468,6 +468,18 @@ Feature: Various utility commands.
And I run :command-accept
Then the message "blah" should be shown
+ Scenario: Command starting with space and calling previous command
+ When I run :set-cmd-text :message-info first
+ And I run :command-accept
+ And I wait for "first" in the log
+ When I run :set-cmd-text : message-info second
+ And I run :command-accept
+ And I wait for "second" in the log
+ And I run :set-cmd-text :
+ And I run :command-history-prev
+ And I run :command-accept
+ Then the message "first" should be shown
+
Scenario: Calling previous command with :completion-item-focus
When I run :set-cmd-text :message-info blah
And I wait for "Entering mode KeyMode.command (reason: *)" in the log
diff --git a/tests/end2end/features/qutescheme.feature b/tests/end2end/features/qutescheme.feature
index b1fee1603..286f8f80a 100644
--- a/tests/end2end/features/qutescheme.feature
+++ b/tests/end2end/features/qutescheme.feature
@@ -54,7 +54,7 @@ Feature: Special qute:// pages
And I run :tab-only
And I open qute:help without waiting
And I wait for "Changing title for idx 0 to 'qutebrowser help'" in the log
- And I hint with args "links normal" and follow a
+ And I hint with args "links normal" and follow ls
Then qute://help/quickstart.html should be loaded
Scenario: Opening a link with qute://help
@@ -62,7 +62,7 @@ Feature: Special qute:// pages
And I run :tab-only
And I open qute://help without waiting
And I wait until qute://help/ is loaded
- And I hint with args "links normal" and follow a
+ And I hint with args "links normal" and follow ls
Then qute://help/quickstart.html should be loaded
Scenario: Opening a link with qute://help/index.html/..
diff --git a/tests/end2end/features/sessions.feature b/tests/end2end/features/sessions.feature
index 0f0c015e0..e48947cbd 100644
--- a/tests/end2end/features/sessions.feature
+++ b/tests/end2end/features/sessions.feature
@@ -395,9 +395,10 @@ Feature: Saving and loading sessions
And I run :session-load -c pin_session
And I wait until data/numbers/3.txt is loaded
And I run :tab-focus 2
- And I run :open hello world
- Then the message "Tab is pinned!" should be shown
+ And I open data/numbers/4.txt
+ Then the message "Tab is pinned! Opening in new tab." should be shown
And the following tabs should be open:
- data/numbers/1.txt
- data/numbers/2.txt (active) (pinned)
+ - data/numbers/4.txt
- data/numbers/3.txt
diff --git a/tests/end2end/features/tabs.feature b/tests/end2end/features/tabs.feature
index ca0efefc4..7db054573 100644
--- a/tests/end2end/features/tabs.feature
+++ b/tests/end2end/features/tabs.feature
@@ -1534,10 +1534,11 @@ Feature: Tab management
Scenario: :tab-pin open url
When I open data/numbers/1.txt
And I run :tab-pin
- And I open data/numbers/2.txt without waiting
- Then the message "Tab is pinned!" should be shown
+ And I open data/numbers/2.txt
+ Then the message "Tab is pinned! Opening in new tab." should be shown
And the following tabs should be open:
- data/numbers/1.txt (active) (pinned)
+ - data/numbers/2.txt
Scenario: :tab-pin open url with tabs.pinned.frozen = false
When I set tabs.pinned.frozen to false
diff --git a/tests/end2end/features/test_urlmarks_bdd.py b/tests/end2end/features/test_urlmarks_bdd.py
index 2d4dcb5b5..8aea592c3 100644
--- a/tests/end2end/features/test_urlmarks_bdd.py
+++ b/tests/end2end/features/test_urlmarks_bdd.py
@@ -21,7 +21,7 @@ import os.path
import pytest_bdd as bdd
-from helpers import utils
+from helpers import testutils
bdd.scenarios('urlmarks.feature')
@@ -48,7 +48,7 @@ def _check_marks(quteproc, quickmarks, expected, contains):
lines = f.readlines()
matched_line = any(
- utils.pattern_match(pattern=expected, value=line.rstrip('\n'))
+ testutils.pattern_match(pattern=expected, value=line.rstrip('\n'))
for line in lines)
assert matched_line == contains, lines
diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py
index 4945dbc77..9ef338768 100644
--- a/tests/end2end/fixtures/quteprocess.py
+++ b/tests/end2end/fixtures/quteprocess.py
@@ -33,11 +33,12 @@ import json
import yaml
import pytest
-from PyQt5.QtCore import pyqtSignal, QUrl
+from PyQt5.QtCore import pyqtSignal, QUrl, QPoint
+from PyQt5.QtGui import QImage, QColor
from qutebrowser.misc import ipc
from qutebrowser.utils import log, utils, javascript
-from helpers import utils as testutils
+from helpers import testutils
from end2end.fixtures import testprocess
@@ -337,8 +338,11 @@ def is_ignored_chromium_message(line):
# Windows N:
# https://github.com/microsoft/playwright/issues/2901
- (r'DXVAVDA fatal error: could not LoadLibrary: .*: The specified '
- r'module could not be found. \(0x7E\)'),
+ ('DXVAVDA fatal error: could not LoadLibrary: *: The specified '
+ 'module could not be found. (0x7E)'),
+
+ # Qt 5.15.3 dev build
+ r'Duplicate id found. Reassigning from * to *',
]
return any(testutils.pattern_match(pattern=pattern, value=message)
for pattern in ignored_messages)
@@ -884,6 +888,40 @@ class QuteProc(testprocess.Process):
with open(path, 'r', encoding='utf-8') as f:
return f.read()
+ def get_screenshot(
+ self,
+ *,
+ probe_pos: QPoint = None,
+ probe_color: QColor = testutils.Color(0, 0, 0),
+ ) -> QImage:
+ """Get a screenshot of the current page.
+
+ Arguments:
+ probe: If given, only continue if the pixel at the given position isn't
+ black (or whatever is specified by probe_color).
+ """
+ for _ in range(5):
+ tmp_path = self.request.getfixturevalue('tmp_path')
+ path = tmp_path / 'screenshot.png'
+ self.send_cmd(f':screenshot --force {path}')
+ self.wait_for(message=f'Screenshot saved to {path}')
+
+ img = QImage(str(path))
+ assert not img.isNull()
+
+ if probe_pos is None:
+ return img
+
+ probed_color = testutils.Color(img.pixelColor(probe_pos))
+ if probed_color == probe_color:
+ return img
+
+ # Rendering might not be completed yet...
+ time.sleep(0.5)
+
+ raise ValueError(
+ f"Pixel probing for {probe_color} failed (got {probed_color} on last try)")
+
def press_keys(self, keys):
"""Press the given keys using :fake-key."""
self.send_cmd(':fake-key -g "{}"'.format(keys))
diff --git a/tests/end2end/fixtures/test_quteprocess.py b/tests/end2end/fixtures/test_quteprocess.py
index 81dd1d13b..c4b226972 100644
--- a/tests/end2end/fixtures/test_quteprocess.py
+++ b/tests/end2end/fixtures/test_quteprocess.py
@@ -104,7 +104,7 @@ def request_mock(quteproc, monkeypatch, server):
])
def test_quteproc_error_message(qtbot, quteproc, cmd, request_mock):
"""Make sure the test fails with an unexpected error message."""
- with qtbot.waitSignal(quteproc.got_error):
+ with qtbot.wait_signal(quteproc.got_error):
quteproc.send_cmd(cmd)
# Usually we wouldn't call this from inside a test, but here we force the
# error to occur during the test rather than at teardown time.
@@ -115,7 +115,7 @@ def test_quteproc_error_message(qtbot, quteproc, cmd, request_mock):
def test_quteproc_error_message_did_fail(qtbot, quteproc, request_mock):
"""Make sure the test does not fail on teardown if the main test failed."""
request_mock.node.rep_call.failed = True
- with qtbot.waitSignal(quteproc.got_error):
+ with qtbot.wait_signal(quteproc.got_error):
quteproc.send_cmd(':message-error test')
# Usually we wouldn't call this from inside a test, but here we force the
# error to occur during the test rather than at teardown time.
@@ -142,13 +142,13 @@ def test_quteproc_skip_and_wait_for(qtbot, quteproc):
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):
+ with qtbot.wait_signal(quteproc.got_error):
quteproc.send_cmd(':message-error "SpellCheck: test"')
def test_quteprocess_quitting(qtbot, quteproc_process):
"""When qutebrowser quits, after_test should fail."""
- with qtbot.waitSignal(quteproc_process.proc.finished, timeout=15000):
+ with qtbot.wait_signal(quteproc_process.proc.finished, timeout=15000):
quteproc_process.send_cmd(':quit')
with pytest.raises(testprocess.ProcessExited):
quteproc_process.after_test()
diff --git a/tests/end2end/fixtures/test_testprocess.py b/tests/end2end/fixtures/test_testprocess.py
index 73fcf8b05..aa6f19c67 100644
--- a/tests/end2end/fixtures/test_testprocess.py
+++ b/tests/end2end/fixtures/test_testprocess.py
@@ -131,7 +131,7 @@ def test_no_ready_python_process(noready_pyproc):
def test_quitting_process(qtbot, quit_pyproc):
- with qtbot.waitSignal(quit_pyproc.proc.finished):
+ with qtbot.wait_signal(quit_pyproc.proc.finished):
quit_pyproc.start()
with pytest.raises(testprocess.ProcessExited):
quit_pyproc.after_test()
@@ -139,7 +139,7 @@ def test_quitting_process(qtbot, quit_pyproc):
def test_quitting_process_expected(qtbot, quit_pyproc):
quit_pyproc.exit_expected = True
- with qtbot.waitSignal(quit_pyproc.proc.finished):
+ with qtbot.wait_signal(quit_pyproc.proc.finished):
quit_pyproc.start()
quit_pyproc.after_test()
diff --git a/tests/end2end/fixtures/test_webserver.py b/tests/end2end/fixtures/test_webserver.py
index 4ad9108ca..3c825e5bc 100644
--- a/tests/end2end/fixtures/test_webserver.py
+++ b/tests/end2end/fixtures/test_webserver.py
@@ -34,7 +34,7 @@ import pytest
('/data/hello.txt', 'Hello World!', True),
])
def test_server(server, qtbot, path, content, expected):
- with qtbot.waitSignal(server.new_request, timeout=100):
+ with qtbot.wait_signal(server.new_request, timeout=100):
url = 'http://localhost:{}{}'.format(server.port, path)
try:
response = urllib.request.urlopen(url)
diff --git a/tests/end2end/fixtures/testprocess.py b/tests/end2end/fixtures/testprocess.py
index 75bab0bf3..33b154e9a 100644
--- a/tests/end2end/fixtures/testprocess.py
+++ b/tests/end2end/fixtures/testprocess.py
@@ -30,7 +30,7 @@ from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QProcess, QObject,
QElapsedTimer, QProcessEnvironment)
from PyQt5.QtTest import QSignalSpy
-from helpers import utils
+from helpers import testutils
from qutebrowser.utils import utils as quteutils
@@ -77,13 +77,13 @@ def _render_log(data, *, verbose, threshold=100):
if (len(data) > threshold and
not verbose and
not is_exception and
- not utils.ON_CI):
+ not testutils.ON_CI):
msg = '[{} lines suppressed, use -v to show]'.format(
len(data) - threshold)
data = [msg] + data[-threshold:]
- if utils.ON_CI:
- data = [utils.gha_group_begin('Log')] + data + [utils.gha_group_end()]
+ if testutils.ON_CI:
+ data = [testutils.gha_group_begin('Log')] + data + [testutils.gha_group_end()]
return '\n'.join(data)
@@ -233,7 +233,7 @@ class Process(QObject):
self._started = True
verbose = self.request.config.getoption('--verbose')
- timeout = 60 if utils.ON_CI else 20
+ timeout = 60 if testutils.ON_CI else 20
for _ in range(timeout):
with self._wait_signal(self.ready, timeout=1000,
raising=False) as blocker:
@@ -350,7 +350,7 @@ class Process(QObject):
elif isinstance(expected, regex_type):
return expected.search(value)
elif isinstance(value, (bytes, str)):
- return utils.pattern_match(pattern=expected, value=value)
+ return testutils.pattern_match(pattern=expected, value=value)
else:
return value == expected
@@ -475,7 +475,7 @@ class Process(QObject):
if timeout is None:
if do_skip:
timeout = 2000
- elif utils.ON_CI:
+ elif testutils.ON_CI:
timeout = 15000
else:
timeout = 5000
diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py
index 4677415f1..81a864c8e 100644
--- a/tests/end2end/fixtures/webserver.py
+++ b/tests/end2end/fixtures/webserver.py
@@ -75,6 +75,7 @@ class Request(testprocess.Line):
'/absolute-redirect': [HTTPStatus.FOUND],
'/cookies/set': [HTTPStatus.FOUND],
+ '/cookies/set-custom': [HTTPStatus.FOUND],
'/500-inline': [HTTPStatus.INTERNAL_SERVER_ERROR],
'/500': [HTTPStatus.INTERNAL_SERVER_ERROR],
diff --git a/tests/end2end/fixtures/webserver_sub.py b/tests/end2end/fixtures/webserver_sub.py
index b7999148f..a4f54e19c 100644
--- a/tests/end2end/fixtures/webserver_sub.py
+++ b/tests/end2end/fixtures/webserver_sub.py
@@ -199,6 +199,16 @@ def set_cookies():
return r
+@app.route('/cookies/set-custom')
+def set_custom_cookie():
+ """Set a cookie with a custom max_age/expires."""
+ r = app.make_response(flask.redirect(flask.url_for('view_cookies')))
+ max_age = flask.request.args.get('max_age')
+ r.set_cookie(key='cookie', value='value',
+ max_age=int(max_age) if max_age else None)
+ return r
+
+
@app.route('/basic-auth/<user>/<passwd>')
def basic_auth(user='user', passwd='passwd'):
"""Prompt the user for authorization using HTTP Basic Auth."""
diff --git a/tests/end2end/test_dirbrowser.py b/tests/end2end/test_dirbrowser.py
index 8a9f895a2..3efbfc14e 100644
--- a/tests/end2end/test_dirbrowser.py
+++ b/tests/end2end/test_dirbrowser.py
@@ -30,7 +30,7 @@ import bs4
from PyQt5.QtCore import QUrl
from qutebrowser.utils import urlutils
-from helpers import utils as testutils
+from helpers import testutils
pytestmark = pytest.mark.qtwebengine_skip("Title is empty when parsing for "
diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py
index b509e355b..f3d74d1f0 100644
--- a/tests/end2end/test_invocations.py
+++ b/tests/end2end/test_invocations.py
@@ -23,13 +23,15 @@ import configparser
import subprocess
import sys
import logging
+import importlib
import re
import json
import pytest
-from PyQt5.QtCore import QProcess
+from PyQt5.QtCore import QProcess, QPoint
-from helpers import utils
+from helpers import testutils
+from qutebrowser.utils import qtutils, utils
ascii_locale = pytest.mark.skipif(sys.hexversion >= 0x03070000,
@@ -46,7 +48,7 @@ def _base_args(config):
args += ['--backend', 'webkit']
if config.webengine:
- args += utils.seccomp_args(qt_flag=True)
+ args += testutils.seccomp_args(qt_flag=True)
args.append('about:blank')
return args
@@ -424,24 +426,116 @@ def test_referrer(quteproc_new, server, server2, request, value, expected):
assert headers.get('Referer') == expected
+def test_preferred_colorscheme_unsupported(request, quteproc_new):
+ """Test versions without preferred-color-scheme support."""
+ if request.config.webengine and qtutils.version_check('5.14'):
+ pytest.skip("preferred-color-scheme is supported")
+
+ args = _base_args(request.config) + ['--temp-basedir']
+ quteproc_new.start(args)
+ quteproc_new.open_path('data/darkmode/prefers-color-scheme.html')
+ content = quteproc_new.get_content()
+ assert content == "Preference support missing."
+
+
@pytest.mark.qtwebkit_skip
-@utils.qt514
-def test_preferred_colorscheme(request, quteproc_new):
+@testutils.qt514
+@pytest.mark.parametrize('value', ["dark", "light", "auto", None])
+def test_preferred_colorscheme(request, quteproc_new, value):
"""Make sure the the preferred colorscheme is set."""
+ if not request.config.webengine:
+ pytest.skip("Skipped with QtWebKit")
+
+ args = _base_args(request.config) + ['--temp-basedir']
+ if value is not None:
+ args += ['-s', 'colors.webpage.preferred_color_scheme', value]
+ quteproc_new.start(args)
+
+ dark_text = "Dark preference detected."
+ light_text = "Light preference detected."
+
+ expected_values = {
+ "dark": [dark_text],
+ "light": [light_text],
+
+ # Depends on the environment the test is running in.
+ "auto": [dark_text, light_text],
+ None: [dark_text, light_text],
+ }
+ xfail = False
+ if not qtutils.version_check('5.15.2', compiled=False):
+ # On older versions, "light" is not supported, so the result will depend on the
+ # environment.
+ expected_values["light"].append(dark_text)
+ elif qtutils.version_check('5.15.2', exact=True, compiled=False):
+ # Test the WORKAROUND https://bugreports.qt.io/browse/QTBUG-89753
+ # With that workaround, we should always get the light preference.
+ for key in ["auto", None]:
+ expected_values[key].remove(dark_text)
+ xfail = value in ["auto", None]
+
+ quteproc_new.open_path('data/darkmode/prefers-color-scheme.html')
+ content = quteproc_new.get_content()
+ assert content in expected_values[value]
+
+ if xfail:
+ # Unsatisfactory result, but expected based on a Qt bug.
+ pytest.xfail("QTBUG-89753")
+
+
+@testutils.qt514
+def test_preferred_colorscheme_with_dark_mode(
+ request, quteproc_new, webengine_versions):
+ """Test interaction between preferred-color-scheme and dark mode."""
+ if not request.config.webengine:
+ pytest.skip("Skipped with QtWebKit")
+
args = _base_args(request.config) + [
'--temp-basedir',
- '-s', 'colors.webpage.prefers_color_scheme_dark', 'true',
+ '-s', 'colors.webpage.preferred_color_scheme', 'dark',
+ '-s', 'colors.webpage.darkmode.enabled', 'true',
+ '-s', 'colors.webpage.darkmode.algorithm', 'brightness-rgb',
]
quteproc_new.start(args)
- quteproc_new.send_cmd(':jseval matchMedia("(prefers-color-scheme: dark)").matches')
- quteproc_new.wait_for(message='True')
+ quteproc_new.open_path('data/darkmode/prefers-color-scheme.html')
+ content = quteproc_new.get_content()
+
+ if webengine_versions.webengine == utils.VersionNumber(5, 15, 3):
+ # https://bugs.chromium.org/p/chromium/issues/detail?id=1177973
+ # No workaround known.
+ expected_text = 'Light preference detected.'
+ # light website color, inverted by darkmode
+ expected_color = testutils.Color(127, 127, 127)
+ xfail = True
+ elif webengine_versions.webengine == utils.VersionNumber(5, 15, 2):
+ # Our workaround breaks when dark mode is enabled...
+ # Also, for some reason, dark mode doesn't work on that page either!
+ expected_text = 'No preference detected.'
+ expected_color = testutils.Color(0, 170, 0) # green
+ xfail = True
+ else:
+ # Qt 5.14 and 5.15.0/.1 work correctly.
+ # Hopefully, so does Qt 6.x in the future?
+ expected_text = 'Dark preference detected.'
+ expected_color = testutils.Color(34, 34, 34) # dark website color
+ xfail = False
+
+ pos = QPoint(0, 0)
+ img = quteproc_new.get_screenshot(probe_pos=pos, probe_color=expected_color)
+ color = testutils.Color(img.pixelColor(pos))
+
+ assert content == expected_text
+ assert color == expected_color
+ if xfail:
+ # We still do some checks, but we want to mark the test outcome as xfail.
+ pytest.xfail("QTBUG-89753")
@pytest.mark.qtwebkit_skip
@pytest.mark.parametrize('reason', [
'Explicitly enabled',
- pytest.param('Qt 5.14', marks=utils.qt514),
+ pytest.param('Qt 5.14', marks=testutils.qt514),
'Qt version changed',
None,
])
@@ -490,3 +584,131 @@ def test_service_worker_workaround(
quteproc_new.ensure_not_logged(message='Removing service workers at *')
else:
assert not service_worker_dir.exists()
+
+
+@testutils.qt513 # Qt 5.12 doesn't store cookies immediately
+@pytest.mark.parametrize('store', [True, False])
+def test_cookies_store(quteproc_new, request, short_tmpdir, store):
+ # Start test process
+ args = _base_args(request.config) + [
+ '--basedir', str(short_tmpdir),
+ '-s', 'content.cookies.store', str(store),
+ ]
+ quteproc_new.start(args)
+
+ # Set cookie and ensure it's set
+ quteproc_new.open_path('cookies/set-custom?max_age=30', wait=False)
+ quteproc_new.wait_for_load_finished('cookies')
+ content = quteproc_new.get_content()
+ data = json.loads(content)
+ assert data == {'cookies': {'cookie': 'value'}}
+
+ # Restart
+ quteproc_new.send_cmd(':quit')
+ quteproc_new.wait_for_quit()
+ quteproc_new.start(args)
+
+ # Check cookies
+ quteproc_new.open_path('cookies')
+ content = quteproc_new.get_content()
+ data = json.loads(content)
+ expected_cookies = {'cookie': 'value'} if store else {}
+ assert data == {'cookies': expected_cookies}
+
+ quteproc_new.send_cmd(':quit')
+ quteproc_new.wait_for_quit()
+
+
+@pytest.mark.parametrize('filename, algorithm, colors', [
+ (
+ 'blank',
+ 'lightness-cielab',
+ {
+ '5.15': testutils.Color(18, 18, 18),
+ '5.14': testutils.Color(27, 27, 27),
+ None: testutils.Color(0, 0, 0),
+ }
+ ),
+ ('blank', 'lightness-hsl', {None: testutils.Color(0, 0, 0)}),
+ ('blank', 'brightness-rgb', {None: testutils.Color(0, 0, 0)}),
+
+ (
+ 'yellow',
+ 'lightness-cielab',
+ {
+ '5.15': testutils.Color(35, 34, 0),
+ '5.14': testutils.Color(35, 34, 0),
+ None: testutils.Color(204, 204, 0),
+ }
+ ),
+ ('yellow', 'lightness-hsl', {None: testutils.Color(204, 204, 0)}),
+ ('yellow', 'brightness-rgb', {None: testutils.Color(0, 0, 204)}),
+])
+def test_dark_mode(webengine_versions, quteproc_new, request,
+ filename, algorithm, colors):
+ if not request.config.webengine:
+ pytest.skip("Skipped with QtWebKit")
+
+ args = _base_args(request.config) + [
+ '--temp-basedir',
+ '-s', 'colors.webpage.darkmode.enabled', 'true',
+ '-s', 'colors.webpage.darkmode.algorithm', algorithm,
+ ]
+ quteproc_new.start(args)
+
+ ver = webengine_versions.webengine
+ minor_version = f'{ver.majorVersion()}.{ver.minorVersion()}'
+ expected = colors.get(minor_version, colors[None])
+
+ quteproc_new.open_path(f'data/darkmode/{filename}.html')
+
+ # Position chosen by fair dice roll.
+ # https://xkcd.com/221/
+ pos = QPoint(4, 4)
+ img = quteproc_new.get_screenshot(probe_pos=pos, probe_color=expected)
+
+ color = testutils.Color(img.pixelColor(pos))
+ # For pytest debug output
+ assert color == expected
+
+
+def test_unavailable_backend(request, quteproc_new):
+ """Test starting with a backend which isn't available.
+
+ If we use --qute-bdd-webengine, we test with QtWebKit here; otherwise we test with
+ QtWebEngine. If both are available, the test is skipped.
+
+ This ensures that we don't accidentally use backend-specific code before checking
+ that the chosen backend is actually available - i.e., that the error message is
+ properly printed, rather than an unhandled exception.
+ """
+ qtwe_module = "PyQt5.QtWebEngineWidgets"
+ qtwk_module = "PyQt5.QtWebKitWidgets"
+ # Note we want to try the *opposite* backend here.
+ if request.config.webengine:
+ pytest.importorskip(qtwe_module)
+ module = qtwk_module
+ backend = 'webkit'
+ else:
+ pytest.importorskip(qtwk_module)
+ module = qtwe_module
+ backend = 'webengine'
+
+ try:
+ importlib.import_module(module)
+ except ImportError:
+ pass
+ else:
+ pytest.skip(f"{module} is available")
+
+ args = [
+ '--debug', '--json-logging', '--no-err-windows',
+ '--backend', backend,
+ '--temp-basedir'
+ ]
+ quteproc_new.exit_expected = True
+ quteproc_new.start(args)
+ line = quteproc_new.wait_for(
+ message=('*qutebrowser tried to start with the Qt* backend but failed '
+ 'because * could not be imported.*'))
+ line.expected = True
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
index 547e11dba..4e0204741 100644
--- a/tests/helpers/fixtures.py
+++ b/tests/helpers/fixtures.py
@@ -45,7 +45,7 @@ import helpers.stubs as stubsmod
from qutebrowser.config import (config, configdata, configtypes, configexc,
configfiles, configcache, stylesheet)
from qutebrowser.api import config as configapi
-from qutebrowser.utils import objreg, standarddir, utils, usertypes
+from qutebrowser.utils import objreg, standarddir, utils, usertypes, version
from qutebrowser.browser import greasemonkey, history, qutescheme
from qutebrowser.browser.webkit import cookies, cache
from qutebrowser.misc import savemanager, sql, objects, sessions
@@ -73,7 +73,7 @@ class WidgetContainer(QWidget):
self._widget = widget
def expose(self):
- with self._qtbot.waitExposed(self):
+ with self._qtbot.wait_exposed(self):
self.show()
self._widget.setFocus()
@@ -407,7 +407,7 @@ def status_command_stub(stubs, qtbot, win_registry):
"""Fixture which provides a fake status-command object."""
cmd = stubs.StatusBarCommandStub()
objreg.register('status-command', cmd, scope='window', window=0)
- qtbot.addWidget(cmd)
+ qtbot.add_widget(cmd)
yield cmd
objreg.delete('status-command', scope='window', window=0)
@@ -725,3 +725,14 @@ def unwritable_tmp_path(tmp_path):
# Make sure pytest can clean up the tmp_path
tmp_path.chmod(0o755)
+
+
+@pytest.fixture
+def webengine_versions(testdata_scheme):
+ """Get QtWebEngine version numbers.
+
+ Calling qtwebengine_versions() initializes QtWebEngine, so we depend on
+ testdata_scheme here, to make sure that happens before.
+ """
+ pytest.importorskip('PyQt5.QtWebEngineWidgets')
+ return version.qtwebengine_versions()
diff --git a/tests/helpers/stubs.py b/tests/helpers/stubs.py
index c1ccdcf8b..4c56cf76c 100644
--- a/tests/helpers/stubs.py
+++ b/tests/helpers/stubs.py
@@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
-# pylint: disable=invalid-name,abstract-method
+# pylint: disable=abstract-method
"""Fake objects/stubs."""
@@ -26,6 +26,9 @@ from unittest import mock
import contextlib
import shutil
import dataclasses
+import builtins
+import importlib
+import types
from PyQt5.QtCore import pyqtSignal, QPoint, QProcess, QObject, QUrl
from PyQt5.QtGui import QIcon
@@ -623,10 +626,11 @@ class FakeHistoryProgress:
"""Fake for a WebHistoryProgress object."""
- def __init__(self):
+ def __init__(self, *, raise_on_tick=False):
self._started = False
self._finished = False
self._value = 0
+ self._raise_on_tick = raise_on_tick
def start(self, _text):
self._started = True
@@ -635,6 +639,8 @@ class FakeHistoryProgress:
pass
def tick(self):
+ if self._raise_on_tick:
+ raise Exception('tick-tock')
self._value += 1
def finish(self):
@@ -676,3 +682,66 @@ class FakeCookieStore:
def setCookieFilter(self, func):
self.cookie_filter = func
+
+
+class ImportFake:
+
+ """A fake for __import__ which is used by the import_fake fixture.
+
+ Attributes:
+ modules: A dict mapping module names to bools. If True, the import will
+ succeed. Otherwise, it'll fail with ImportError.
+ version_attribute: The name to use in the fake modules for the version
+ attribute.
+ version: The version to use for the modules.
+ _real_import: Saving the real __import__ builtin so the imports can be
+ done normally for modules not in self. modules.
+ """
+
+ def __init__(self, modules, monkeypatch):
+ self._monkeypatch = monkeypatch
+ self.modules = modules
+ self.version_attribute = '__version__'
+ self.version = '1.2.3'
+ self._real_import = builtins.__import__
+ self._real_importlib_import = importlib.import_module
+
+ def patch(self):
+ """Patch import functions."""
+ self._monkeypatch.setattr(builtins, '__import__', self.fake_import)
+ self._monkeypatch.setattr(
+ importlib, 'import_module', self.fake_importlib_import)
+
+ def _do_import(self, name):
+ """Helper for fake_import and fake_importlib_import to do the work.
+
+ Return:
+ The imported fake module, or None if normal importing should be
+ used.
+ """
+ if name not in self.modules:
+ # Not one of the modules to test -> use real import
+ return None
+ elif self.modules[name]:
+ ns = types.SimpleNamespace()
+ if self.version_attribute is not None:
+ setattr(ns, self.version_attribute, self.version)
+ return ns
+ else:
+ raise ImportError("Fake ImportError for {}.".format(name))
+
+ def fake_import(self, name, *args, **kwargs):
+ """Fake for the builtin __import__."""
+ module = self._do_import(name)
+ if module is not None:
+ return module
+ else:
+ return self._real_import(name, *args, **kwargs)
+
+ def fake_importlib_import(self, name):
+ """Fake for importlib.import_module."""
+ module = self._do_import(name)
+ if module is not None:
+ return module
+ else:
+ return self._real_importlib_import(name)
diff --git a/tests/helpers/test_helper_utils.py b/tests/helpers/test_helper_utils.py
index 2f4822df9..5d723429b 100644
--- a/tests/helpers/test_helper_utils.py
+++ b/tests/helpers/test_helper_utils.py
@@ -20,7 +20,7 @@
import pytest
-from helpers import utils
+from helpers import testutils
@pytest.mark.parametrize('val1, val2', [
@@ -32,7 +32,7 @@ from helpers import utils
("foobarbaz", "foo*baz"),
])
def test_partial_compare_equal(val1, val2):
- assert utils.partial_compare(val1, val2)
+ assert testutils.partial_compare(val1, val2)
@pytest.mark.parametrize('val1, val2, error', [
@@ -48,9 +48,9 @@ def test_partial_compare_equal(val1, val2):
(23.42, 13.37, "23.42 != 13.37 (float comparison)"),
])
def test_partial_compare_not_equal(val1, val2, error):
- outcome = utils.partial_compare(val1, val2)
+ outcome = testutils.partial_compare(val1, val2)
assert not outcome
- assert isinstance(outcome, utils.PartialCompareOutcome)
+ assert isinstance(outcome, testutils.PartialCompareOutcome)
assert outcome.error == error
@@ -72,9 +72,9 @@ def test_partial_compare_not_equal(val1, val2, error):
('foo?ar', 'foo?ar', True),
])
def test_pattern_match(pattern, value, expected):
- assert utils.pattern_match(pattern=pattern, value=value) == expected
+ assert testutils.pattern_match(pattern=pattern, value=value) == expected
def test_nop_contextmanager():
- with utils.nop_contextmanager():
+ with testutils.nop_contextmanager():
pass
diff --git a/tests/helpers/utils.py b/tests/helpers/testutils.py
index 41da08331..8bb622133 100644
--- a/tests/helpers/utils.py
+++ b/tests/helpers/testutils.py
@@ -32,19 +32,32 @@ import importlib.machinery
import pytest
from PyQt5.QtCore import qVersion
+from PyQt5.QtGui import QColor
try:
from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION_STR
except ImportError:
PYQT_WEBENGINE_VERSION_STR = None
-from qutebrowser.utils import qtutils, log
+from qutebrowser.utils import qtutils, log, utils
ON_CI = 'CI' in os.environ
+qt513 = pytest.mark.skipif(
+ not qtutils.version_check('5.13'), reason="Needs Qt 5.13 or newer")
qt514 = pytest.mark.skipif(
not qtutils.version_check('5.14'), reason="Needs Qt 5.14 or newer")
+class Color(QColor):
+
+ """A QColor with a nicer repr()."""
+
+ def __repr__(self):
+ return utils.get_repr(self, constructor=True, red=self.red(),
+ green=self.green(), blue=self.blue(),
+ alpha=self.alpha())
+
+
class PartialCompareOutcome:
"""Storage for a partial_compare error.
diff --git a/tests/unit/browser/test_caret.py b/tests/unit/browser/test_caret.py
index ad5425b8e..288471ea0 100644
--- a/tests/unit/browser/test_caret.py
+++ b/tests/unit/browser/test_caret.py
@@ -107,7 +107,7 @@ def test_selection_callback_wrong_mode(qtbot, caplog,
async callback was happening, so we don't want to mess with the status bar.
"""
assert mode_manager.mode == usertypes.KeyMode.normal
- with qtbot.assertNotEmitted(webengine_tab.caret.selection_toggled):
+ with qtbot.assert_not_emitted(webengine_tab.caret.selection_toggled):
webengine_tab.caret._toggle_sel_translate('normal')
msg = 'Ignoring caret selection callback in KeyMode.normal'
diff --git a/tests/unit/browser/test_history.py b/tests/unit/browser/test_history.py
index 9e3bd0519..9b08de30d 100644
--- a/tests/unit/browser/test_history.py
+++ b/tests/unit/browser/test_history.py
@@ -290,7 +290,7 @@ class TestHistoryInterface:
def test_history_interface(self, qtbot, webview, hist_interface):
html = b"<a href='about:blank'>foo</a>"
url = urlutils.data_url('text/html', html)
- with qtbot.waitSignal(webview.loadFinished):
+ with qtbot.wait_signal(webview.loadFinished):
webview.load(url)
@@ -392,6 +392,25 @@ class TestRebuild:
('example.com/1', '', 1),
('example.com/2', '', 2),
]
+ assert not hist3.metainfo['force_rebuild']
+
+ def test_force_rebuild(self, web_history, stubs):
+ """Ensure that completion is regenerated if we force a rebuild."""
+ web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1)
+ web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2)
+ web_history.completion.delete('url', 'example.com/2')
+
+ hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress())
+ # User version always changes, so this won't work
+ # assert list(hist2.completion) == [('example.com/1', '', 1)]
+ hist2.metainfo['force_rebuild'] = True
+
+ hist3 = history.WebHistory(progress=stubs.FakeHistoryProgress())
+ assert list(hist3.completion) == [
+ ('example.com/1', '', 1),
+ ('example.com/2', '', 2),
+ ]
+ assert not hist3.metainfo['force_rebuild']
def test_exclude(self, config_stub, web_history, stubs):
"""Ensure that patterns in completion.web_history.exclude are ignored.
@@ -443,6 +462,23 @@ class TestRebuild:
assert progress._started
assert progress._finished
+ def test_interrupted(self, stubs, web_history, monkeypatch):
+ """If we interrupt the rebuilding process, force_rebuild should still be set."""
+ web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1)
+ progress = stubs.FakeHistoryProgress(raise_on_tick=True)
+
+ # Trigger a completion rebuild
+ monkeypatch.setattr(sql, 'user_version_changed', lambda: True)
+
+ with pytest.raises(Exception, match='tick-tock'):
+ history.WebHistory(progress=progress)
+
+ assert web_history.metainfo['force_rebuild']
+
+ # If we now try again, we should get another rebuild. But due to user_version
+ # always changing, we can't test this at the moment (see the FIXME in the
+ # docstring for details)
+
class TestCompletionMetaInfo:
@@ -466,12 +502,6 @@ class TestCompletionMetaInfo:
def test_contains(self, metainfo):
assert 'excluded_patterns' in metainfo
- def test_delete_old_key(self, monkeypatch, metainfo):
- metainfo.insert({'key': 'force_rebuild', 'value': False})
- info2 = history.CompletionMetaInfo()
- monkeypatch.setitem(info2.KEYS, 'force_rebuild', False)
- assert 'force_rebuild' not in info2
-
def test_modify(self, metainfo):
assert not metainfo['excluded_patterns']
value = 'https://example.com/'
diff --git a/tests/unit/browser/test_inspector.py b/tests/unit/browser/test_inspector.py
index cb76a18a6..f7f532050 100644
--- a/tests/unit/browser/test_inspector.py
+++ b/tests/unit/browser/test_inspector.py
@@ -146,9 +146,9 @@ def test_detach_after_toggling(hidden_again, needs_recreate,
if needs_recreate:
fake_inspector.needs_recreate = True
- with qtbot.waitSignal(fake_inspector.recreate):
+ with qtbot.wait_signal(fake_inspector.recreate):
fake_inspector.set_position(inspector.Position.window)
else:
- with qtbot.assertNotEmitted(fake_inspector.recreate):
+ with qtbot.assert_not_emitted(fake_inspector.recreate):
fake_inspector.set_position(inspector.Position.window)
assert fake_inspector.isVisible() and fake_inspector.isWindow()
diff --git a/tests/unit/browser/webengine/test_darkmode.py b/tests/unit/browser/webengine/test_darkmode.py
index 743f2ab1a..3f5272dab 100644
--- a/tests/unit/browser/webengine/test_darkmode.py
+++ b/tests/unit/browser/webengine/test_darkmode.py
@@ -21,10 +21,10 @@ import logging
import pytest
from qutebrowser.config import configdata
-from qutebrowser.utils import usertypes, version
+from qutebrowser.utils import usertypes, version, utils
from qutebrowser.browser.webengine import darkmode
from qutebrowser.misc import objects
-from helpers import utils
+from helpers import testutils
@pytest.fixture(autouse=True)
@@ -32,24 +32,63 @@ def patch_backend(monkeypatch):
monkeypatch.setattr(objects, 'backend', usertypes.Backend.QtWebEngine)
-@pytest.mark.parametrize('qversion, enabled, expected', [
- # Disabled or nothing set
- ("5.14", False, []),
- ("5.15.0", False, []),
- ("5.15.1", False, []),
- ("5.15.2", False, []),
-
- # Enabled in configuration
- ("5.14", True, []),
- ("5.15.0", True, []),
- ("5.15.1", True, []),
- ("5.15.2", True, [("preferredColorScheme", "1")]),
+@pytest.fixture
+def gentoo_versions():
+ return version.WebEngineVersions(
+ webengine=utils.VersionNumber(5, 15, 2),
+ chromium='87.0.4280.144',
+ source='faked',
+ )
+
+
+@pytest.mark.parametrize('value, webengine_version, expected', [
+ # Auto
+ ("auto", "5.14", []),
+ ("auto", "5.15.0", []),
+ ("auto", "5.15.1", []),
+ ("auto", "5.15.2", [("preferredColorScheme", "2")]), # QTBUG-89753
+ ("auto", "5.15.3", []),
+ ("auto", "6.0.0", []),
+
+ # Unset
+ (None, "5.14", []),
+ (None, "5.15.0", []),
+ (None, "5.15.1", []),
+ (None, "5.15.2", [("preferredColorScheme", "2")]), # QTBUG-89753
+ (None, "5.15.3", []),
+ (None, "6.0.0", []),
+
+ # Dark
+ ("dark", "5.14", []),
+ ("dark", "5.15.0", []),
+ ("dark", "5.15.1", []),
+ ("dark", "5.15.2", [("preferredColorScheme", "1")]),
+ ("dark", "5.15.3", [("preferredColorScheme", "0")]),
+ ("dark", "6.0.0", [("preferredColorScheme", "0")]),
+
+ # Light
+ ("light", "5.14", []),
+ ("light", "5.15.0", []),
+ ("light", "5.15.1", []),
+ ("light", "5.15.2", [("preferredColorScheme", "2")]),
+ ("light", "5.15.3", [("preferredColorScheme", "1")]),
+ ("light", "6.0.0", [("preferredColorScheme", "1")]),
])
-@utils.qt514
-def test_colorscheme(config_stub, monkeypatch, qversion, enabled, expected):
- monkeypatch.setattr(darkmode.qtutils, 'qVersion', lambda: qversion)
- config_stub.val.colors.webpage.prefers_color_scheme_dark = enabled
- assert list(darkmode.settings()) == expected
+@testutils.qt514
+def test_colorscheme(config_stub, value, webengine_version, expected):
+ versions = version.WebEngineVersions.from_pyqt(webengine_version)
+ if value is not None:
+ config_stub.val.colors.webpage.preferred_color_scheme = value
+
+ darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
+ assert darkmode_settings['blink-settings'] == expected
+
+
+@testutils.qt514
+def test_colorscheme_gentoo_workaround(config_stub, gentoo_versions):
+ config_stub.val.colors.webpage.preferred_color_scheme = "dark"
+ darkmode_settings = darkmode.settings(versions=gentoo_versions, special_flags=[])
+ assert darkmode_settings['blink-settings'] == [("preferredColorScheme", "0")]
@pytest.mark.parametrize('settings, expected', [
@@ -57,57 +96,69 @@ def test_colorscheme(config_stub, monkeypatch, qversion, enabled, expected):
({}, []),
# Enabled without customization
- ({'enabled': True}, [('forceDarkModeEnabled', 'true')]),
+ ({'enabled': True}, [('darkModeEnabled', 'true')]),
# Algorithm
(
{'enabled': True, 'algorithm': 'brightness-rgb'},
[
- ('forceDarkModeEnabled', 'true'),
- ('forceDarkModeInversionAlgorithm', '2')
+ ('darkModeEnabled', 'true'),
+ ('darkModeInversionAlgorithm', '2')
],
),
])
-def test_basics(config_stub, monkeypatch, settings, expected):
+def test_basics(config_stub, settings, expected):
for k, v in settings.items():
config_stub.set_obj('colors.webpage.darkmode.' + k, v)
- monkeypatch.setattr(darkmode, '_variant',
- lambda: darkmode.Variant.qt_515_2)
if expected:
- expected.append(('forceDarkModeImagePolicy', '2'))
+ expected.append(('darkModeImagePolicy', '2'))
- assert list(darkmode.settings()) == expected
+ # Using Qt 5.15.1 because it has the least special cases.
+ versions = version.WebEngineVersions.from_pyqt('5.15.1')
+ darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
+ assert darkmode_settings['blink-settings'] == expected
-QT_514_SETTINGS = [
+QT_514_SETTINGS = {'blink-settings': [
('darkMode', '2'),
('darkModeImagePolicy', '2'),
('darkModeGrayscale', 'true'),
-]
+]}
-QT_515_0_SETTINGS = [
+QT_515_0_SETTINGS = {'blink-settings': [
('darkModeEnabled', 'true'),
('darkModeInversionAlgorithm', '2'),
('darkModeGrayscale', 'true'),
-]
+]}
-QT_515_1_SETTINGS = [
+QT_515_1_SETTINGS = {'blink-settings': [
('darkModeEnabled', 'true'),
('darkModeInversionAlgorithm', '2'),
('darkModeImagePolicy', '2'),
('darkModeGrayscale', 'true'),
-]
+]}
-QT_515_2_SETTINGS = [
+QT_515_2_SETTINGS = {'blink-settings': [
+ ('preferredColorScheme', '2'), # QTBUG-89753
('forceDarkModeEnabled', 'true'),
('forceDarkModeInversionAlgorithm', '2'),
('forceDarkModeImagePolicy', '2'),
('forceDarkModeGrayscale', 'true'),
-]
+]}
+
+
+QT_515_3_SETTINGS = {
+ 'blink-settings': [('forceDarkModeEnabled', 'true')],
+ 'dark-mode-settings': [
+ ('InversionAlgorithm', '1'),
+ ('ImagePolicy', '2'),
+ ('IsGrayScale', 'true'),
+ ],
+}
@pytest.mark.parametrize('qversion, expected', [
@@ -119,16 +170,9 @@ QT_515_2_SETTINGS = [
('5.15.1', QT_515_1_SETTINGS),
('5.15.2', QT_515_2_SETTINGS),
+ ('5.15.3', QT_515_3_SETTINGS),
])
-def test_qt_version_differences(config_stub, monkeypatch, qversion, expected):
- monkeypatch.setattr(darkmode.qtutils, 'qVersion', lambda: qversion)
-
- major, minor, patch = [int(part) for part in qversion.split('.')]
- hexversion = major << 16 | minor << 8 | patch
- if major > 5 or minor >= 13:
- # Added in Qt 5.13
- monkeypatch.setattr(darkmode, 'PYQT_WEBENGINE_VERSION', hexversion)
-
+def test_qt_version_differences(config_stub, qversion, expected):
settings = {
'enabled': True,
'algorithm': 'brightness-rgb',
@@ -137,10 +181,12 @@ def test_qt_version_differences(config_stub, monkeypatch, qversion, expected):
for k, v in settings.items():
config_stub.set_obj('colors.webpage.darkmode.' + k, v)
- assert list(darkmode.settings()) == expected
+ versions = version.WebEngineVersions.from_pyqt(qversion)
+ darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
+ assert darkmode_settings == expected
-@utils.qt514
+@testutils.qt514
@pytest.mark.parametrize('setting, value, exp_key, exp_val', [
('contrast', -0.5,
'Contrast', '-0.5'),
@@ -157,36 +203,37 @@ def test_qt_version_differences(config_stub, monkeypatch, qversion, expected):
('grayscale.images', 0.5,
'ImageGrayscale', '0.5'),
])
-def test_customization(config_stub, monkeypatch, setting, value, exp_key, exp_val):
+def test_customization(config_stub, setting, value, exp_key, exp_val):
config_stub.val.colors.webpage.darkmode.enabled = True
config_stub.set_obj('colors.webpage.darkmode.' + setting, value)
- monkeypatch.setattr(darkmode, '_variant', lambda: darkmode.Variant.qt_515_2)
expected = []
- expected.append(('forceDarkModeEnabled', 'true'))
+ expected.append(('darkModeEnabled', 'true'))
if exp_key != 'ImagePolicy':
- expected.append(('forceDarkModeImagePolicy', '2'))
- expected.append(('forceDarkMode' + exp_key, exp_val))
-
- assert list(darkmode.settings()) == expected
-
+ expected.append(('darkModeImagePolicy', '2'))
+ expected.append(('darkMode' + exp_key, exp_val))
+
+ versions = version.WebEngineVersions.from_pyqt('5.15.1')
+ darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
+ assert darkmode_settings['blink-settings'] == expected
+
+
+@pytest.mark.parametrize('webengine_version, expected', [
+ ('5.13.0', darkmode.Variant.qt_511_to_513),
+ ('5.14.0', darkmode.Variant.qt_514),
+ ('5.15.0', darkmode.Variant.qt_515_0),
+ ('5.15.1', darkmode.Variant.qt_515_1),
+ ('5.15.2', darkmode.Variant.qt_515_2),
+ ('5.15.3', darkmode.Variant.qt_515_3),
+ ('6.0.0', darkmode.Variant.qt_515_3),
+])
+def test_variant(webengine_version, expected):
+ versions = version.WebEngineVersions.from_pyqt(webengine_version)
+ assert darkmode._variant(versions) == expected
-@pytest.mark.parametrize('qversion, webengine_version, expected', [
- # Without PYQT_WEBENGINE_VERSION
- ('5.12.9', None, darkmode.Variant.qt_511_to_513),
- # With PYQT_WEBENGINE_VERSION
- (None, 0x050d00, darkmode.Variant.qt_511_to_513),
- (None, 0x050e00, darkmode.Variant.qt_514),
- (None, 0x050f00, darkmode.Variant.qt_515_0),
- (None, 0x050f01, darkmode.Variant.qt_515_1),
- (None, 0x050f02, darkmode.Variant.qt_515_2),
- (None, 0x060000, darkmode.Variant.qt_515_2), # Qt 6
-])
-def test_variant(monkeypatch, qversion, webengine_version, expected):
- monkeypatch.setattr(darkmode.qtutils, 'qVersion', lambda: qversion)
- monkeypatch.setattr(darkmode, 'PYQT_WEBENGINE_VERSION', webengine_version)
- assert darkmode._variant() == expected
+def test_variant_gentoo_workaround(gentoo_versions):
+ assert darkmode._variant(gentoo_versions) == darkmode.Variant.qt_515_3
@pytest.mark.parametrize('value, is_valid, expected', [
@@ -194,24 +241,23 @@ def test_variant(monkeypatch, qversion, webengine_version, expected):
('qt_515_2', True, darkmode.Variant.qt_515_2),
])
def test_variant_override(monkeypatch, caplog, value, is_valid, expected):
- monkeypatch.setattr(darkmode.qtutils, 'qVersion', lambda: None)
- monkeypatch.setattr(darkmode, 'PYQT_WEBENGINE_VERSION', 0x050f00)
+ versions = version.WebEngineVersions.from_pyqt('5.15.0')
monkeypatch.setenv('QUTE_DARKMODE_VARIANT', value)
with caplog.at_level(logging.WARNING):
- assert darkmode._variant() == expected
+ assert darkmode._variant(versions) == expected
log_msg = 'Ignoring invalid QUTE_DARKMODE_VARIANT=invalid_value'
assert (log_msg in caplog.messages) != is_valid
-def test_broken_smart_images_policy(config_stub, monkeypatch, caplog):
+def test_broken_smart_images_policy(config_stub, caplog):
config_stub.val.colors.webpage.darkmode.enabled = True
config_stub.val.colors.webpage.darkmode.policy.images = 'smart'
- monkeypatch.setattr(darkmode, 'PYQT_WEBENGINE_VERSION', 0x050f00)
+ versions = version.WebEngineVersions.from_pyqt('5.15.0')
with caplog.at_level(logging.WARNING):
- settings = list(darkmode.settings())
+ darkmode_settings = darkmode.settings(versions=versions, special_flags=[])
assert caplog.messages[-1] == (
'Ignoring colors.webpage.darkmode.policy.images = smart because of '
@@ -221,28 +267,25 @@ def test_broken_smart_images_policy(config_stub, monkeypatch, caplog):
[('darkModeEnabled', 'true')], # Qt 5.15
[('darkMode', '4')], # Qt 5.14
]
- assert settings in expected
-
-
-def test_new_chromium():
- """Fail if we encounter an unknown Chromium version.
-
- Dark mode in Chromium (or rather, the underlying Blink) is being changed with
- almost every Chromium release.
-
- Make this test fail deliberately with newer Chromium versions, so that
- we can test whether dark mode still works manually, and adjust if not.
- """
- assert version._chromium_version() in [
- 'unavailable', # QtWebKit
- '61.0.3163.140', # Qt 5.10
- '65.0.3325.230', # Qt 5.11
- '69.0.3497.128', # Qt 5.12
- '73.0.3683.105', # Qt 5.13
- '77.0.3865.129', # Qt 5.14
- '80.0.3987.163', # Qt 5.15.0
- '83.0.4103.122', # Qt 5.15.2
+ assert darkmode_settings['blink-settings'] in expected
+
+
+@pytest.mark.parametrize('flag, expected', [
+ ('--blink-settings=key=value', [('key', 'value')]),
+ ('--blink-settings=key=equal=rights', [('key', 'equal=rights')]),
+ ('--blink-settings=one=1,two=2', [('one', '1'), ('two', '2')]),
+ ('--enable-features=feat', []),
+])
+def test_pass_through_existing_settings(config_stub, flag, expected):
+ config_stub.val.colors.webpage.darkmode.enabled = True
+ versions = version.WebEngineVersions.from_pyqt('5.15.1')
+ settings = darkmode.settings(versions=versions, special_flags=[flag])
+
+ dark_mode_expected = [
+ ('darkModeEnabled', 'true'),
+ ('darkModeImagePolicy', '2'),
]
+ assert settings['blink-settings'] == expected + dark_mode_expected
def test_options(configdata_init):
diff --git a/tests/unit/browser/webengine/test_webenginedownloads.py b/tests/unit/browser/webengine/test_webenginedownloads.py
index f3fb13f1b..877af3c9a 100644
--- a/tests/unit/browser/webengine/test_webenginedownloads.py
+++ b/tests/unit/browser/webengine/test_webenginedownloads.py
@@ -19,12 +19,13 @@
import os.path
import base64
+import dataclasses
import pytest
pytest.importorskip('PyQt5.QtWebEngineWidgets')
from PyQt5.QtWebEngineWidgets import QWebEngineProfile
-from qutebrowser.utils import urlutils, usertypes
+from qutebrowser.utils import urlutils, usertypes, utils
from qutebrowser.browser.webengine import webenginedownloads
@@ -41,6 +42,15 @@ def test_strip_suffix(path, expected):
assert webenginedownloads._strip_suffix(path) == expected
+@dataclasses.dataclass
+class _ExpectedNames:
+
+ """The filenames used in the tests."""
+
+ before: str
+ after: str
+
+
class TestDataUrlWorkaround:
"""With data URLs, we get rather weird base64 filenames back from QtWebEngine.
@@ -71,6 +81,28 @@ class TestDataUrlWorkaround:
return urlutils.data_url('application/pdf', pdf_bytes)
@pytest.fixture
+ def expected_names(self, webengine_versions, pdf_bytes):
+ """Get the expected filenames before/after the workaround.
+
+ With QtWebEngine 5.15.3, this is handled correctly inside QtWebEngine
+ and we get a qwe_download.pdf instead.
+ """
+ if webengine_versions.webengine >= utils.VersionNumber(5, 15, 3):
+ return _ExpectedNames(before='qwe_download.pdf', after='qwe_download.pdf')
+
+ with_slash = b'% ?' in pdf_bytes
+ base64_data = base64.b64encode(pdf_bytes).decode('ascii')
+
+ if with_slash:
+ assert '/' in base64_data
+ before = base64_data.split('/')[1]
+ else:
+ assert '/' not in base64_data
+ before = 'pdf' # from the mimetype
+
+ return _ExpectedNames(before=before, after='download.pdf')
+
+ @pytest.fixture
def webengine_profile(self, qapp):
profile = QWebEngineProfile.defaultProfile()
profile.setParent(qapp)
@@ -86,13 +118,13 @@ class TestDataUrlWorkaround:
webengine_profile.downloadRequested.disconnect()
def test_workaround(self, webengine_tab, message_mock, qtbot,
- pdf_url, download_manager):
+ pdf_url, download_manager, expected_names):
"""Verify our workaround works properly."""
- with qtbot.waitSignal(message_mock.got_question):
+ with qtbot.wait_signal(message_mock.got_question):
webengine_tab.load_url(pdf_url)
question = message_mock.get_question()
- assert question.default == 'download.pdf'
+ assert question.default == expected_names.after
def test_explicit_filename(self, webengine_tab, message_mock, qtbot,
pdf_url, download_manager):
@@ -100,10 +132,10 @@ class TestDataUrlWorkaround:
pdf_url_str = pdf_url.toDisplayString()
html = f'<a href="{pdf_url_str}" download="filename.pdf" id="link">'
- with qtbot.waitSignal(webengine_tab.load_finished):
+ with qtbot.wait_signal(webengine_tab.load_finished):
webengine_tab.set_html(html)
- with qtbot.waitSignal(message_mock.got_question):
+ with qtbot.wait_signal(message_mock.got_question):
webengine_tab.elements.find_id(
"link",
lambda elem: elem.click(usertypes.ClickTarget.normal),
@@ -112,20 +144,8 @@ class TestDataUrlWorkaround:
question = message_mock.get_question()
assert question.default == 'filename.pdf'
- @pytest.fixture
- def expected_wrong_filename(self, pdf_bytes):
- with_slash = b'% ?' in pdf_bytes
- base64_data = base64.b64encode(pdf_bytes).decode('ascii')
-
- if with_slash:
- assert '/' in base64_data
- return base64_data.split('/')[1]
- else:
- assert '/' not in base64_data
- return 'pdf' # from the mimetype
-
def test_workaround_needed(self, qtbot, webengineview,
- pdf_url, expected_wrong_filename, webengine_profile):
+ pdf_url, expected_names, webengine_profile):
"""Verify that our workaround for this is still needed.
In other words, check whether we get those base64-filenames rather than a
@@ -134,9 +154,9 @@ class TestDataUrlWorkaround:
def check_item(item):
assert item.mimeType() == 'application/pdf'
assert item.url().scheme() == 'data'
- assert os.path.basename(item.path()) == expected_wrong_filename
+ assert os.path.basename(item.path()) == expected_names.before
return True
- with qtbot.waitSignal(webengine_profile.downloadRequested,
+ with qtbot.wait_signal(webengine_profile.downloadRequested,
check_params_cb=check_item):
webengineview.load(pdf_url)
diff --git a/tests/unit/browser/webkit/network/test_filescheme.py b/tests/unit/browser/webkit/network/test_filescheme.py
index 51ab4472c..12cacb5a2 100644
--- a/tests/unit/browser/webkit/network/test_filescheme.py
+++ b/tests/unit/browser/webkit/network/test_filescheme.py
@@ -29,7 +29,7 @@ from PyQt5.QtNetwork import QNetworkRequest
from qutebrowser.browser.webkit.network import filescheme
from qutebrowser.utils import urlutils, utils
-from helpers import utils as testutils
+from helpers import testutils
@pytest.mark.parametrize('create_file, create_dir, filterfunc, expected', [
diff --git a/tests/unit/browser/webkit/network/test_networkreply.py b/tests/unit/browser/webkit/network/test_networkreply.py
index b01fe6c5b..3cffb2fd7 100644
--- a/tests/unit/browser/webkit/network/test_networkreply.py
+++ b/tests/unit/browser/webkit/network/test_networkreply.py
@@ -52,7 +52,7 @@ class TestFixedDataNetworkReply:
b'Hello World! This is a test.'])
def test_data(self, qtbot, req, data):
reply = networkreply.FixedDataNetworkReply(req, data, 'test/foo')
- with qtbot.waitSignals([reply.metaDataChanged, reply.readyRead,
+ with qtbot.wait_signals([reply.metaDataChanged, reply.readyRead,
reply.finished], order='strict'):
pass
@@ -78,7 +78,7 @@ def test_error_network_reply(qtbot, req):
reply = networkreply.ErrorNetworkReply(
req, "This is an error", QNetworkReply.UnknownNetworkError)
- with qtbot.waitSignals([reply.error, reply.finished], order='strict'):
+ with qtbot.wait_signals([reply.error, reply.finished], order='strict'):
pass
reply.abort() # shouldn't do anything
diff --git a/tests/unit/browser/webkit/test_cookies.py b/tests/unit/browser/webkit/test_cookies.py
index 0c30c44df..81da561ce 100644
--- a/tests/unit/browser/webkit/test_cookies.py
+++ b/tests/unit/browser/webkit/test_cookies.py
@@ -85,7 +85,7 @@ class TestSetCookies:
"""Test setCookiesFromUrl with cookies enabled."""
config_stub.val.content.cookies.accept = 'all'
- with qtbot.waitSignal(ram_jar.changed):
+ with qtbot.wait_signal(ram_jar.changed):
assert ram_jar.setCookiesFromUrl([cookie], url)
# assert the cookies are added correctly
@@ -100,7 +100,7 @@ class TestSetCookies:
"""Test setCookiesFromUrl when cookies are not accepted."""
config_stub.val.content.cookies.accept = 'never'
- with qtbot.assertNotEmitted(ram_jar.changed):
+ with qtbot.assert_not_emitted(ram_jar.changed):
assert not ram_jar.setCookiesFromUrl([cookie], url)
assert not ram_jar.cookiesForUrl(url)
@@ -112,11 +112,11 @@ class TestSetCookies:
org_url = QUrl('http://example.org/')
- with qtbot.waitSignal(ram_jar.changed):
+ with qtbot.wait_signal(ram_jar.changed):
assert ram_jar.setCookiesFromUrl([cookie], org_url)
assert ram_jar.cookiesForUrl(org_url)
- with qtbot.assertNotEmitted(ram_jar.changed):
+ with qtbot.assert_not_emitted(ram_jar.changed):
assert not ram_jar.setCookiesFromUrl([cookie], url)
assert not ram_jar.cookiesForUrl(url)
@@ -175,7 +175,7 @@ def test_cookies_changed_emit(config_stub, fake_save_manager,
monkeypatch.setattr(lineparser, 'LineParser', LineparserSaveStub)
jar = cookies.CookieJar()
- with qtbot.waitSignal(jar.changed):
+ with qtbot.wait_signal(jar.changed):
config_stub.val.content.cookies.store = False
diff --git a/tests/unit/commands/test_userscripts.py b/tests/unit/commands/test_userscripts.py
index 6758bdd08..48bc31c32 100644
--- a/tests/unit/commands/test_userscripts.py
+++ b/tests/unit/commands/test_userscripts.py
@@ -44,7 +44,7 @@ class TestQtFIFOReader:
def test_single_line(self, reader, qtbot):
"""Test QSocketNotifier with a single line of data."""
- with qtbot.waitSignal(reader.got_line) as blocker:
+ with qtbot.wait_signal(reader.got_line) as blocker:
with open(reader._filepath, 'w', encoding='utf-8') as f:
f.write('foobar\n')
@@ -74,8 +74,8 @@ def test_command(qtbot, py_proc, runner):
with open(os.environ['QUTE_FIFO'], 'w') as f:
f.write('foo\n')
""")
- with qtbot.waitSignal(runner.finished, timeout=10000):
- with qtbot.waitSignal(runner.got_cmd, timeout=10000) as blocker:
+ with qtbot.wait_signal(runner.finished, timeout=10000):
+ with qtbot.wait_signal(runner.got_cmd, timeout=10000) as blocker:
runner.prepare_run(cmd, *args)
runner.store_html('')
runner.store_text('')
@@ -97,8 +97,8 @@ def test_custom_env(qtbot, monkeypatch, py_proc, runner):
f.write('\n')
""")
- with qtbot.waitSignal(runner.finished, timeout=10000):
- with qtbot.waitSignal(runner.got_cmd, timeout=10000) as blocker:
+ with qtbot.wait_signal(runner.finished, timeout=10000):
+ with qtbot.wait_signal(runner.got_cmd, timeout=10000) as blocker:
runner.prepare_run(cmd, *args, env=env)
runner.store_html('')
runner.store_text('')
@@ -131,8 +131,8 @@ def test_source(qtbot, py_proc, runner):
f.write('\n')
""")
- with qtbot.waitSignal(runner.finished, timeout=10000):
- with qtbot.waitSignal(runner.got_cmd, timeout=10000) as blocker:
+ with qtbot.wait_signal(runner.finished, timeout=10000):
+ with qtbot.wait_signal(runner.got_cmd, timeout=10000) as blocker:
runner.prepare_run(cmd, *args)
runner.store_html('This is HTML')
runner.store_text('This is text')
@@ -158,8 +158,8 @@ def test_command_with_error(qtbot, py_proc, runner, caplog):
""")
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(runner.finished, timeout=10000):
- with qtbot.waitSignal(runner.got_cmd, timeout=10000) as blocker:
+ with qtbot.wait_signal(runner.finished, timeout=10000):
+ with qtbot.wait_signal(runner.got_cmd, timeout=10000) as blocker:
runner.prepare_run(cmd, *args)
runner.store_text('Hello World')
runner.store_html('')
@@ -195,7 +195,7 @@ def test_killed_command(qtbot, tmpdir, py_proc, runner, caplog):
""")
args.append(str(data_file))
- with qtbot.waitSignal(watcher.directoryChanged, timeout=10000):
+ with qtbot.wait_signal(watcher.directoryChanged, timeout=10000):
runner.prepare_run(cmd, *args)
runner.store_text('Hello World')
runner.store_html('')
@@ -206,7 +206,7 @@ def test_killed_command(qtbot, tmpdir, py_proc, runner, caplog):
data = json.load(data_file)
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(runner.finished):
+ with qtbot.wait_signal(runner.finished):
os.kill(int(data['pid']), signal.SIGTERM)
assert not os.path.exists(data['text_file'])
@@ -220,7 +220,7 @@ def test_temporary_files_failed_cleanup(caplog, qtbot, py_proc, runner):
""")
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(runner.finished, timeout=10000):
+ with qtbot.wait_signal(runner.finished, timeout=10000):
runner.prepare_run(cmd, *args)
runner.store_text('')
runner.store_html('')
@@ -237,7 +237,7 @@ def test_unicode_error(caplog, qtbot, py_proc, runner):
f.write(b'\x80')
""")
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(runner.finished, timeout=10000):
+ with qtbot.wait_signal(runner.finished, timeout=10000):
runner.prepare_run(cmd, *args)
runner.store_text('')
runner.store_html('')
diff --git a/tests/unit/completion/test_completiondelegate.py b/tests/unit/completion/test_completiondelegate.py
index 5b2e519ea..ad081ccbf 100644
--- a/tests/unit/completion/test_completiondelegate.py
+++ b/tests/unit/completion/test_completiondelegate.py
@@ -92,7 +92,7 @@ def test_highlighted(qtbot):
# Needed so the highlighting actually works.
edit = QTextEdit()
- qtbot.addWidget(edit)
+ qtbot.add_widget(edit)
edit.setDocument(doc)
colors = [f.foreground().color() for f in doc.allFormats()]
diff --git a/tests/unit/completion/test_completionmodel.py b/tests/unit/completion/test_completionmodel.py
index e2d55082e..2130f1f1c 100644
--- a/tests/unit/completion/test_completionmodel.py
+++ b/tests/unit/completion/test_completionmodel.py
@@ -77,7 +77,7 @@ def test_set_pattern(pat, qtbot):
for c in cats:
c.set_pattern = mock.Mock(spec=[])
model.add_category(c)
- with qtbot.waitSignals([model.layoutAboutToBeChanged, model.layoutChanged],
+ with qtbot.wait_signals([model.layoutAboutToBeChanged, model.layoutChanged],
order='strict'):
model.set_pattern(pat)
for c in cats:
diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py
index 494531ff4..89390cbf1 100644
--- a/tests/unit/completion/test_completionwidget.py
+++ b/tests/unit/completion/test_completionwidget.py
@@ -39,7 +39,7 @@ def completionview(qtbot, status_command_stub, config_stub, win_registry,
'qutebrowser.completion.completiondelegate.CompletionItemDelegate',
new=lambda *_: None)
view = completionwidget.CompletionView(cmd=status_command_stub, win_id=0)
- qtbot.addWidget(view)
+ qtbot.add_widget(view)
return view
@@ -73,10 +73,10 @@ def test_set_pattern_no_model(completionview):
def test_maybe_update_geometry(completionview, config_stub, qtbot):
"""Ensure completion is resized only if shrink is True."""
- with qtbot.assertNotEmitted(completionview.update_geometry):
+ with qtbot.assert_not_emitted(completionview.update_geometry):
completionview._maybe_update_geometry()
config_stub.val.completion.shrink = True
- with qtbot.waitSignal(completionview.update_geometry):
+ with qtbot.wait_signal(completionview.update_geometry):
completionview._maybe_update_geometry()
@@ -137,10 +137,10 @@ def test_completion_item_focus(which, tree, expected, completionview, model, qtb
completionview.set_model(model)
for entry in expected:
if entry is None:
- with qtbot.assertNotEmitted(completionview.selection_changed):
+ with qtbot.assert_not_emitted(completionview.selection_changed):
completionview.completion_item_focus(which)
else:
- with qtbot.waitSignal(completionview.selection_changed) as sig:
+ with qtbot.wait_signal(completionview.selection_changed) as sig:
completionview.completion_item_focus(which)
assert sig.args == [entry]
@@ -153,11 +153,11 @@ def test_completion_item_focus_no_model(which, completionview, model, qtbot):
Validates #1812: help completion repeatedly completes
"""
- with qtbot.assertNotEmitted(completionview.selection_changed):
+ with qtbot.assert_not_emitted(completionview.selection_changed):
completionview.completion_item_focus(which)
completionview.set_model(model)
completionview.set_model(None)
- with qtbot.assertNotEmitted(completionview.selection_changed):
+ with qtbot.assert_not_emitted(completionview.selection_changed):
completionview.completion_item_focus(which)
@@ -214,7 +214,7 @@ class TestCompletionItemFocusPage:
cat = listcategory.ListCategory('Test', items)
model.add_category(cat)
completionview.set_model(model)
- with qtbot.waitSignal(completionview.selection_changed) as blocker:
+ with qtbot.wait_signal(completionview.selection_changed) as blocker:
completionview.completion_item_focus(which)
assert blocker.args == [expected]
@@ -259,7 +259,7 @@ class TestCompletionItemFocusPage:
for move, item in steps:
print('{:9} -> expecting {}'.format(move, item))
- with qtbot.waitSignal(completionview.selection_changed) as blocker:
+ with qtbot.wait_signal(completionview.selection_changed) as blocker:
completionview.completion_item_focus(move)
assert blocker.args == [item]
@@ -273,7 +273,7 @@ class TestCompletionItemFocusPage:
completionview.set_model(model)
for move, item in [('next', 'Item 1'), ('next-page', 'Target item')]:
- with qtbot.waitSignal(completionview.selection_changed) as blocker:
+ with qtbot.wait_signal(completionview.selection_changed) as blocker:
completionview.completion_item_focus(move)
assert blocker.args == [item]
diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py
index 8a6b24557..22e9c6490 100644
--- a/tests/unit/completion/test_models.py
+++ b/tests/unit/completion/test_models.py
@@ -28,7 +28,7 @@ from datetime import datetime
from unittest import mock
import hypothesis
-import hypothesis.strategies
+import hypothesis.strategies as hst
import pytest
from PyQt5.QtCore import QUrl, QDateTime
try:
@@ -459,9 +459,10 @@ def test_filesystem_completion_model_interface(info, local_files_path):
@hypothesis.given(
- as_uri=hypothesis.strategies.booleans(),
- add_sep=hypothesis.strategies.booleans(),
- text=hypothesis.strategies.text(),
+ as_uri=hst.booleans(),
+ add_sep=hst.booleans(),
+ text=hst.text(alphabet=hst.characters(
+ blacklist_categories=['Cc'], blacklist_characters='\x00')),
)
def test_filesystem_completion_hypothesis(info, as_uri, add_sep, text):
if as_uri:
@@ -1445,7 +1446,7 @@ def undo_completion_retains_sort_order(tabbed_browser_stubs, info):
_check_completions(model, {"Closed tabs": expected})
-@hypothesis.given(text=hypothesis.strategies.text())
+@hypothesis.given(text=hst.text())
def test_listcategory_hypothesis(text):
"""Make sure we can't produce invalid patterns."""
cat = listcategory.ListCategory("test", [])
diff --git a/tests/unit/components/test_blockutils.py b/tests/unit/components/test_blockutils.py
index 205dd7fc1..f206f3b6a 100644
--- a/tests/unit/components/test_blockutils.py
+++ b/tests/unit/components/test_blockutils.py
@@ -76,7 +76,7 @@ def test_blocklist_dl(qtbot, pretend_blocklists):
dl = blockutils.BlocklistDownloads(list_qurls)
dl.single_download_finished.connect(on_single_download)
- with qtbot.waitSignal(dl.all_downloads_finished) as blocker:
+ with qtbot.wait_signal(dl.all_downloads_finished) as blocker:
dl.initiate()
assert blocker.args == [total_expected]
diff --git a/tests/unit/components/test_braveadblock.py b/tests/unit/components/test_braveadblock.py
index 0afb7499e..3b99fbd38 100644
--- a/tests/unit/components/test_braveadblock.py
+++ b/tests/unit/components/test_braveadblock.py
@@ -30,7 +30,7 @@ import pytest
from qutebrowser.api.interceptor import ResourceType
from qutebrowser.components import braveadblock
from qutebrowser.components.utils import blockutils
-from helpers import utils
+from helpers import testutils
pytestmark = pytest.mark.usefixtures("qapp")
@@ -107,7 +107,7 @@ def run_function_on_dataset(given_function):
contains tuples of (url, source_url, type) in each line. We give these
to values to the given function, row by row.
"""
- dataset = utils.adblock_dataset_tsv()
+ dataset = testutils.adblock_dataset_tsv()
reader = csv.DictReader(dataset, delimiter="\t")
for row in reader:
url = QUrl(row["url"])
@@ -144,8 +144,8 @@ def easylist_easyprivacy_both(tmpdir):
bl_dst_dir.mkdir()
urls = []
for blocklist, filename in [
- (utils.easylist_txt(), "easylist.txt"),
- (utils.easyprivacy_txt(), "easyprivacy.txt"),
+ (testutils.easylist_txt(), "easylist.txt"),
+ (testutils.easyprivacy_txt(), "easyprivacy.txt"),
]:
bl_dst_path = bl_dst_dir / filename
with open(bl_dst_path, "w", encoding="utf-8") as f:
diff --git a/tests/unit/components/test_hostblock.py b/tests/unit/components/test_hostblock.py
index 3869aba66..876be1c53 100644
--- a/tests/unit/components/test_hostblock.py
+++ b/tests/unit/components/test_hostblock.py
@@ -29,7 +29,7 @@ from PyQt5.QtCore import QUrl
from qutebrowser.components import hostblock
from qutebrowser.utils import urlmatch
-from helpers import utils
+from helpers import testutils
pytestmark = pytest.mark.usefixtures("qapp")
@@ -557,7 +557,7 @@ def test_add_directory(config_stub, tmpdir, host_blocker_factory):
def test_adblock_benchmark(data_tmpdir, benchmark, host_blocker_factory):
blocked_hosts = data_tmpdir / "blocked-hosts"
- blocked_hosts.write_text("\n".join(utils.blocked_hosts()), encoding="utf-8")
+ blocked_hosts.write_text("\n".join(testutils.blocked_hosts()), encoding="utf-8")
url = QUrl("https://www.example.org/")
blocker = host_blocker_factory()
diff --git a/tests/unit/config/test_config.py b/tests/unit/config/test_config.py
index 7f35af024..8a9d8154d 100644
--- a/tests/unit/config/test_config.py
+++ b/tests/unit/config/test_config.py
@@ -432,7 +432,7 @@ class TestConfig:
assert conf.get_obj(name1) == 'never'
assert conf.get_obj(name2) is True
- with qtbot.waitSignals([conf.changed, conf.changed]) as blocker:
+ with qtbot.wait_signals([conf.changed, conf.changed]) as blocker:
conf.clear(save_yaml=save_yaml)
options = {e.args[0] for e in blocker.all_signals_and_args}
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
index fdd12d308..255ea8acc 100644
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -617,6 +617,24 @@ class TestYamlMigrations:
def test_title_format(self, migration_test, setting, old, new):
migration_test(setting, old, new)
+ @pytest.mark.parametrize('setting', [
+ 'colors.webpage.force_dark_color_scheme',
+ 'colors.webpage.prefers_color_scheme_dark',
+ ])
+ @pytest.mark.parametrize('old, new', [
+ (True, 'dark'),
+ (False, 'auto'),
+ ])
+ def test_preferred_color_scheme(self, autoconfig, yaml, setting, old, new):
+ autoconfig.write({setting: {'global': old}})
+
+ yaml.load()
+ yaml._save()
+
+ data = autoconfig.read()
+ assert setting not in data
+ assert data['colors.webpage.preferred_color_scheme']['global'] == new
+
@pytest.mark.parametrize('old, new', [
(None, ('Mozilla/5.0 ({os_info}) '
'AppleWebKit/{webkit_version} (KHTML, like Gecko) '
@@ -729,22 +747,28 @@ class ConfPy:
def __init__(self, tmpdir, filename: str = "config.py"):
self._file = tmpdir / filename
self.filename = str(self._file)
- config.instance.warn_autoconfig = False
def write(self, *lines):
text = '\n'.join(lines)
self._file.write_text(text, 'utf-8', ensure=True)
- def read(self, error=False):
+ def read(self, error=False, warn_autoconfig=False):
"""Read the config.py via configfiles and check for errors."""
if error:
with pytest.raises(configexc.ConfigFileErrors) as excinfo:
- configfiles.read_config_py(self.filename)
+ configfiles.read_config_py(
+ self.filename,
+ warn_autoconfig=warn_autoconfig,
+ )
errors = excinfo.value.errors
assert len(errors) == 1
return errors[0]
else:
- configfiles.read_config_py(self.filename, raising=True)
+ configfiles.read_config_py(
+ self.filename,
+ raising=True,
+ warn_autoconfig=warn_autoconfig,
+ )
return None
def write_qbmodule(self):
@@ -1025,9 +1049,8 @@ class TestConfigPy:
def test_load_autoconfig_warning(self, confpy):
confpy.write('')
- config.instance.warn_autoconfig = True
with pytest.raises(configexc.ConfigFileErrors) as excinfo:
- configfiles.read_config_py(confpy.filename)
+ configfiles.read_config_py(confpy.filename, warn_autoconfig=True)
assert len(excinfo.value.errors) == 1
error = excinfo.value.errors[0]
assert error.text == "autoconfig loading not specified"
@@ -1194,6 +1217,21 @@ class TestConfigPy:
assert error.text == "Error while reading doesnotexist.py"
assert isinstance(error.exception, FileNotFoundError)
+ @pytest.mark.parametrize('reverse', [True, False])
+ def test_source_warn_autoconfig(self, tmpdir, confpy, reverse):
+ subfile = tmpdir / 'config' / 'subfile.py'
+ subfile.write_text("c.content.javascript.enabled = False",
+ encoding='utf-8')
+ lines = [
+ "config.source('subfile.py')",
+ "config.load_autoconfig(False)",
+ ]
+ if reverse:
+ lines.reverse()
+
+ confpy.write(*lines)
+ confpy.read(warn_autoconfig=True)
+
class TestConfigPyWriter:
diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py
index 4a7788874..1945f7f8a 100644
--- a/tests/unit/config/test_configinit.py
+++ b/tests/unit/config/test_configinit.py
@@ -204,6 +204,19 @@ class TestEarlyInit:
assert dump == '\n'.join(expected)
+ def test_autoconfig_warning(self, init_patch, args, config_tmpdir, caplog):
+ """Test the warning shown for missing autoconfig loading."""
+ config_py_file = config_tmpdir / 'config.py'
+ config_py_file.ensure()
+
+ with caplog.at_level(logging.ERROR):
+ configinit.early_init(args)
+
+ # Check error messages
+ assert len(configinit._init_errors.errors) == 1
+ error = configinit._init_errors.errors[0]
+ assert str(error).startswith("autoconfig loading not specified")
+
@pytest.mark.parametrize('byte', [
b'\x00', # configparser.Error
b'\xda', # UnicodeDecodeError
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index 493cf0ace..1a0a9cb43 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -39,7 +39,7 @@ from qutebrowser.config import configtypes, configexc
from qutebrowser.utils import debug, utils, qtutils, urlmatch, usertypes
from qutebrowser.browser.network import pac
from qutebrowser.keyinput import keyutils
-from helpers import utils as testutils
+from helpers import testutils
class Font(QFont):
diff --git a/tests/unit/config/test_configutils.py b/tests/unit/config/test_configutils.py
index f7cefec31..4d3082a92 100644
--- a/tests/unit/config/test_configutils.py
+++ b/tests/unit/config/test_configutils.py
@@ -25,7 +25,7 @@ from PyQt5.QtWidgets import QLabel
from qutebrowser.config import configutils, configdata, configtypes, configexc
from qutebrowser.utils import urlmatch, usertypes, qtutils
-from tests.helpers import utils
+from tests.helpers import testutils
@pytest.fixture
@@ -277,7 +277,7 @@ def test_no_pattern_support(func, opt, pattern):
def test_add_url_benchmark(values, benchmark):
- blocked_hosts = list(utils.blocked_hosts())
+ blocked_hosts = list(testutils.blocked_hosts())
def _add_blocked():
for line in blocked_hosts:
@@ -294,7 +294,7 @@ def test_add_url_benchmark(values, benchmark):
def test_domain_lookup_sparse_benchmark(url, values, benchmark):
url = QUrl(url)
values.add(False, urlmatch.UrlPattern("*.foo.bar.baz"))
- for line in utils.blocked_hosts():
+ for line in testutils.blocked_hosts():
values.add(False, urlmatch.UrlPattern(line))
benchmark(lambda: values.get_for_url(url))
@@ -382,7 +382,7 @@ class TestFontFamilies:
print(stylesheet)
label.setStyleSheet(stylesheet)
- with qtbot.waitExposed(label):
+ with qtbot.wait_exposed(label):
# Needed so the font gets calculated
label.show()
info = label.fontInfo()
@@ -396,7 +396,7 @@ class TestFontFamilies:
qtbot.add_widget(label)
fallback_label.setText("fallback")
- with qtbot.waitExposed(fallback_label):
+ with qtbot.wait_exposed(fallback_label):
# Needed so the font gets calculated
fallback_label.show()
diff --git a/tests/unit/config/test_qtargs.py b/tests/unit/config/test_qtargs.py
index be27274e5..e7dbd5d95 100644
--- a/tests/unit/config/test_qtargs.py
+++ b/tests/unit/config/test_qtargs.py
@@ -24,27 +24,42 @@ import pytest
from qutebrowser import qutebrowser
from qutebrowser.config import qtargs
-from qutebrowser.utils import usertypes
-from helpers import utils
+from qutebrowser.utils import usertypes, version
+from helpers import testutils
-class TestQtArgs:
+@pytest.fixture
+def parser(mocker):
+ """Fixture to provide an argparser.
- @pytest.fixture
- def parser(self, mocker):
- """Fixture to provide an argparser.
+ Monkey-patches .exit() of the argparser so it doesn't exit on errors.
+ """
+ parser = qutebrowser.get_argparser()
+ mocker.patch.object(parser, 'exit', side_effect=Exception)
+ return parser
- Monkey-patches .exit() of the argparser so it doesn't exit on errors.
- """
- parser = qutebrowser.get_argparser()
- mocker.patch.object(parser, 'exit', side_effect=Exception)
- return parser
- @pytest.fixture(autouse=True)
- def reduce_args(self, monkeypatch, config_stub):
- """Make sure no --disable-shared-workers/referer argument get added."""
- monkeypatch.setattr(qtargs.qtutils, 'qVersion', lambda: '5.15.0')
- config_stub.val.content.headers.referer = 'always'
+@pytest.fixture
+def version_patcher(monkeypatch):
+ """Get a patching function to patch the QtWebEngine version."""
+ def run(ver):
+ versions = version.WebEngineVersions.from_pyqt(ver)
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(version, 'qtwebengine_versions',
+ lambda avoid_init: versions)
+
+ return run
+
+
+@pytest.fixture
+def reduce_args(config_stub, version_patcher):
+ """Make sure no --disable-shared-workers/referer argument get added."""
+ version_patcher('5.15.0')
+ config_stub.val.content.headers.referer = 'always'
+
+
+@pytest.mark.usefixtures('reduce_args')
+class TestQtArgs:
@pytest.mark.parametrize('args, expected', [
# No Qt arguments
@@ -89,35 +104,65 @@ class TestQtArgs:
for arg in ['--foo', '--bar']:
assert arg in args
- @pytest.mark.parametrize('backend, expected', [
- (usertypes.Backend.QtWebEngine, True),
- (usertypes.Backend.QtWebKit, False),
+
+def test_no_webengine_available(monkeypatch, config_stub, parser, stubs):
+ """Test that we don't fail if QtWebEngine is requested but unavailable.
+
+ Note this is not inside TestQtArgs because we don't want the reduce_args patching
+ here.
+ """
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(qtargs.version, 'webenginesettings', None)
+
+ fake = stubs.ImportFake({'qutebrowser.browser.webengine': False}, monkeypatch)
+ fake.patch()
+
+ parsed = parser.parse_args([])
+ args = qtargs.qt_args(parsed)
+ assert args == [sys.argv[0]]
+
+
+@pytest.mark.usefixtures('reduce_args')
+class TestWebEngineArgs:
+
+ @pytest.fixture(autouse=True)
+ def ensure_webengine(self):
+ """Skip all tests if QtWebEngine is unavailable."""
+ pytest.importorskip("PyQt5.QtWebEngine")
+
+ @pytest.mark.parametrize('backend, qt_version, expected', [
+ (usertypes.Backend.QtWebEngine, '5.13.0', False),
+ (usertypes.Backend.QtWebEngine, '5.14.0', True),
+ (usertypes.Backend.QtWebEngine, '5.14.1', True),
+ (usertypes.Backend.QtWebEngine, '5.15.0', False),
+ (usertypes.Backend.QtWebEngine, '5.15.1', False),
+
+ (usertypes.Backend.QtWebKit, '5.14.0', False),
])
- def test_shared_workers(self, config_stub, monkeypatch, parser,
- backend, expected):
- monkeypatch.setattr(qtargs.qtutils, 'qVersion', lambda: '5.14.0')
+ def test_shared_workers(self, config_stub, version_patcher, monkeypatch, parser,
+ qt_version, backend, expected):
+ version_patcher(qt_version)
monkeypatch.setattr(qtargs.objects, 'backend', backend)
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
assert ('--disable-shared-workers' in args) == expected
- @pytest.mark.parametrize('backend, version_check, debug_flag, expected', [
+ @pytest.mark.parametrize('backend, qt_version, debug_flag, expected', [
# Qt >= 5.12.3: Enable with -D stack, do nothing without it.
- (usertypes.Backend.QtWebEngine, True, True, True),
- (usertypes.Backend.QtWebEngine, True, False, None),
+ (usertypes.Backend.QtWebEngine, '5.12.3', True, True),
+ (usertypes.Backend.QtWebEngine, '5.12.3', False, None),
# Qt < 5.12.3: Do nothing with -D stack, disable without it.
- (usertypes.Backend.QtWebEngine, False, True, None),
- (usertypes.Backend.QtWebEngine, False, False, False),
+ (usertypes.Backend.QtWebEngine, '5.12.2', True, None),
+ (usertypes.Backend.QtWebEngine, '5.12.2', False, False),
# QtWebKit: Do nothing
- (usertypes.Backend.QtWebKit, True, True, None),
- (usertypes.Backend.QtWebKit, True, False, None),
- (usertypes.Backend.QtWebKit, False, True, None),
- (usertypes.Backend.QtWebKit, False, False, None),
+ (usertypes.Backend.QtWebKit, '5.12.3', True, None),
+ (usertypes.Backend.QtWebKit, '5.12.3', False, None),
+ (usertypes.Backend.QtWebKit, '5.12.2', True, None),
+ (usertypes.Backend.QtWebKit, '5.12.2', False, None),
])
- def test_in_process_stack_traces(self, monkeypatch, parser, backend,
- version_check, debug_flag, expected):
- monkeypatch.setattr(qtargs.qtutils, 'version_check',
- lambda version, compiled=False, exact=False: version_check)
+ def test_in_process_stack_traces(self, monkeypatch, parser, backend, version_patcher,
+ qt_version, debug_flag, expected):
+ version_patcher(qt_version)
monkeypatch.setattr(qtargs.objects, 'backend', backend)
parsed = parser.parse_args(['--debug-flag', 'stack'] if debug_flag
else [])
@@ -139,8 +184,7 @@ class TestQtArgs:
(['--debug-flag', 'wait-renderer-process'], ['--renderer-startup-dialog']),
])
def test_chromium_flags(self, monkeypatch, parser, flags, args):
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
parsed = parser.parse_args(flags)
args = qtargs.qt_args(parsed)
@@ -158,10 +202,8 @@ class TestQtArgs:
('software-opengl', False),
('chromium', True),
])
- def test_disable_gpu(self, config, added,
- config_stub, monkeypatch, parser):
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
+ def test_disable_gpu(self, config, added, config_stub, monkeypatch, parser):
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
config_stub.val.qt.force_software_rendering = config
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
@@ -182,10 +224,8 @@ class TestQtArgs:
'--force-webrtc-ip-handling-policy='
'disable_non_proxied_udp'),
])
- def test_webrtc(self, config_stub, monkeypatch, parser,
- policy, arg):
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
+ def test_webrtc(self, config_stub, monkeypatch, parser, policy, arg):
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
config_stub.val.content.webrtc_ip_handling_policy = policy
parsed = parser.parse_args([])
@@ -203,8 +243,7 @@ class TestQtArgs:
])
def test_canvas_reading(self, config_stub, monkeypatch, parser,
canvas_reading, added):
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
config_stub.val.content.canvas_reading = canvas_reading
parsed = parser.parse_args([])
@@ -218,8 +257,7 @@ class TestQtArgs:
])
def test_process_model(self, config_stub, monkeypatch, parser,
process_model, added):
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
config_stub.val.qt.process_model = process_model
parsed = parser.parse_args([])
@@ -240,8 +278,7 @@ class TestQtArgs:
])
def test_low_end_device_mode(self, config_stub, monkeypatch, parser,
low_end_device_mode, arg):
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
config_stub.val.qt.low_end_device_mode = low_end_device_mode
parsed = parser.parse_args([])
@@ -270,9 +307,10 @@ class TestQtArgs:
('5.14.0', 'same-domain', '--enable-features=ReducedReferrerGranularity'),
('5.15.0', 'same-domain', '--enable-features=ReducedReferrerGranularity'),
])
- def test_referer(self, config_stub, monkeypatch, parser, qt_version, referer, arg):
+ def test_referer(self, config_stub, monkeypatch, version_patcher, parser,
+ qt_version, referer, arg):
monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
- monkeypatch.setattr(qtargs.qtutils, 'qVersion', lambda: qt_version)
+ version_patcher(qt_version)
# Avoid WebRTC pipewire feature
monkeypatch.setattr(qtargs.utils, 'is_linux', False)
@@ -290,27 +328,40 @@ class TestQtArgs:
else:
assert arg in args
- @pytest.mark.parametrize('dark, qt_version, added', [
- (True, "5.13", False), # not supported
- (True, "5.14", True),
- (True, "5.15.0", True),
- (True, "5.15.1", True),
- (True, "5.15.2", False), # handled via blink setting
-
- (False, "5.13", False),
- (False, "5.14", False),
- (False, "5.15.0", False),
- (False, "5.15.1", False),
- (False, "5.15.2", False),
+ @pytest.mark.parametrize('value, qt_version, added', [
+ ("dark", "5.13", False), # not supported
+ ("dark", "5.14", True),
+ ("dark", "5.15.0", True),
+ ("dark", "5.15.1", True),
+ # handled via blink setting
+ ("dark", "5.15.2", False),
+ ("dark", "5.15.3", False),
+ ("dark", "6.0.0", False),
+
+ ("light", "5.13", False),
+ ("light", "5.14", False),
+ ("light", "5.15.0", False),
+ ("light", "5.15.1", False),
+ ("light", "5.15.2", False),
+ ("light", "5.15.2", False),
+ ("light", "5.15.3", False),
+ ("light", "6.0.0", False),
+
+ ("auto", "5.13", False),
+ ("auto", "5.14", False),
+ ("auto", "5.15.0", False),
+ ("auto", "5.15.1", False),
+ ("auto", "5.15.2", False),
+ ("auto", "5.15.2", False),
+ ("auto", "5.15.3", False),
+ ("auto", "6.0.0", False),
])
- @utils.qt514
- def test_prefers_color_scheme_dark(self, config_stub, monkeypatch, parser,
- dark, qt_version, added):
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
- monkeypatch.setattr(qtargs.qtutils, 'qVersion', lambda: qt_version)
+ @testutils.qt514
+ def test_preferred_color_scheme(
+ self, config_stub, version_patcher, parser, value, qt_version, added):
+ version_patcher(qt_version)
- config_stub.val.colors.webpage.prefers_color_scheme_dark = dark
+ config_stub.val.colors.webpage.preferred_color_scheme = value
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
@@ -329,8 +380,7 @@ class TestQtArgs:
])
def test_overlay_scrollbar(self, config_stub, monkeypatch, parser,
bar, is_mac, added):
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
monkeypatch.setattr(qtargs.utils, 'is_mac', is_mac)
# Avoid WebRTC pipewire feature
monkeypatch.setattr(qtargs.utils, 'is_linux', False)
@@ -343,13 +393,10 @@ class TestQtArgs:
assert ('--enable-features=OverlayScrollbar' in args) == added
@pytest.fixture
- def feature_flag_patch(self, monkeypatch):
+ def feature_flag_patch(self, monkeypatch, config_stub, version_patcher):
"""Patch away things affecting feature flags."""
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
- monkeypatch.setattr(qtargs.qtutils, 'version_check',
- lambda version, exact=False, compiled=True:
- True)
+ config_stub.val.scrolling.bar = 'never'
+ version_patcher('5.15.3')
monkeypatch.setattr(qtargs.utils, 'is_mac', False)
# Avoid WebRTC pipewire feature
monkeypatch.setattr(qtargs.utils, 'is_linux', False)
@@ -409,10 +456,21 @@ class TestQtArgs:
arg for arg in args
if arg.startswith(qtargs._DISABLE_FEATURES)
]
- assert len(disable_features_args) == 1
- features = set(disable_features_args[0].split('=')[1].split(','))
- features -= {'InstalledApp'}
- assert features == set(passed_features)
+ assert disable_features_args == [flag]
+
+ def test_blink_settings_passthrough(self, parser, config_stub, feature_flag_patch):
+ config_stub.val.colors.webpage.darkmode.enabled = True
+
+ flag = qtargs._BLINK_SETTINGS + 'foo=bar'
+ parsed = parser.parse_args(['--qt-flag', flag.lstrip('-')])
+ args = qtargs.qt_args(parsed)
+
+ blink_settings_args = [
+ arg for arg in args
+ if arg.startswith(qtargs._BLINK_SETTINGS)
+ ]
+ assert len(blink_settings_args) == 1
+ assert blink_settings_args[0].startswith('--blink-settings=foo=bar,')
@pytest.mark.parametrize('qt_version, has_workaround', [
('5.14.0', False),
@@ -421,10 +479,8 @@ class TestQtArgs:
('5.15.3', False),
('6.0.0', False),
])
- def test_installedapp_workaround(self, parser, monkeypatch, qt_version, has_workaround):
- monkeypatch.setattr(qtargs.qtutils, 'qVersion', lambda: qt_version)
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
+ def test_installedapp_workaround(self, parser, version_patcher, qt_version, has_workaround):
+ version_patcher(qt_version)
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
@@ -436,22 +492,43 @@ class TestQtArgs:
expected = ['--disable-features=InstalledApp'] if has_workaround else []
assert disable_features_args == expected
- def test_blink_settings(self, config_stub, monkeypatch, parser):
+ @pytest.mark.parametrize('variant, expected', [
+ (
+ 'qt_515_1',
+ ['--blink-settings=darkModeEnabled=true,darkModeImagePolicy=2'],
+ ),
+ (
+ 'qt_515_2',
+ [
+ (
+ '--blink-settings=preferredColorScheme=2,'
+ 'forceDarkModeEnabled=true,'
+ 'forceDarkModeImagePolicy=2'
+ )
+ ],
+ ),
+ (
+ 'qt_515_3',
+ [
+ '--blink-settings=forceDarkModeEnabled=true',
+ '--dark-mode-settings=ImagePolicy=2',
+ ]
+ ),
+ ])
+ def test_dark_mode_settings(self, config_stub, monkeypatch, parser,
+ variant, expected):
from qutebrowser.browser.webengine import darkmode
- monkeypatch.setattr(qtargs.objects, 'backend',
- usertypes.Backend.QtWebEngine)
- monkeypatch.setattr(darkmode, '_variant',
- lambda: darkmode.Variant.qt_515_2)
+ monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(
+ darkmode, '_variant', lambda _versions: darkmode.Variant[variant])
config_stub.val.colors.webpage.darkmode.enabled = True
parsed = parser.parse_args([])
args = qtargs.qt_args(parsed)
- expected = ('--blink-settings=forceDarkModeEnabled=true,'
- 'forceDarkModeImagePolicy=2')
-
- assert expected in args
+ for arg in expected:
+ assert arg in args
class TestEnvVars:
diff --git a/tests/unit/config/test_stylesheet.py b/tests/unit/config/test_stylesheet.py
index 935280b01..4ffa107ed 100644
--- a/tests/unit/config/test_stylesheet.py
+++ b/tests/unit/config/test_stylesheet.py
@@ -28,7 +28,7 @@ class StyleObj(QObject):
def __init__(self, stylesheet=None, parent=None):
super().__init__(parent)
if stylesheet is not None:
- self.STYLESHEET = stylesheet # noqa: N801,N806 pylint: disable=invalid-name
+ self.STYLESHEET = stylesheet
self.rendered_stylesheet = None
def setStyleSheet(self, stylesheet):
@@ -45,8 +45,9 @@ def test_get_stylesheet(config_stub):
@pytest.mark.parametrize('delete', [True, False])
@pytest.mark.parametrize('stylesheet_param', [True, False])
@pytest.mark.parametrize('update', [True, False])
-def test_set_register_stylesheet(delete, stylesheet_param, update, qtbot,
- config_stub, caplog):
+@pytest.mark.parametrize('changed_option', ['colors.hints.fg', 'colors.hints.bg'])
+def test_set_register_stylesheet(delete, stylesheet_param, update, changed_option,
+ qtbot, config_stub, caplog):
config_stub.val.colors.hints.fg = 'magenta'
qss = "{{ conf.colors.hints.fg }}"
@@ -63,10 +64,11 @@ def test_set_register_stylesheet(delete, stylesheet_param, update, qtbot,
assert obj.rendered_stylesheet == 'magenta'
if delete:
- with qtbot.waitSignal(obj.destroyed):
+ with qtbot.wait_signal(obj.destroyed):
obj.deleteLater()
- config_stub.val.colors.hints.fg = 'yellow'
+ config_stub.set_obj(changed_option, 'yellow')
- expected = 'magenta' if delete or not update else 'yellow'
+ expected = ('magenta' if delete or not update or changed_option != 'colors.hints.fg'
+ else 'yellow')
assert obj.rendered_stylesheet == expected
diff --git a/tests/unit/javascript/conftest.py b/tests/unit/javascript/conftest.py
index 793ec25d7..47884687d 100644
--- a/tests/unit/javascript/conftest.py
+++ b/tests/unit/javascript/conftest.py
@@ -53,27 +53,28 @@ class JSTester:
'warning': 'error'
}
- def load(self, path, **kwargs):
+ def load(self, path, base_url=QUrl(), **kwargs):
"""Load and display the given jinja test data.
Args:
path: The path to the test file, relative to the javascript/
folder.
+ base_url: The url to pass to set_html.
**kwargs: Passed to jinja's template.render().
"""
template = self._jinja_env.get_template(path)
try:
- with self.qtbot.waitSignal(self.tab.load_finished,
+ with self.qtbot.wait_signal(self.tab.load_finished,
timeout=2000) as blocker:
- self.tab.set_html(template.render(**kwargs))
+ self.tab.set_html(template.render(**kwargs), base_url=base_url)
except self.qtbot.TimeoutError:
# Sometimes this fails for some odd reason on macOS, let's just try
# again.
print("Trying to load page again...")
- with self.qtbot.waitSignal(self.tab.load_finished,
+ with self.qtbot.wait_signal(self.tab.load_finished,
timeout=2000) as blocker:
- self.tab.set_html(template.render(**kwargs))
+ self.tab.set_html(template.render(**kwargs), base_url=base_url)
assert blocker.args == [True]
@@ -94,7 +95,7 @@ class JSTester:
url: The QUrl to load.
force: Whether to force loading even if the file is invalid.
"""
- with self.qtbot.waitSignal(self.tab.load_finished,
+ with self.qtbot.wait_signal(self.tab.load_finished,
timeout=2000) as blocker:
self.tab.load_url(url)
if not force:
diff --git a/tests/unit/javascript/test_js_quirks.py b/tests/unit/javascript/test_js_quirks.py
new file mode 100644
index 000000000..b906aa17c
--- /dev/null
+++ b/tests/unit/javascript/test_js_quirks.py
@@ -0,0 +1,68 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
+#
+# This file is part of qutebrowser.
+#
+# qutebrowser is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# qutebrowser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
+
+"""Tests for QtWebEngine JavaScript quirks.
+
+This tests JS functionality which is missing in older QtWebEngine releases, but we have
+polyfills for. They should either pass because the polyfill is active, or pass because
+the native functionality exists.
+"""
+
+import pytest
+
+from PyQt5.QtCore import QUrl
+from qutebrowser.utils import usertypes
+
+
+@pytest.mark.parametrize('base_url, source, expected', [
+ pytest.param(
+ QUrl(),
+ '"This is a test".replaceAll("test", "fest")',
+ "This is a fest",
+ id='replace-all',
+ ),
+ pytest.param(
+ QUrl(),
+ '"This is a test".replaceAll(/[tr]est/g, "fest")',
+ "This is a fest",
+ id='replace-all-regex',
+ ),
+ pytest.param(
+ QUrl(),
+ '"This is a [test[".replaceAll("[", "<")',
+ "This is a <test<",
+ id='replace-all-reserved-string',
+ ),
+ pytest.param(
+ QUrl('https://test.qutebrowser.org/test'),
+ 'typeof globalThis.setTimeout === "function"',
+ True,
+ id='global-this',
+ ),
+ pytest.param(
+ QUrl(),
+ 'Object.fromEntries([["0", "a"], ["1", "b"]])',
+ {'0': 'a', '1': 'b'},
+ id='object-fromentries',
+ ),
+])
+def test_js_quirks(js_tester_webengine, base_url, source, expected):
+ js_tester_webengine.tab._scripts._inject_site_specific_quirks()
+ js_tester_webengine.load('base.html', base_url=base_url)
+ js_tester_webengine.run(source, expected, world=usertypes.JsWorld.main)
diff --git a/tests/unit/keyinput/test_basekeyparser.py b/tests/unit/keyinput/test_basekeyparser.py
index 42fa2aeed..30ee36301 100644
--- a/tests/unit/keyinput/test_basekeyparser.py
+++ b/tests/unit/keyinput/test_basekeyparser.py
@@ -25,7 +25,7 @@ from PyQt5.QtCore import Qt
import pytest
from qutebrowser.keyinput import basekeyparser, keyutils
-from qutebrowser.utils import usertypes
+from qutebrowser.utils import utils, usertypes
# Alias because we need this a lot in here.
@@ -119,9 +119,11 @@ def test_read_config(keyparser, key_config_stub, changed_mode, expected):
class TestHandle:
def test_valid_key(self, prompt_keyparser, handle_text):
+ modifier = Qt.MetaModifier if utils.is_mac else Qt.ControlModifier
+
infos = [
- keyutils.KeyInfo(Qt.Key_A, Qt.ControlModifier),
- keyutils.KeyInfo(Qt.Key_X, Qt.ControlModifier),
+ keyutils.KeyInfo(Qt.Key_A, modifier),
+ keyutils.KeyInfo(Qt.Key_X, modifier),
]
for info in infos:
prompt_keyparser.handle(info.to_event())
@@ -131,9 +133,11 @@ class TestHandle:
assert not prompt_keyparser._sequence
def test_valid_key_count(self, prompt_keyparser):
+ modifier = Qt.MetaModifier if utils.is_mac else Qt.ControlModifier
+
infos = [
keyutils.KeyInfo(Qt.Key_5, Qt.NoModifier),
- keyutils.KeyInfo(Qt.Key_A, Qt.ControlModifier),
+ keyutils.KeyInfo(Qt.Key_A, modifier),
]
for info in infos:
prompt_keyparser.handle(info.to_event())
@@ -308,7 +312,7 @@ class TestCount:
def test_count_keystring_update(self, qtbot,
handle_text, prompt_keyparser):
"""Make sure the keystring is updated correctly when entering count."""
- with qtbot.waitSignals([
+ with qtbot.wait_signals([
prompt_keyparser.keystring_updated,
prompt_keyparser.keystring_updated]) as blocker:
handle_text(prompt_keyparser, Qt.Key_4, Qt.Key_2)
@@ -331,7 +335,7 @@ def test_clear_keystring(qtbot, keyparser):
"""Test that the keystring is cleared and the signal is emitted."""
keyparser._sequence = keyseq('test')
keyparser._count = '23'
- with qtbot.waitSignal(keyparser.keystring_updated):
+ with qtbot.wait_signal(keyparser.keystring_updated):
keyparser.clear_keystring()
assert not keyparser._sequence
assert not keyparser._count
diff --git a/tests/unit/keyinput/test_keyutils.py b/tests/unit/keyinput/test_keyutils.py
index 6ac01ead5..8ab2ab147 100644
--- a/tests/unit/keyinput/test_keyutils.py
+++ b/tests/unit/keyinput/test_keyutils.py
@@ -28,6 +28,7 @@ from PyQt5.QtWidgets import QWidget
from unit.keyinput import key_data
from qutebrowser.keyinput import keyutils
+from qutebrowser.utils import utils
@pytest.fixture(params=key_data.KEYS, ids=lambda k: k.attribute)
@@ -421,10 +422,13 @@ class TestKeySequence:
('', Qt.Key_Colon, Qt.AltModifier | Qt.ShiftModifier, ':',
'<Alt+Shift+:>'),
- # Modifiers
- ('', Qt.Key_A, Qt.ControlModifier, '', '<Ctrl+A>'),
- ('', Qt.Key_A, Qt.ControlModifier | Qt.ShiftModifier, '', '<Ctrl+Shift+A>'),
- ('', Qt.Key_A, Qt.MetaModifier, '', '<Meta+A>'),
+ # Swapping Control/Meta on macOS
+ ('', Qt.Key_A, Qt.ControlModifier, '',
+ '<Meta+A>' if utils.is_mac else '<Ctrl+A>'),
+ ('', Qt.Key_A, Qt.ControlModifier | Qt.ShiftModifier, '',
+ '<Meta+Shift+A>' if utils.is_mac else '<Ctrl+Shift+A>'),
+ ('', Qt.Key_A, Qt.MetaModifier, '',
+ '<Ctrl+A>' if utils.is_mac else '<Meta+A>'),
# Handling of Backtab
('', Qt.Key_Backtab, Qt.NoModifier, '', '<Backtab>'),
@@ -441,6 +445,27 @@ class TestKeySequence:
new = seq.append_event(event)
assert new == keyutils.KeySequence.parse(expected)
+ @pytest.mark.fake_os('mac')
+ @pytest.mark.parametrize('modifiers, expected', [
+ (Qt.ControlModifier,
+ Qt.MetaModifier),
+ (Qt.MetaModifier,
+ Qt.ControlModifier),
+ (Qt.ControlModifier | Qt.MetaModifier,
+ Qt.ControlModifier | Qt.MetaModifier),
+ (Qt.ControlModifier | Qt.ShiftModifier,
+ Qt.MetaModifier | Qt.ShiftModifier),
+ (Qt.MetaModifier | Qt.ShiftModifier,
+ Qt.ControlModifier | Qt.ShiftModifier),
+ (Qt.ShiftModifier, Qt.ShiftModifier),
+ ])
+ def test_fake_mac(self, modifiers, expected):
+ """Make sure Control/Meta are swapped with a simulated Mac."""
+ seq = keyutils.KeySequence()
+ info = keyutils.KeyInfo(key=Qt.Key_A, modifiers=modifiers)
+ new = seq.append_event(info.to_event())
+ assert new[0] == keyutils.KeyInfo(Qt.Key_A, expected)
+
@pytest.mark.parametrize('key', [Qt.Key_unknown, 0x0])
def test_append_event_invalid(self, key):
seq = keyutils.KeySequence()
diff --git a/tests/unit/mainwindow/statusbar/test_textbase.py b/tests/unit/mainwindow/statusbar/test_textbase.py
index e77b11671..631c6ce44 100644
--- a/tests/unit/mainwindow/statusbar/test_textbase.py
+++ b/tests/unit/mainwindow/statusbar/test_textbase.py
@@ -64,7 +64,7 @@ def test_resize(qtbot):
long_string = 'Hello world! ' * 20
label.setText(long_string)
- with qtbot.waitExposed(label):
+ with qtbot.wait_exposed(label):
label.show()
text_1 = label._elided_text
diff --git a/tests/unit/mainwindow/test_messageview.py b/tests/unit/mainwindow/test_messageview.py
index 03b8c9879..82328ffea 100644
--- a/tests/unit/mainwindow/test_messageview.py
+++ b/tests/unit/mainwindow/test_messageview.py
@@ -37,14 +37,14 @@ def view(qtbot, config_stub):
usertypes.MessageLevel.error])
@pytest.mark.flaky # on macOS
def test_single_message(qtbot, view, level):
- with qtbot.waitExposed(view, timeout=5000):
+ with qtbot.wait_exposed(view, timeout=5000):
view.show_message(level, 'test')
assert view._messages[0].isVisible()
def test_message_hiding(qtbot, view):
"""Messages should be hidden after the timer times out."""
- with qtbot.waitSignal(view._clear_timer.timeout):
+ with qtbot.wait_signal(view._clear_timer.timeout):
view.show_message(usertypes.MessageLevel.info, 'test')
assert not view._messages
@@ -61,7 +61,7 @@ def test_size_hint(view):
def test_word_wrap(view, qtbot):
"""A long message should be wrapped."""
- with qtbot.waitSignal(view._clear_timer.timeout):
+ with qtbot.wait_signal(view._clear_timer.timeout):
view.show_message(usertypes.MessageLevel.info, 'short')
height1 = view.sizeHint().height()
assert height1 > 0
@@ -88,7 +88,7 @@ def test_show_message_twice(view):
def test_show_message_twice_after_first_disappears(qtbot, view):
"""Show the same message twice after the first is gone."""
- with qtbot.waitSignal(view._clear_timer.timeout):
+ with qtbot.wait_signal(view._clear_timer.timeout):
view.show_message(usertypes.MessageLevel.info, 'test')
# Just a sanity check
assert not view._messages
@@ -101,7 +101,7 @@ def test_changing_timer_with_messages_shown(qtbot, view, config_stub):
"""When we change messages.timeout, the timer should be restarted."""
config_stub.val.messages.timeout = 900000 # 15s
view.show_message(usertypes.MessageLevel.info, 'test')
- with qtbot.waitSignal(view._clear_timer.timeout):
+ with qtbot.wait_signal(view._clear_timer.timeout):
config_stub.val.messages.timeout = 100
diff --git a/tests/unit/mainwindow/test_tabwidget.py b/tests/unit/mainwindow/test_tabwidget.py
index 3f9e0292d..564ca1b38 100644
--- a/tests/unit/mainwindow/test_tabwidget.py
+++ b/tests/unit/mainwindow/test_tabwidget.py
@@ -36,7 +36,7 @@ class TestTabWidget:
@pytest.fixture
def widget(self, qtbot, monkeypatch, config_stub):
w = tabwidget.TabWidget(0)
- qtbot.addWidget(w)
+ qtbot.add_widget(w)
monkeypatch.setattr(tabwidget.objects, 'backend',
usertypes.Backend.QtWebKit)
w.show()
@@ -53,7 +53,7 @@ class TestTabWidget:
tab = fake_web_tab()
widget.addTab(tab, icon, 'foobar')
- with qtbot.waitExposed(widget):
+ with qtbot.wait_exposed(widget):
widget.show()
# Sizing tests
@@ -118,7 +118,7 @@ class TestTabWidget:
for i in range(num_tabs):
widget.addTab(fake_web_tab(), 'foobar' + str(i))
- with qtbot.waitExposed(widget):
+ with qtbot.wait_exposed(widget):
widget.show()
benchmark(widget.update_tab_titles)
diff --git a/tests/unit/misc/test_autoupdate.py b/tests/unit/misc/test_autoupdate.py
index f86b9b0ff..f7cf78248 100644
--- a/tests/unit/misc/test_autoupdate.py
+++ b/tests/unit/misc/test_autoupdate.py
@@ -64,8 +64,8 @@ def test_get_version_success(qtbot):
http_stub = HTTPGetStub(success=True)
client = autoupdate.PyPIVersionClient(client=http_stub)
- with qtbot.assertNotEmitted(client.error):
- with qtbot.waitSignal(client.success):
+ with qtbot.assert_not_emitted(client.error):
+ with qtbot.wait_signal(client.success):
client.get_version('test')
assert http_stub.url == QUrl(client.API_URL.format('test'))
@@ -76,8 +76,8 @@ def test_get_version_error(qtbot):
http_stub = HTTPGetStub(success=False)
client = autoupdate.PyPIVersionClient(client=http_stub)
- with qtbot.assertNotEmitted(client.success):
- with qtbot.waitSignal(client.error):
+ with qtbot.assert_not_emitted(client.success):
+ with qtbot.wait_signal(client.error):
client.get_version('test')
@@ -88,6 +88,6 @@ def test_invalid_json(qtbot, json):
client = autoupdate.PyPIVersionClient(client=http_stub)
client.get_version('test')
- with qtbot.assertNotEmitted(client.success):
- with qtbot.waitSignal(client.error):
+ with qtbot.assert_not_emitted(client.success):
+ with qtbot.wait_signal(client.error):
client.get_version('test')
diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py
index d27c2f885..ee167e8bb 100644
--- a/tests/unit/misc/test_editor.py
+++ b/tests/unit/misc/test_editor.py
@@ -223,12 +223,13 @@ def _update_file(filename, contents):
This might write the file multiple times, but different systems have
different mtime's, so we can't be sure how long to wait otherwise.
"""
- old_mtime = new_mtime = os.stat(filename).st_mtime
+ file_path = pathlib.Path(filename)
+ old_mtime = new_mtime = file_path.stat().st_mtime
while old_mtime == new_mtime:
time.sleep(0.1)
with open(filename, 'w', encoding='utf-8') as f:
f.write(contents)
- new_mtime = os.stat(filename).st_mtime
+ new_mtime = file_path.stat().st_mtime
def test_modify_watch(qtbot):
diff --git a/tests/unit/misc/test_elf.py b/tests/unit/misc/test_elf.py
new file mode 100644
index 000000000..d5d5b0ac5
--- /dev/null
+++ b/tests/unit/misc/test_elf.py
@@ -0,0 +1,88 @@
+# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
+
+# Copyright 2021 Florian Bruhin (The-Compiler) <mail@qutebrowser.org>
+#
+# This file is part of qutebrowser.
+#
+# qutebrowser is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# qutebrowser is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>.
+
+import io
+import struct
+
+import pytest
+import hypothesis
+from hypothesis import strategies as hst
+
+from qutebrowser.misc import elf
+from qutebrowser.utils import utils
+
+
+@pytest.mark.parametrize('fmt, expected', [
+ (elf.Ident._FORMAT, 0x10),
+
+ (elf.Header._FORMATS[elf.Bitness.x64], 0x30),
+ (elf.Header._FORMATS[elf.Bitness.x32], 0x24),
+
+ (elf.SectionHeader._FORMATS[elf.Bitness.x64], 0x40),
+ (elf.SectionHeader._FORMATS[elf.Bitness.x32], 0x28),
+])
+def test_format_sizes(fmt, expected):
+ """Ensure the struct format have the expected sizes.
+
+ See https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header
+ and https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#Section_header
+ """
+ assert struct.calcsize(fmt) == expected
+
+
+@pytest.mark.skipif(not utils.is_linux, reason="Needs Linux")
+def test_result(qapp, caplog):
+ """Test the real result of ELF parsing.
+
+ NOTE: If you're a distribution packager (or contributor) and see this test failing,
+ I'd like your help with making either the code or the test more reliable! The
+ underlying code is susceptible to changes in the environment, and while it's been
+ tested in various environments (Archlinux, Ubuntu), might break in yours.
+
+ If that happens, please report a bug about it!
+ """
+ pytest.importorskip('PyQt5.QtWebEngineCore')
+
+ versions = elf.parse_webenginecore()
+ assert versions is not None
+
+ # No failing mmap
+ assert len(caplog.messages) == 1
+ assert caplog.messages[0].startswith('Got versions from ELF:')
+
+ from qutebrowser.browser.webengine import webenginesettings
+ webenginesettings.init_user_agent()
+ ua = webenginesettings.parsed_user_agent
+
+ assert ua.qt_version == versions.webengine
+ assert ua.upstream_browser_version == versions.chromium
+
+
+@hypothesis.given(data=hst.builds(
+ lambda *a: b''.join(a),
+ hst.sampled_from([b'', b'\x7fELF', b'\x7fELF\x02\x01\x01']),
+ hst.binary(min_size=0x70),
+))
+def test_hypothesis(data):
+ """Fuzz ELF parsing and make sure no crashes happen."""
+ fobj = io.BytesIO(data)
+ try:
+ elf._parse_from_file(fobj)
+ except elf.ParseError as e:
+ print(e)
diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py
index 7f6a11ddd..9e1b3916c 100644
--- a/tests/unit/misc/test_guiprocess.py
+++ b/tests/unit/misc/test_guiprocess.py
@@ -36,7 +36,7 @@ def proc(qtbot, caplog):
yield p
if p._proc.state() == QProcess.Running:
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(p.finished, timeout=10000,
+ with qtbot.wait_signal(p.finished, timeout=10000,
raising=False) as blocker:
p._proc.terminate()
if not blocker.signal_triggered:
@@ -54,7 +54,7 @@ def fake_proc(monkeypatch, stubs):
def test_start(proc, qtbot, message_mock, py_proc):
"""Test simply starting a process."""
- with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
+ with qtbot.wait_signals([proc.started, proc.finished], timeout=10000,
order='strict'):
argv = py_proc("import sys; print('test'); sys.exit(0)")
proc.start(*argv)
@@ -70,7 +70,7 @@ def test_start_verbose(proc, qtbot, message_mock, py_proc):
"""Test starting a process verbosely."""
proc.verbose = True
- with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
+ with qtbot.wait_signals([proc.started, proc.finished], timeout=10000,
order='strict'):
argv = py_proc("import sys; print('test'); sys.exit(0)")
proc.start(*argv)
@@ -99,7 +99,7 @@ def test_start_output_message(proc, qtbot, caplog, message_mock, py_proc,
code.append("sys.exit(0)")
with caplog.at_level(logging.ERROR, 'message'):
- with qtbot.waitSignals([proc.started, proc.finished],
+ with qtbot.wait_signals([proc.started, proc.finished],
timeout=10000,
order='strict'):
argv = py_proc(';'.join(code))
@@ -149,7 +149,7 @@ def test_start_env(monkeypatch, qtbot, py_proc):
sys.exit(0)
""")
- with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
+ with qtbot.wait_signals([proc.started, proc.finished], timeout=10000,
order='strict'):
proc.start(*argv)
@@ -180,7 +180,7 @@ def test_start_detached_error(fake_proc, message_mock, caplog):
def test_double_start(qtbot, proc, py_proc):
"""Test starting a GUIProcess twice."""
- with qtbot.waitSignal(proc.started, timeout=10000):
+ with qtbot.wait_signal(proc.started, timeout=10000):
argv = py_proc("import time; time.sleep(10)")
proc.start(*argv)
with pytest.raises(ValueError):
@@ -189,11 +189,11 @@ def test_double_start(qtbot, proc, py_proc):
def test_double_start_finished(qtbot, proc, py_proc):
"""Test starting a GUIProcess twice (with the first call finished)."""
- with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
+ with qtbot.wait_signals([proc.started, proc.finished], timeout=10000,
order='strict'):
argv = py_proc("import sys; sys.exit(0)")
proc.start(*argv)
- with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
+ with qtbot.wait_signals([proc.started, proc.finished], timeout=10000,
order='strict'):
argv = py_proc("import sys; sys.exit(0)")
proc.start(*argv)
@@ -222,7 +222,7 @@ def test_start_logging(fake_proc, caplog):
def test_error(qtbot, proc, caplog, message_mock):
"""Test the process emitting an error."""
with caplog.at_level(logging.ERROR, 'message'):
- with qtbot.waitSignal(proc.error, timeout=5000):
+ with qtbot.wait_signal(proc.error, timeout=5000):
proc.start('this_does_not_exist_either', [])
msg = message_mock.getmsg(usertypes.MessageLevel.error)
@@ -231,7 +231,7 @@ def test_error(qtbot, proc, caplog, message_mock):
def test_exit_unsuccessful(qtbot, proc, message_mock, py_proc, caplog):
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(proc.finished, timeout=10000):
+ with qtbot.wait_signal(proc.finished, timeout=10000):
proc.start(*py_proc('import sys; sys.exit(1)'))
msg = message_mock.getmsg(usertypes.MessageLevel.error)
@@ -241,7 +241,7 @@ def test_exit_unsuccessful(qtbot, proc, message_mock, py_proc, caplog):
def test_exit_crash(qtbot, proc, message_mock, py_proc, caplog):
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(proc.finished, timeout=10000):
+ with qtbot.wait_signal(proc.finished, timeout=10000):
proc.start(*py_proc("""
import os, signal
os.kill(os.getpid(), signal.SIGSEGV)
@@ -259,7 +259,7 @@ def test_exit_crash(qtbot, proc, message_mock, py_proc, caplog):
def test_exit_unsuccessful_output(qtbot, proc, caplog, py_proc, stream):
"""When a process fails, its output should be logged."""
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(proc.finished, timeout=10000):
+ with qtbot.wait_signal(proc.finished, timeout=10000):
proc.start(*py_proc("""
import sys
print("test", file=sys.{})
@@ -275,7 +275,7 @@ def test_exit_successful_output(qtbot, proc, py_proc, stream):
The test doesn't actually check the log as it'd fail because of the error
logging.
"""
- with qtbot.waitSignal(proc.finished, timeout=10000):
+ with qtbot.wait_signal(proc.finished, timeout=10000):
proc.start(*py_proc("""
import sys
print("test", file=sys.{})
@@ -285,7 +285,7 @@ def test_exit_successful_output(qtbot, proc, py_proc, stream):
def test_stdout_not_decodable(proc, qtbot, message_mock, py_proc):
"""Test handling malformed utf-8 in stdout."""
- with qtbot.waitSignals([proc.started, proc.finished], timeout=10000,
+ with qtbot.wait_signals([proc.started, proc.finished], timeout=10000,
order='strict'):
argv = py_proc(r"""
import sys
diff --git a/tests/unit/misc/test_ipc.py b/tests/unit/misc/test_ipc.py
index 1ea70afe7..c19d0bc42 100644
--- a/tests/unit/misc/test_ipc.py
+++ b/tests/unit/misc/test_ipc.py
@@ -37,7 +37,7 @@ from PyQt5.QtTest import QSignalSpy
import qutebrowser
from qutebrowser.misc import ipc
from qutebrowser.utils import standarddir, utils
-from helpers import stubs, utils as testutils
+from helpers import stubs, testutils
pytestmark = pytest.mark.usefixtures('qapp')
@@ -57,7 +57,7 @@ def ipc_server(qapp, qtbot):
yield server
if (server._socket is not None and
server._socket.state() != QLocalSocket.UnconnectedState):
- with qtbot.waitSignal(server._socket.disconnected, raising=False):
+ with qtbot.wait_signal(server._socket.disconnected, raising=False):
server._socket.abort()
try:
server.shutdown()
@@ -306,10 +306,10 @@ class TestListen:
@pytest.mark.posix
def test_permissions_posix(self, ipc_server):
ipc_server.listen()
- sockfile = ipc_server._server.fullServerName()
- sockdir = pathlib.Path(sockfile).parent
+ sockfile_path = pathlib.Path(ipc_server._server.fullServerName())
+ sockdir = sockfile_path.parent
- file_stat = os.stat(sockfile)
+ file_stat = sockfile_path.stat()
dir_stat = sockdir.stat()
# pylint: disable=no-member,useless-suppression
@@ -322,7 +322,7 @@ class TestListen:
print('sockdir: {} / owner {} / mode {:o}'.format(
sockdir, dir_stat.st_uid, dir_stat.st_mode))
print('sockfile: {} / owner {} / mode {:o}'.format(
- sockfile, file_stat.st_uid, file_stat.st_mode))
+ sockfile_path, file_stat.st_uid, file_stat.st_mode))
assert file_owner_ok or dir_owner_ok
assert file_mode_ok or dir_mode_ok
@@ -331,16 +331,18 @@ class TestListen:
def test_atime_update(self, qtbot, ipc_server):
ipc_server._atime_timer.setInterval(500) # We don't want to wait
ipc_server.listen()
- old_atime = os.stat(ipc_server._server.fullServerName()).st_atime_ns
- with qtbot.waitSignal(ipc_server._atime_timer.timeout, timeout=2000):
+ sockfile_path = pathlib.Path(ipc_server._server.fullServerName())
+ old_atime = sockfile_path.stat().st_atime_ns
+
+ with qtbot.wait_signal(ipc_server._atime_timer.timeout, timeout=2000):
pass
# Make sure the timer is not singleShot
- with qtbot.waitSignal(ipc_server._atime_timer.timeout, timeout=2000):
+ with qtbot.wait_signal(ipc_server._atime_timer.timeout, timeout=2000):
pass
- new_atime = os.stat(ipc_server._server.fullServerName()).st_atime_ns
+ new_atime = sockfile_path.stat().st_atime_ns
assert old_atime != new_atime
@@ -366,7 +368,7 @@ class TestListen:
sockfile.unlink()
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(ipc_server._atime_timer.timeout,
+ with qtbot.wait_signal(ipc_server._atime_timer.timeout,
timeout=2000):
pass
@@ -443,7 +445,7 @@ class TestHandleConnection:
ipc_server._server = FakeServer(socket)
- with qtbot.waitSignal(ipc_server.got_args) as blocker:
+ with qtbot.wait_signal(ipc_server.got_args) as blocker:
ipc_server.handle_connection()
assert blocker.args == [['foo'], 'tab', '']
@@ -456,7 +458,7 @@ def connected_socket(qtbot, qlocalsocket, ipc_server):
pytest.skip("Skipping connected_socket test - "
"https://github.com/qutebrowser/qutebrowser/issues/1045")
ipc_server.listen()
- with qtbot.waitSignal(ipc_server._server.newConnection):
+ with qtbot.wait_signal(ipc_server._server.newConnection):
qlocalsocket.connectToServer('qute-test')
yield qlocalsocket
qlocalsocket.disconnectFromServer()
@@ -496,8 +498,8 @@ NEW_VERSION = str(ipc.PROTOCOL_VERSION + 1).encode('utf-8')
def test_invalid_data(qtbot, ipc_server, connected_socket, caplog, data, msg):
signals = [ipc_server.got_invalid_data, connected_socket.disconnected]
with caplog.at_level(logging.ERROR):
- with qtbot.assertNotEmitted(ipc_server.got_args):
- with qtbot.waitSignals(signals, order='strict'):
+ with qtbot.assert_not_emitted(ipc_server.got_args):
+ with qtbot.wait_signals(signals, order='strict'):
connected_socket.write(data)
invalid_msg = 'Ignoring invalid IPC data from socket '
@@ -514,8 +516,8 @@ def test_multiline(qtbot, ipc_server, connected_socket):
' "protocol_version": {version}}}\n'.format(
version=ipc.PROTOCOL_VERSION))
- with qtbot.assertNotEmitted(ipc_server.got_invalid_data):
- with qtbot.waitSignals([ipc_server.got_args, ipc_server.got_args],
+ with qtbot.assert_not_emitted(ipc_server.got_invalid_data):
+ with qtbot.wait_signals([ipc_server.got_args, ipc_server.got_args],
order='strict'):
connected_socket.write(data.encode('utf-8'))
@@ -536,10 +538,10 @@ class TestSendToRunningInstance:
def test_normal(self, qtbot, tmp_path, ipc_server, mocker, has_cwd):
ipc_server.listen()
- with qtbot.assertNotEmitted(ipc_server.got_invalid_data):
- with qtbot.waitSignal(ipc_server.got_args,
+ with qtbot.assert_not_emitted(ipc_server.got_invalid_data):
+ with qtbot.wait_signal(ipc_server.got_args,
timeout=5000) as blocker:
- with qtbot.waitSignal(ipc_server.got_raw,
+ with qtbot.wait_signal(ipc_server.got_raw,
timeout=5000) as raw_blocker:
with testutils.change_cwd(tmp_path):
if not has_cwd:
@@ -588,11 +590,11 @@ def test_timeout(qtbot, caplog, qlocalsocket, ipc_server):
ipc_server._timer.setInterval(100)
ipc_server.listen()
- with qtbot.waitSignal(ipc_server._server.newConnection):
+ with qtbot.wait_signal(ipc_server._server.newConnection):
qlocalsocket.connectToServer('qute-test')
with caplog.at_level(logging.ERROR):
- with qtbot.waitSignal(qlocalsocket.disconnected, timeout=5000):
+ with qtbot.wait_signal(qlocalsocket.disconnected, timeout=5000):
pass
assert caplog.messages[-1].startswith("IPC connection timed out")
@@ -654,7 +656,7 @@ class TestSendOrListen:
assert "Starting IPC server..." in caplog.messages
assert ret_server is ipc.server
- with qtbot.waitSignal(ret_server.got_args):
+ with qtbot.wait_signal(ret_server.got_args):
ret_client = ipc.send_or_listen(args)
assert ret_client is None
diff --git a/tests/unit/misc/test_keyhints.py b/tests/unit/misc/test_keyhints.py
index 580869b45..8d9c53c93 100644
--- a/tests/unit/misc/test_keyhints.py
+++ b/tests/unit/misc/test_keyhints.py
@@ -55,8 +55,8 @@ def keyhint(qtbot, config_stub, key_config_stub):
def test_show_and_hide(qtbot, keyhint):
- with qtbot.waitSignal(keyhint.update_geometry):
- with qtbot.waitExposed(keyhint):
+ with qtbot.wait_signal(keyhint.update_geometry):
+ with qtbot.wait_exposed(keyhint):
keyhint.show()
keyhint.update_keyhint(usertypes.KeyMode.normal, '')
assert not keyhint.isVisible()
diff --git a/tests/unit/misc/test_miscwidgets.py b/tests/unit/misc/test_miscwidgets.py
index 572a5cbc9..6e1919ec3 100644
--- a/tests/unit/misc/test_miscwidgets.py
+++ b/tests/unit/misc/test_miscwidgets.py
@@ -127,7 +127,7 @@ class TestFullscreenNotification:
def test_timeout(self, qtbot, key_config_stub):
w = miscwidgets.FullscreenNotification()
qtbot.add_widget(w)
- with qtbot.waitSignal(w.destroyed):
+ with qtbot.wait_signal(w.destroyed):
w.set_timeout(1)
diff --git a/tests/unit/misc/test_msgbox.py b/tests/unit/misc/test_msgbox.py
index 893f317d7..c5c23639a 100644
--- a/tests/unit/misc/test_msgbox.py
+++ b/tests/unit/misc/test_msgbox.py
@@ -77,7 +77,7 @@ def test_finished_signal(qtbot):
qtbot.add_widget(box)
- with qtbot.waitSignal(box.finished):
+ with qtbot.wait_signal(box.finished):
box.accept()
assert signal_triggered
@@ -92,10 +92,8 @@ def test_information(qtbot):
assert box.icon() == QMessageBox.Information
-def test_no_err_windows(fake_args, capsys):
+def test_no_err_windows(fake_args, caplog):
fake_args.no_err_windows = True
box = msgbox.information(parent=None, title='foo', text='bar')
box.exec() # should do nothing
- out, err = capsys.readouterr()
- assert not out
- assert err == 'Message box: foo; bar\n'
+ assert caplog.messages == ['foo\n\nbar']
diff --git a/tests/unit/misc/test_pastebin.py b/tests/unit/misc/test_pastebin.py
index e750bb299..7a47e723c 100644
--- a/tests/unit/misc/test_pastebin.py
+++ b/tests/unit/misc/test_pastebin.py
@@ -99,8 +99,8 @@ def test_paste_private(pbclient):
"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):
+ with qtbot.assert_not_emitted(pbclient.error):
+ with qtbot.wait_signal(pbclient.success):
pbclient._client.success.emit(http)
@@ -110,12 +110,12 @@ def test_on_client_success(http, pbclient, qtbot):
"http//invalid.com"
])
def test_client_success_invalid_http(http, pbclient, qtbot):
- with qtbot.assertNotEmitted(pbclient.success):
- with qtbot.waitSignal(pbclient.error):
+ with qtbot.assert_not_emitted(pbclient.success):
+ with qtbot.wait_signal(pbclient.error):
pbclient._client.success.emit(http)
def test_client_error(pbclient, qtbot):
- with qtbot.assertNotEmitted(pbclient.success):
- with qtbot.waitSignal(pbclient.error):
+ with qtbot.assert_not_emitted(pbclient.success):
+ with qtbot.wait_signal(pbclient.error):
pbclient._client.error.emit("msg")
diff --git a/tests/unit/misc/test_sql.py b/tests/unit/misc/test_sql.py
index 211280a6b..f6fa68869 100644
--- a/tests/unit/misc/test_sql.py
+++ b/tests/unit/misc/test_sql.py
@@ -128,18 +128,18 @@ def test_init():
def test_insert(qtbot):
table = sql.SqlTable('Foo', ['name', 'val', 'lucky'])
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.insert({'name': 'one', 'val': 1, 'lucky': False})
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.insert({'name': 'wan', 'val': 1, 'lucky': False})
def test_insert_replace(qtbot):
table = sql.SqlTable('Foo', ['name', 'val', 'lucky'],
constraints={'name': 'PRIMARY KEY'})
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.insert({'name': 'one', 'val': 1, 'lucky': False}, replace=True)
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.insert({'name': 'one', 'val': 11, 'lucky': True}, replace=True)
assert list(table) == [('one', 11, True)]
@@ -150,7 +150,7 @@ def test_insert_replace(qtbot):
def test_insert_batch(qtbot):
table = sql.SqlTable('Foo', ['name', 'val', 'lucky'])
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.insert_batch({'name': ['one', 'nine', 'thirteen'],
'val': [1, 9, 13],
'lucky': [False, False, True]})
@@ -164,12 +164,12 @@ def test_insert_batch_replace(qtbot):
table = sql.SqlTable('Foo', ['name', 'val', 'lucky'],
constraints={'name': 'PRIMARY KEY'})
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.insert_batch({'name': ['one', 'nine', 'thirteen'],
'val': [1, 9, 13],
'lucky': [False, False, True]})
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.insert_batch({'name': ['one', 'nine'],
'val': [11, 19],
'lucky': [True, True]},
@@ -219,19 +219,14 @@ def test_delete(qtbot):
table.insert({'name': 'thirteen', 'val': 13, 'lucky': True})
with pytest.raises(KeyError):
table.delete('name', 'nope')
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.delete('name', 'thirteen')
assert list(table) == [('one', 1, False), ('nine', 9, False)]
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.delete('lucky', False)
assert not list(table)
-def test_delete_optional(qtbot):
- table = sql.SqlTable('Foo', ['name', 'val'])
- table.delete('name', 'doesnotexist', optional=True)
-
-
def test_len():
table = sql.SqlTable('Foo', ['name', 'val', 'lucky'])
assert len(table) == 0
@@ -289,7 +284,7 @@ def test_delete_all(qtbot):
table.insert({'name': 'one', 'val': 1, 'lucky': False})
table.insert({'name': 'nine', 'val': 9, 'lucky': False})
table.insert({'name': 'thirteen', 'val': 13, 'lucky': True})
- with qtbot.waitSignal(table.changed):
+ with qtbot.wait_signal(table.changed):
table.delete_all()
assert list(table) == []
diff --git a/tests/unit/misc/test_throttle.py b/tests/unit/misc/test_throttle.py
index 48148a6a6..33e9949f2 100644
--- a/tests/unit/misc/test_throttle.py
+++ b/tests/unit/misc/test_throttle.py
@@ -25,11 +25,11 @@ import sip
import pytest
from PyQt5.QtCore import QObject
-from helpers import utils
+from helpers import testutils
from qutebrowser.misc import throttle
-DELAY = 500 if utils.ON_CI else 100
+DELAY = 500 if testutils.ON_CI else 100
@pytest.fixture
diff --git a/tests/unit/misc/userscripts/test_qute_lastpass.py b/tests/unit/misc/userscripts/test_qute_lastpass.py
index 7e2c7dfbd..3846028ee 100644
--- a/tests/unit/misc/userscripts/test_qute_lastpass.py
+++ b/tests/unit/misc/userscripts/test_qute_lastpass.py
@@ -26,9 +26,9 @@ from unittest.mock import ANY, call
import pytest
-from helpers import utils
+from helpers import testutils
-qute_lastpass = utils.import_userscript('qute-lastpass')
+qute_lastpass = testutils.import_userscript('qute-lastpass')
default_lpass_match = [
{
diff --git a/tests/unit/scripts/test_check_coverage.py b/tests/unit/scripts/test_check_coverage.py
index 5a7551b1c..9708c6283 100644
--- a/tests/unit/scripts/test_check_coverage.py
+++ b/tests/unit/scripts/test_check_coverage.py
@@ -177,7 +177,7 @@ def test_untested_floats(covtest):
@pytest.mark.skipif(
- sys.version_info[:4] == (3, 10, 0, 'alpha') and sys.version_info.serial < 5,
+ sys.version_info[:4] == (3, 10, 0, 'alpha'),
reason='Different results, see https://github.com/nedbat/coveragepy/issues/1106')
def test_untested_branches(covtest):
covtest.makefile("""
diff --git a/tests/unit/scripts/test_run_vulture.py b/tests/unit/scripts/test_run_vulture.py
index f1e5a261a..0ef5afcee 100644
--- a/tests/unit/scripts/test_run_vulture.py
+++ b/tests/unit/scripts/test_run_vulture.py
@@ -23,7 +23,7 @@ import textwrap
import pytest
-from tests.helpers import utils
+from tests.helpers import testutils
try:
from scripts.dev import run_vulture
@@ -53,7 +53,7 @@ class VultureDir:
"""Run vulture over all generated files and return the output."""
names = [p.name for p in self._tmp_path.glob('*')]
assert names
- with utils.change_cwd(self._tmp_path):
+ with testutils.change_cwd(self._tmp_path):
return run_vulture.run(names)
def makepyfile(self, **kwargs):
@@ -86,7 +86,7 @@ def test_unused_func(vultdir):
msg = "*test_unused_func*foo.py:2: unused function 'foo' (60% confidence)"
msgs = vultdir.run()
assert len(msgs) == 1
- assert utils.pattern_match(pattern=msg, value=msgs[0])
+ assert testutils.pattern_match(pattern=msg, value=msgs[0])
def test_unused_method_camelcase(vultdir):
diff --git a/tests/unit/test_qutebrowser.py b/tests/unit/test_qutebrowser.py
index c553606c4..d9275631d 100644
--- a/tests/unit/test_qutebrowser.py
+++ b/tests/unit/test_qutebrowser.py
@@ -60,3 +60,18 @@ class TestLogFilter:
_out, err = capsys.readouterr()
print(err)
assert 'Invalid log category invalid - valid categories' in err
+
+
+class TestJsonArgs:
+
+ def test_partial(self, parser):
+ """Make sure we can provide a subset of all arguments.
+
+ This ensures that it's possible to restart into an older version of qutebrowser
+ when a new argument was added.
+ """
+ args = parser.parse_args(['--json-args', '{"debug": true}'])
+ args = qutebrowser._unpack_json_args(args)
+ # pylint: disable=no-member
+ assert args.debug
+ assert not args.temp_basedir
diff --git a/tests/unit/utils/test_javascript.py b/tests/unit/utils/test_javascript.py
index e8acc30fb..7a97ef6d1 100644
--- a/tests/unit/utils/test_javascript.py
+++ b/tests/unit/utils/test_javascript.py
@@ -73,7 +73,7 @@ class TestStringEscape:
"""Test conversion by running JS in a tab."""
escaped = javascript.string_escape(text)
- with qtbot.waitCallback() as cb:
+ with qtbot.wait_callback() as cb:
web_tab.run_js_async('"{}";'.format(escaped), cb)
cb.assert_called_with(text)
diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py
index eb1233332..c686e4f74 100644
--- a/tests/unit/utils/test_qtutils.py
+++ b/tests/unit/utils/test_qtutils.py
@@ -34,6 +34,7 @@ from PyQt5.QtGui import QColor
from qutebrowser.utils import qtutils, utils, usertypes
import overflow_test_cases
+from helpers import testutils
if utils.is_linux:
# Those are not run on macOS because that seems to cause a hang sometimes.
@@ -941,38 +942,28 @@ class TestEventLoop:
assert not self.loop._executing
-class Color(QColor):
-
- """A QColor with a nicer repr()."""
-
- def __repr__(self):
- return utils.get_repr(self, constructor=True, red=self.red(),
- green=self.green(), blue=self.blue(),
- alpha=self.alpha())
-
-
class TestInterpolateColor:
@dataclasses.dataclass
class Colors:
- white: Color
- black: Color
+ white: testutils.Color
+ black: testutils.Color
@pytest.fixture
def colors(self):
"""Example colors to be used."""
- return self.Colors(Color('white'), Color('black'))
+ return self.Colors(testutils.Color('white'), testutils.Color('black'))
def test_invalid_start(self, colors):
"""Test an invalid start color."""
with pytest.raises(qtutils.QtValueError):
- qtutils.interpolate_color(Color(), colors.white, 0)
+ qtutils.interpolate_color(testutils.Color(), colors.white, 0)
def test_invalid_end(self, colors):
"""Test an invalid end color."""
with pytest.raises(qtutils.QtValueError):
- qtutils.interpolate_color(colors.white, Color(), 0)
+ qtutils.interpolate_color(colors.white, testutils.Color(), 0)
@pytest.mark.parametrize('perc', [-1, 101])
def test_invalid_percentage(self, colors, perc):
@@ -985,52 +976,50 @@ class TestInterpolateColor:
with pytest.raises(ValueError):
qtutils.interpolate_color(colors.white, colors.black, 10, QColor.Cmyk)
- @pytest.mark.parametrize('colorspace', [QColor.Rgb, QColor.Hsv,
- QColor.Hsl])
+ @pytest.mark.parametrize('colorspace', [QColor.Rgb, QColor.Hsv, QColor.Hsl])
def test_0_100(self, colors, colorspace):
"""Test 0% and 100% in different colorspaces."""
white = qtutils.interpolate_color(colors.white, colors.black, 0, colorspace)
black = qtutils.interpolate_color(colors.white, colors.black, 100, colorspace)
- assert Color(white) == colors.white
- assert Color(black) == colors.black
+ assert testutils.Color(white) == colors.white
+ assert testutils.Color(black) == colors.black
def test_interpolation_rgb(self):
"""Test an interpolation in the RGB colorspace."""
color = qtutils.interpolate_color(
- Color(0, 40, 100), Color(0, 20, 200), 50, QColor.Rgb)
- assert Color(color) == Color(0, 30, 150)
+ testutils.Color(0, 40, 100), testutils.Color(0, 20, 200), 50, QColor.Rgb)
+ assert testutils.Color(color) == testutils.Color(0, 30, 150)
def test_interpolation_hsv(self):
"""Test an interpolation in the HSV colorspace."""
- start = Color()
- stop = Color()
+ start = testutils.Color()
+ stop = testutils.Color()
start.setHsv(0, 40, 100)
stop.setHsv(0, 20, 200)
color = qtutils.interpolate_color(start, stop, 50, QColor.Hsv)
- expected = Color()
+ expected = testutils.Color()
expected.setHsv(0, 30, 150)
- assert Color(color) == expected
+ assert testutils.Color(color) == expected
def test_interpolation_hsl(self):
"""Test an interpolation in the HSL colorspace."""
- start = Color()
- stop = Color()
+ start = testutils.Color()
+ stop = testutils.Color()
start.setHsl(0, 40, 100)
stop.setHsl(0, 20, 200)
color = qtutils.interpolate_color(start, stop, 50, QColor.Hsl)
- expected = Color()
+ expected = testutils.Color()
expected.setHsl(0, 30, 150)
- assert Color(color) == expected
+ assert testutils.Color(color) == expected
- @pytest.mark.parametrize('colorspace', [QColor.Rgb, QColor.Hsv,
- QColor.Hsl])
+ @pytest.mark.parametrize('colorspace', [QColor.Rgb, QColor.Hsv, QColor.Hsl])
def test_interpolation_alpha(self, colorspace):
"""Test interpolation of colorspace's alpha."""
- start = Color(0, 0, 0, 30)
- stop = Color(0, 0, 0, 100)
+ start = testutils.Color(0, 0, 0, 30)
+ stop = testutils.Color(0, 0, 0, 100)
color = qtutils.interpolate_color(start, stop, 50, colorspace)
- expected = Color(0, 0, 0, 65)
- assert Color(color) == expected
+ expected = testutils.Color(0, 0, 0, 65)
+ assert testutils.Color(color) == expected
@pytest.mark.parametrize('percentage, expected', [
(0, (0, 0, 0)),
@@ -1040,6 +1029,6 @@ class TestInterpolateColor:
def test_interpolation_none(self, percentage, expected):
"""Test an interpolation with a gradient turned off."""
color = qtutils.interpolate_color(
- Color(0, 0, 0), Color(255, 255, 255), percentage, None)
+ testutils.Color(0, 0, 0), testutils.Color(255, 255, 255), percentage, None)
assert isinstance(color, QColor)
- assert Color(color) == Color(*expected)
+ assert testutils.Color(color) == testutils.Color(*expected)
diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py
index f6ac8039d..4cf60943c 100644
--- a/tests/unit/utils/test_utils.py
+++ b/tests/unit/utils/test_utils.py
@@ -30,7 +30,7 @@ import shlex
import math
import zipfile
-from PyQt5.QtCore import QUrl
+from PyQt5.QtCore import QUrl, QRect
from PyQt5.QtGui import QClipboard
import pytest
import hypothesis
@@ -42,6 +42,22 @@ import qutebrowser.utils # for test_qualname
from qutebrowser.utils import utils, version, usertypes
+class TestVersionNumber:
+
+ @pytest.mark.parametrize('args, expected', [
+ ([5, 15, 2], 'VersionNumber(5, 15, 2)'),
+ ([5, 15], 'VersionNumber(5, 15)'),
+ ([5], 'VersionNumber(5)'),
+ ])
+ def test_repr(self, args, expected):
+ num = utils.VersionNumber(*args)
+ assert repr(num) == expected
+
+ def test_not_normalized(self):
+ with pytest.raises(ValueError, match='Refusing to construct'):
+ utils.VersionNumber(5, 15, 0)
+
+
ELLIPSIS = '\u2026'
@@ -993,3 +1009,53 @@ class TestCleanupFileContext:
pass
assert len(caplog.messages) == 1
assert caplog.messages[0].startswith("Failed to delete tempfile")
+
+
+class TestParseRect:
+
+ @pytest.mark.parametrize('value, expected', [
+ ('1x1+0+0', QRect(0, 0, 1, 1)),
+ ('123x789+12+34', QRect(12, 34, 123, 789)),
+ ])
+ def test_valid(self, value, expected):
+ assert utils.parse_rect(value) == expected
+
+ @pytest.mark.parametrize('value, message', [
+ ('0x0+1+1', "Invalid rectangle"),
+ ('1x1-1+1', "String 1x1-1+1 does not match WxH+X+Y"),
+ ('1x1+1-1', "String 1x1+1-1 does not match WxH+X+Y"),
+ ('1x1', "String 1x1 does not match WxH+X+Y"),
+ ('+1+2', "String +1+2 does not match WxH+X+Y"),
+ ('1e0x1+0+0', "String 1e0x1+0+0 does not match WxH+X+Y"),
+ ('¹x1+0+0', "String ¹x1+0+0 does not match WxH+X+Y"),
+ ])
+ def test_invalid(self, value, message):
+ with pytest.raises(ValueError) as excinfo:
+ utils.parse_rect(value)
+ assert str(excinfo.value) == message
+
+ @hypothesis.given(strategies.text())
+ def test_hypothesis_text(self, s):
+ try:
+ utils.parse_rect(s)
+ except ValueError as e:
+ print(e)
+
+ @hypothesis.given(strategies.tuples(
+ strategies.integers(),
+ strategies.integers(),
+ strategies.integers(),
+ strategies.integers(),
+ ).map(lambda tpl: '{}x{}+{}+{}'.format(*tpl)))
+ def test_hypothesis_sophisticated(self, s):
+ try:
+ utils.parse_rect(s)
+ except ValueError as e:
+ print(e)
+
+ @hypothesis.given(strategies.from_regex(utils._RECT_PATTERN))
+ def test_hypothesis_regex(self, s):
+ try:
+ utils.parse_rect(s)
+ except ValueError as e:
+ print(e)
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index 9a8e6d075..f846c91ac 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -21,13 +21,9 @@
import io
import sys
-import collections
import os.path
import subprocess
import contextlib
-import builtins
-import types
-import importlib
import logging
import textwrap
import datetime
@@ -36,11 +32,12 @@ import dataclasses
import pytest
import hypothesis
import hypothesis.strategies
+from PyQt5.QtCore import PYQT_VERSION_STR
import qutebrowser
-from qutebrowser.config import config
+from qutebrowser.config import config, websettings
from qutebrowser.utils import version, usertypes, utils, standarddir
-from qutebrowser.misc import pastebin, objects
+from qutebrowser.misc import pastebin, objects, elf
from qutebrowser.browser import pdfjs
@@ -76,7 +73,7 @@ from qutebrowser.browser import pdfjs
""",
version.DistributionInfo(
id='ubuntu', parsed=version.Distribution.ubuntu,
- version=utils.parse_version('14.4'),
+ version=utils.VersionNumber(14, 4, 5),
pretty='Ubuntu 14.04.5 LTS')),
# Ubuntu 17.04
("""
@@ -89,7 +86,7 @@ from qutebrowser.browser import pdfjs
""",
version.DistributionInfo(
id='ubuntu', parsed=version.Distribution.ubuntu,
- version=utils.parse_version('17.4'),
+ version=utils.VersionNumber(17, 4),
pretty='Ubuntu 17.04')),
# Debian Jessie
("""
@@ -101,7 +98,7 @@ from qutebrowser.browser import pdfjs
""",
version.DistributionInfo(
id='debian', parsed=version.Distribution.debian,
- version=utils.parse_version('8'),
+ version=utils.VersionNumber(8),
pretty='Debian GNU/Linux 8 (jessie)')),
# Void Linux
("""
@@ -132,7 +129,7 @@ from qutebrowser.browser import pdfjs
""",
version.DistributionInfo(
id='fedora', parsed=version.Distribution.fedora,
- version=utils.parse_version('25'),
+ version=utils.VersionNumber(25),
pretty='Fedora 25 (Twenty Five)')),
# OpenSUSE
("""
@@ -145,7 +142,7 @@ from qutebrowser.browser import pdfjs
""",
version.DistributionInfo(
id='opensuse', parsed=version.Distribution.opensuse,
- version=utils.parse_version('42.2'),
+ version=utils.VersionNumber(42, 2),
pretty='openSUSE Leap 42.2')),
# Linux Mint
("""
@@ -158,7 +155,7 @@ from qutebrowser.browser import pdfjs
""",
version.DistributionInfo(
id='linuxmint', parsed=version.Distribution.linuxmint,
- version=utils.parse_version('18.1'),
+ version=utils.VersionNumber(18, 1),
pretty='Linux Mint 18.1')),
# Manjaro
("""
@@ -178,6 +175,97 @@ from qutebrowser.browser import pdfjs
version.DistributionInfo(
id='funtoo', parsed=version.Distribution.gentoo,
version=None, pretty='Funtoo GNU/Linux')),
+ # KDE neon
+ ("""
+ NAME="KDE neon"
+ VERSION="5.20"
+ ID=neon
+ ID_LIKE="ubuntu debian"
+ PRETTY_NAME="KDE neon User Edition 5.20"
+ VARIANT="User Edition"
+ VERSION_ID="20.04"
+ """,
+ version.DistributionInfo(
+ id='neon', parsed=version.Distribution.neon,
+ version=utils.VersionNumber(5, 20), pretty='KDE neon User Edition 5.20')),
+ # Archlinux ARM
+ ("""
+ NAME="Arch Linux ARM"
+ PRETTY_NAME="Arch Linux ARM"
+ ID=archarm
+ ID_LIKE=arch
+ """,
+ version.DistributionInfo(
+ id='archarm', parsed=version.Distribution.arch,
+ version=None, pretty='Arch Linux ARM')),
+ # Alpine
+ ("""
+ NAME="Alpine Linux"
+ ID=alpine
+ VERSION_ID=3.12_alpha20200122
+ PRETTY_NAME="Alpine Linux edge"
+ """,
+ version.DistributionInfo(
+ id='alpine', parsed=version.Distribution.alpine,
+ version=utils.VersionNumber(3, 12), pretty='Alpine Linux edge')),
+ # EndeavourOS
+ ("""
+ NAME="EndeavourOS"
+ PRETTY_NAME="EndeavourOS"
+ ID=endeavouros
+ ID_LIKE=arch
+ BUILD_ID=rolling
+ DOCUMENTATION_URL="https://endeavouros.com/wiki/"
+ LOGO=endeavouros
+ """,
+ version.DistributionInfo(
+ id='endeavouros', parsed=version.Distribution.arch,
+ version=None, pretty='EndeavourOS')),
+ # Manjaro ARM
+ ("""
+ NAME="Manjaro-ARM"
+ ID=manjaro-arm
+ ID_LIKE=manjaro arch
+ PRETTY_NAME="Manjaro ARM"
+ """,
+ version.DistributionInfo(
+ id='manjaro-arm', parsed=version.Distribution.manjaro,
+ version=None, pretty='Manjaro ARM')),
+ # Artix Linux
+ ("""
+ NAME="Artix Linux"
+ PRETTY_NAME="Artix Linux"
+ ID=artix
+ """,
+ version.DistributionInfo(
+ id='artix', parsed=version.Distribution.arch,
+ version=None, pretty='Artix Linux')),
+ # NixOS
+ ("""
+ NAME=NixOS
+ ID=nixos
+ VERSION="21.03pre268206.536fe36e23a (Okapi)"
+ VERSION_CODENAME=okapi
+ VERSION_ID="21.03pre268206.536fe36e23a"
+ PRETTY_NAME="NixOS 21.03 (Okapi)"
+ """,
+ version.DistributionInfo(
+ id='nixos', parsed=version.Distribution.nixos,
+ version=utils.VersionNumber(21, 3),
+ pretty='NixOS 21.03 (Okapi)')),
+ # SolusOS
+ ("""
+ NAME="Solus"
+ VERSION="4.2"
+ ID="solus"
+ VERSION_CODENAME=fortitude
+ VERSION_ID="4.2"
+ PRETTY_NAME="Solus 4.2 Fortitude"
+ """,
+ version.DistributionInfo(
+ id='solus', parsed=version.Distribution.solus,
+ version=utils.VersionNumber(4, 2),
+ pretty='Solus 4.2 Fortitude')),
# KDE Platform
("""
NAME=KDE
@@ -185,27 +273,27 @@ from qutebrowser.browser import pdfjs
VERSION_ID="5.12"
ID=org.kde.Platform
""",
- version.DistributionInfo(
- id='org.kde.Platform', parsed=version.Distribution.kde_flatpak,
- version=utils.parse_version('5.12'),
- pretty='KDE')),
+ version.DistributionInfo(
+ id='org.kde.Platform', parsed=version.Distribution.kde_flatpak,
+ version=utils.VersionNumber(5, 12),
+ pretty='KDE')),
# No PRETTY_NAME
("""
NAME="Tux"
ID=tux
- """,
- version.DistributionInfo(
- id='tux', parsed=version.Distribution.unknown,
- version=None, pretty='Tux')),
+ """,
+ version.DistributionInfo(
+ id='tux', parsed=version.Distribution.unknown,
+ version=None, pretty='Tux')),
# Invalid multi-line value
("""
ID=tux
PRETTY_NAME="Multiline
Text"
- """,
- version.DistributionInfo(
- id='tux', parsed=version.Distribution.unknown,
- version=None, pretty='Multiline')),
+ """,
+ version.DistributionInfo(
+ id='tux', parsed=version.Distribution.unknown,
+ version=None, pretty='Multiline')),
])
def test_distribution(tmpdir, monkeypatch, os_release, expected):
os_release_file = tmpdir / 'os-release'
@@ -220,7 +308,7 @@ def test_distribution(tmpdir, monkeypatch, os_release, expected):
(None, False),
(version.DistributionInfo(
id='org.kde.Platform', parsed=version.Distribution.kde_flatpak,
- version=utils.parse_version('5.12'),
+ version=utils.VersionNumber(5, 12),
pretty='Unknown'), True),
(version.DistributionInfo(
id='arch', parsed=version.Distribution.arch, version=None,
@@ -538,70 +626,11 @@ def test_path_info(monkeypatch, equal):
assert pathinfo['system data'] == 'SYSTEM DATA PATH'
-class ImportFake:
-
- """A fake for __import__ which is used by the import_fake fixture.
-
- Attributes:
- modules: A dict mapping module names to bools. If True, the import will
- succeed. Otherwise, it'll fail with ImportError.
- version_attribute: The name to use in the fake modules for the version
- attribute.
- version: The version to use for the modules.
- _real_import: Saving the real __import__ builtin so the imports can be
- done normally for modules not in self. modules.
- """
-
- def __init__(self):
- self.modules = collections.OrderedDict(
- [(mod, True) for mod in version.MODULE_INFO])
- self.version_attribute = '__version__'
- self.version = '1.2.3'
- self._real_import = builtins.__import__
- self._real_importlib_import = importlib.import_module
-
- def _do_import(self, name):
- """Helper for fake_import and fake_importlib_import to do the work.
-
- Return:
- The imported fake module, or None if normal importing should be
- used.
- """
- if name not in self.modules:
- # Not one of the modules to test -> use real import
- return None
- elif self.modules[name]:
- ns = types.SimpleNamespace()
- if self.version_attribute is not None:
- setattr(ns, self.version_attribute, self.version)
- return ns
- else:
- raise ImportError("Fake ImportError for {}.".format(name))
-
- def fake_import(self, name, *args, **kwargs):
- """Fake for the builtin __import__."""
- module = self._do_import(name)
- if module is not None:
- return module
- else:
- return self._real_import(name, *args, **kwargs)
-
- def fake_importlib_import(self, name):
- """Fake for importlib.import_module."""
- module = self._do_import(name)
- if module is not None:
- return module
- else:
- return self._real_importlib_import(name)
-
-
@pytest.fixture
-def import_fake(monkeypatch):
+def import_fake(stubs, monkeypatch):
"""Fixture to patch imports using ImportFake."""
- fake = ImportFake()
- monkeypatch.setattr(builtins, '__import__', fake.fake_import)
- monkeypatch.setattr(version.importlib, 'import_module',
- fake.fake_importlib_import)
+ fake = stubs.ImportFake({mod: True for mod in version.MODULE_INFO}, monkeypatch)
+ fake.patch()
return fake
@@ -869,6 +898,103 @@ class TestPDFJSVersion:
assert ver.split()[0] not in ['no', 'unknown'], ver
+class TestWebEngineVersions:
+
+ @pytest.mark.parametrize('version, expected', [
+ (
+ version.WebEngineVersions(
+ webengine=utils.VersionNumber(5, 15, 2),
+ chromium=None,
+ source='UA'),
+ "QtWebEngine 5.15.2",
+ ),
+ (
+ version.WebEngineVersions(
+ webengine=utils.VersionNumber(5, 15, 2),
+ chromium='87.0.4280.144',
+ source='UA'),
+ "QtWebEngine 5.15.2, Chromium 87.0.4280.144",
+ ),
+ (
+ version.WebEngineVersions(
+ webengine=utils.VersionNumber(5, 15, 2),
+ chromium='87.0.4280.144',
+ source='faked'),
+ "QtWebEngine 5.15.2, Chromium 87.0.4280.144 (from faked)",
+ ),
+ ])
+ def test_str(self, version, expected):
+ assert str(version) == expected
+
+ def test_from_ua(self):
+ ua = websettings.UserAgent(
+ os_info='X11; Linux x86_64',
+ webkit_version='537.36',
+ upstream_browser_key='Chrome',
+ upstream_browser_version='83.0.4103.122',
+ qt_key='QtWebEngine',
+ qt_version='5.15.2',
+ )
+ expected = version.WebEngineVersions(
+ webengine=utils.VersionNumber(5, 15, 2),
+ chromium='83.0.4103.122',
+ source='UA',
+ )
+ assert version.WebEngineVersions.from_ua(ua) == expected
+
+ def test_from_elf(self):
+ elf_version = elf.Versions(webengine='5.15.2', chromium='83.0.4103.122')
+ expected = version.WebEngineVersions(
+ webengine=utils.VersionNumber(5, 15, 2),
+ chromium='83.0.4103.122',
+ source='ELF',
+ )
+ assert version.WebEngineVersions.from_elf(elf_version) == expected
+
+ @pytest.mark.parametrize('qt_version, chromium_version', [
+ ('5.12.10', '69.0.3497.128'),
+ ('5.14.2', '77.0.3865.129'),
+ ('5.15.1', '80.0.3987.163'),
+ ('5.15.2', '83.0.4103.122'),
+ ])
+ def test_from_pyqt(self, qt_version, chromium_version):
+ expected = version.WebEngineVersions(
+ webengine=utils.parse_version(qt_version),
+ chromium=chromium_version,
+ source='PyQt',
+ )
+ assert version.WebEngineVersions.from_pyqt(qt_version) == expected
+
+ def test_real_chromium_version(self, qapp):
+ """Compare the inferred Chromium version with the real one."""
+ if '.dev' in PYQT_VERSION_STR:
+ pytest.skip("dev version of PyQt5")
+
+ try:
+ from PyQt5.QtWebEngine import PYQT_WEBENGINE_VERSION_STR
+ except ImportError as e:
+ # QtWebKit or QtWebEngine < 5.13
+ pytest.skip(str(e))
+
+ pyqt_webengine_version = version._get_pyqt_webengine_qt_version()
+ if pyqt_webengine_version is None:
+ pyqt_webengine_version = PYQT_WEBENGINE_VERSION_STR
+
+ versions = version.WebEngineVersions.from_pyqt(pyqt_webengine_version)
+
+ if pyqt_webengine_version == '5.15.3':
+ # Transient situation - we expect to get QtWebEngine 5.15.3 soon,
+ # so this will line up again.
+ assert versions.chromium == '87.0.4280.144'
+ pytest.xfail("Transient situation")
+ else:
+ from qutebrowser.browser.webengine import webenginesettings
+ webenginesettings.init_user_agent()
+ expected = webenginesettings.parsed_user_agent.upstream_browser_version
+
+ assert versions.chromium == expected
+
+
class FakeQSslSocket:
"""Fake for the QSslSocket Qt class.
@@ -902,25 +1028,19 @@ class TestChromiumVersion:
@pytest.fixture(autouse=True)
def clear_parsed_ua(self, monkeypatch):
+ pytest.importorskip('PyQt5.QtWebEngineWidgets')
if version.webenginesettings is not None:
# Not available with QtWebKit
monkeypatch.setattr(version.webenginesettings, 'parsed_user_agent', None)
def test_fake_ua(self, monkeypatch, caplog):
- pytest.importorskip('PyQt5.QtWebEngineWidgets')
-
ver = '77.0.3865.98'
version.webenginesettings._init_user_agent_str(
_QTWE_USER_AGENT.format(ver))
- assert version._chromium_version() == ver
-
- def test_no_webengine(self, monkeypatch):
- monkeypatch.setattr(version, 'webenginesettings', None)
- assert version._chromium_version() == 'unavailable'
+ assert version.qtwebengine_versions().chromium == ver
def test_prefers_saved_user_agent(self, monkeypatch):
- pytest.importorskip('PyQt5.QtWebEngineWidgets')
version.webenginesettings._init_user_agent_str(_QTWE_USER_AGENT)
class FakeProfile:
@@ -930,17 +1050,69 @@ class TestChromiumVersion:
monkeypatch.setattr(version.webenginesettings, 'QWebEngineProfile',
FakeProfile())
- version._chromium_version()
+ version.qtwebengine_versions()
def test_unpatched(self, qapp, cache_tmpdir, data_tmpdir, config_stub):
- pytest.importorskip('PyQt5.QtWebEngineWidgets')
- unexpected = ['', 'unknown', 'unavailable', 'avoided']
- assert version._chromium_version() not in unexpected
+ assert version.qtwebengine_versions().chromium is not None
def test_avoided(self, monkeypatch):
- pytest.importorskip('PyQt5.QtWebEngineWidgets')
- monkeypatch.setattr(objects, 'debug_flags', ['avoid-chromium-init'])
- assert version._chromium_version() == 'avoided'
+ versions = version.qtwebengine_versions(avoid_init=True)
+ assert versions.source in ['ELF', 'importlib', 'PyQt', 'Qt']
+
+ @pytest.fixture
+ def patch_elf_fail(self, monkeypatch):
+ """Simulate parsing the version from ELF to fail."""
+ monkeypatch.setattr(elf, 'parse_webenginecore', lambda: None)
+
+ @pytest.fixture
+ def patch_old_pyqt(self, monkeypatch):
+ """Simulate an old PyQt without PYQT_WEBENGINE_VERSION_STR."""
+ monkeypatch.setattr(version, 'PYQT_WEBENGINE_VERSION_STR', None)
+
+ @pytest.fixture
+ def patch_no_importlib(self, monkeypatch, stubs):
+ """Simulate missing importlib modules."""
+ import_fake = stubs.ImportFake({
+ 'importlib_metadata': False,
+ 'importlib.metadata': False,
+ }, monkeypatch)
+ import_fake.patch()
+
+ @pytest.fixture
+ def patch_importlib_no_package(self, monkeypatch):
+ """Simulate importlib not finding PyQtWebEngine-Qt."""
+ try:
+ import importlib.metadata as importlib_metadata
+ except ImportError:
+ importlib_metadata = pytest.importorskip("importlib_metadata")
+
+ def _fake_version(name):
+ assert name == 'PyQtWebEngine-Qt'
+ raise importlib_metadata.PackageNotFoundError(name)
+
+ monkeypatch.setattr(importlib_metadata, 'version', _fake_version)
+
+ @pytest.mark.parametrize('patches, sources', [
+ (['elf_fail'], ['importlib', 'PyQt', 'Qt']),
+ (['elf_fail', 'old_pyqt'], ['importlib', 'Qt']),
+ (['elf_fail', 'no_importlib'], ['PyQt', 'Qt']),
+ (['elf_fail', 'no_importlib', 'old_pyqt'], ['Qt']),
+ (['elf_fail', 'importlib_no_package'], ['PyQt', 'Qt']),
+ (['elf_fail', 'importlib_no_package', 'old_pyqt'], ['Qt']),
+ ], ids=','.join)
+ def test_simulated(self, request, patches, sources):
+ """Test various simulated error conditions.
+
+ This dynamically gets a list of fixtures (above) to do the patching. It then
+ checks whether the version it got is from one of the expected sources. Depending
+ on the environment this test is run in, some sources might fail "naturally",
+ i.e. without any patching related to them.
+ """
+ for patch in patches:
+ request.getfixturevalue(f'patch_{patch}')
+
+ versions = version.qtwebengine_versions(avoid_init=True)
+ assert versions.source in sources
@dataclasses.dataclass
@@ -1014,11 +1186,13 @@ def test_version_info(params, stubs, monkeypatch, config_stub):
'autoconfig_loaded': "yes" if params.autoconfig_loaded else "no",
}
- ua = _QTWE_USER_AGENT.format('CHROMIUMVERSION')
- if version.webenginesettings is None:
- patches['_chromium_version'] = lambda: 'CHROMIUMVERSION'
- else:
- version.webenginesettings._init_user_agent_str(ua)
+ patches['qtwebengine_versions'] = (
+ lambda avoid_init: version.WebEngineVersions(
+ webengine=utils.VersionNumber(1, 2, 3),
+ chromium=None,
+ source='faked',
+ )
+ )
if params.config_py_loaded:
substitutions["config_py_loaded"] = "{} has been loaded".format(
@@ -1034,7 +1208,7 @@ def test_version_info(params, stubs, monkeypatch, config_stub):
else:
monkeypatch.delattr(version, 'qtutils.qWebKitVersion', raising=False)
patches['objects.backend'] = usertypes.Backend.QtWebEngine
- substitutions['backend'] = 'QtWebEngine (Chromium CHROMIUMVERSION)'
+ substitutions['backend'] = 'QtWebEngine 1.2.3 (from faked)'
if params.known_distribution:
patches['distribution'] = lambda: version.DistributionInfo(
diff --git a/tests/unit/utils/usertypes/test_question.py b/tests/unit/utils/usertypes/test_question.py
index dbb34c47f..5e3731109 100644
--- a/tests/unit/utils/usertypes/test_question.py
+++ b/tests/unit/utils/usertypes/test_question.py
@@ -53,14 +53,14 @@ def test_done(mode, answer, signal_names, question, qtbot):
question.mode = mode
question.answer = answer
signals = [getattr(question, name) for name in signal_names]
- with qtbot.waitSignals(signals, order='strict'):
+ with qtbot.wait_signals(signals, order='strict'):
question.done()
assert not question.is_aborted
def test_cancel(question, qtbot):
"""Test Question.cancel()."""
- with qtbot.waitSignals([question.cancelled, question.completed],
+ with qtbot.wait_signals([question.cancelled, question.completed],
order='strict'):
question.cancel()
assert not question.is_aborted
@@ -68,7 +68,7 @@ def test_cancel(question, qtbot):
def test_abort(question, qtbot):
"""Test Question.abort()."""
- with qtbot.waitSignals([question.aborted, question.completed],
+ with qtbot.wait_signals([question.aborted, question.completed],
order='strict'):
question.abort()
assert question.is_aborted
diff --git a/tests/unit/utils/usertypes/test_timer.py b/tests/unit/utils/usertypes/test_timer.py
index 37c56b653..4dc85b06f 100644
--- a/tests/unit/utils/usertypes/test_timer.py
+++ b/tests/unit/utils/usertypes/test_timer.py
@@ -70,13 +70,13 @@ def test_start_overflow():
def test_timeout_start(qtbot):
"""Make sure the timer works with start()."""
t = usertypes.Timer()
- with qtbot.waitSignal(t.timeout, timeout=3000):
+ with qtbot.wait_signal(t.timeout, timeout=3000):
t.start(200)
def test_timeout_set_interval(qtbot):
"""Make sure the timer works with setInterval()."""
t = usertypes.Timer()
- with qtbot.waitSignal(t.timeout, timeout=3000):
+ with qtbot.wait_signal(t.timeout, timeout=3000):
t.setInterval(200)
t.start()