summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/end2end/features/conftest.py6
-rw-r--r--tests/end2end/features/downloads.feature1
-rw-r--r--tests/end2end/features/editor.feature2
-rw-r--r--tests/end2end/features/hints.feature1
-rw-r--r--tests/end2end/features/keyinput.feature3
-rw-r--r--tests/end2end/features/misc.feature2
-rw-r--r--tests/end2end/features/prompts.feature2
-rw-r--r--tests/end2end/fixtures/webserver.py4
-rw-r--r--tests/end2end/test_invocations.py1
-rw-r--r--tests/unit/browser/test_pdfjs.py4
-rw-r--r--tests/unit/browser/webengine/test_darkmode.py203
-rw-r--r--tests/unit/completion/test_completer.py2
-rw-r--r--tests/unit/completion/test_completionwidget.py19
-rw-r--r--tests/unit/completion/test_models.py4
-rw-r--r--tests/unit/config/test_configcommands.py2
-rw-r--r--tests/unit/config/test_configfiles.py1
-rw-r--r--tests/unit/config/test_configtypes.py2
-rw-r--r--tests/unit/keyinput/test_modeparsers.py4
-rw-r--r--tests/unit/misc/test_guiprocess.py3
-rw-r--r--tests/unit/misc/test_pakjoy.py2
-rw-r--r--tests/unit/test_qt_machinery.py12
-rw-r--r--tests/unit/utils/test_qtutils.py6
-rw-r--r--tests/unit/utils/test_version.py91
-rw-r--r--tests/unit/utils/usertypes/test_timer.py63
24 files changed, 402 insertions, 38 deletions
diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py
index 082b999b1..b7f112182 100644
--- a/tests/end2end/features/conftest.py
+++ b/tests/end2end/features/conftest.py
@@ -759,3 +759,9 @@ def set_up_fileselector(quteproc, py_proc, tmpdir, kind, files, output_type):
fileselect_cmd = json.dumps([cmd, *args])
quteproc.set_setting('fileselect.handler', 'external')
quteproc.set_setting(f'fileselect.{kind}.command', fileselect_cmd)
+
+
+@bdd.then(bdd.parsers.parse("I run {command}"))
+def run_command_then(quteproc, command):
+ """Run a qutebrowser command."""
+ quteproc.send_cmd(command)
diff --git a/tests/end2end/features/downloads.feature b/tests/end2end/features/downloads.feature
index c2f359f14..a1bbee870 100644
--- a/tests/end2end/features/downloads.feature
+++ b/tests/end2end/features/downloads.feature
@@ -712,3 +712,4 @@ Feature: Downloading things from a website.
And I wait for "Asking question *" in the log
And I run :prompt-fileselect-external
Then the error "Can only launch external fileselect for FilenamePrompt, not LineEditPrompt" should be shown
+ And I run :mode-leave
diff --git a/tests/end2end/features/editor.feature b/tests/end2end/features/editor.feature
index 9ca855d27..018d65b9f 100644
--- a/tests/end2end/features/editor.feature
+++ b/tests/end2end/features/editor.feature
@@ -188,6 +188,8 @@ Feature: Opening external editors
And I run :cmd-edit
Then the error "command must start with one of :/?" should be shown
And "Leaving mode KeyMode.command *" should not be logged
+ And I run :mode-leave
+ And "Leaving mode KeyMode.command *" should be logged
## select single file
diff --git a/tests/end2end/features/hints.feature b/tests/end2end/features/hints.feature
index ddf42132f..b2a549fb5 100644
--- a/tests/end2end/features/hints.feature
+++ b/tests/end2end/features/hints.feature
@@ -279,6 +279,7 @@ Feature: Using hints
When I open data/hints/iframe_scroll.html
And I wait for "* simple loaded" in the log
And I hint with args "all normal" and follow a
+ And I wait for "Clicked non-editable element!" in the log
And I run :scroll bottom
And I hint with args "links normal" and follow a
Then "navigation request: url http://localhost:*/data/hello2.txt (current http://localhost:*/data/hints/iframe_scroll.html), type link_clicked, *" should be logged
diff --git a/tests/end2end/features/keyinput.feature b/tests/end2end/features/keyinput.feature
index 3ab5d2434..f7f354def 100644
--- a/tests/end2end/features/keyinput.feature
+++ b/tests/end2end/features/keyinput.feature
@@ -32,6 +32,7 @@ Feature: Keyboard input
Scenario: :fake-key sending key to the website
When I open data/keyinput/log.html
+ And I wait 0.01s
And I run :fake-key x
Then the javascript message "key press: 88" should be logged
And the javascript message "key release: 88" should be logged
@@ -48,12 +49,14 @@ Feature: Keyboard input
Scenario: :fake-key sending special key to the website
When I open data/keyinput/log.html
+ And I wait 0.01s
And I run :fake-key <Escape>
Then the javascript message "key press: 27" should be logged
And the javascript message "key release: 27" should be logged
Scenario: :fake-key sending keychain to the website
When I open data/keyinput/log.html
+ And I wait 0.01s
And I run :fake-key x<greater>y<less>" "
Then the javascript message "key press: 88" should be logged
And the javascript message "key release: 88" should be logged
diff --git a/tests/end2end/features/misc.feature b/tests/end2end/features/misc.feature
index 90ce5334a..6e606a195 100644
--- a/tests/end2end/features/misc.feature
+++ b/tests/end2end/features/misc.feature
@@ -507,7 +507,7 @@ Feature: Various utility commands.
Scenario: Clicking on focused element
When I open data/click_element.html
- And I run :fake-key <Tab>
+ And I run :jseval document.getElementById("qute-input").focus()
And I wait for the javascript message "qute-input focused"
And I run :click-element focused
Then "Entering mode KeyMode.insert (reason: clicking input)" should be logged
diff --git a/tests/end2end/features/prompts.feature b/tests/end2end/features/prompts.feature
index 32bdd29e7..43199fa3b 100644
--- a/tests/end2end/features/prompts.feature
+++ b/tests/end2end/features/prompts.feature
@@ -413,7 +413,7 @@ Feature: Prompts
}
@qtwebengine_skip
- Scenario: Cancellling webpage authentication with QtWebKit
+ Scenario: Cancelling webpage authentication with QtWebKit
When I open basic-auth/user6/password6 without waiting
And I wait for a prompt
And I run :mode-leave
diff --git a/tests/end2end/fixtures/webserver.py b/tests/end2end/fixtures/webserver.py
index 924cb520b..f8e28cc40 100644
--- a/tests/end2end/fixtures/webserver.py
+++ b/tests/end2end/fixtures/webserver.py
@@ -115,8 +115,8 @@ class ExpectedRequest:
def is_ignored_webserver_message(line: str) -> bool:
return testutils.pattern_match(
pattern=(
- "Client ('127.0.0.1', *) lost — peer dropped the TLS connection suddenly, "
- "during handshake: (1, '[SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] ssl/tls "
+ "Client ('127.0.0.1', *) lost * peer dropped the TLS connection suddenly, "
+ "during handshake: (1, '[SSL: SSLV3_ALERT_CERTIFICATE_UNKNOWN] * "
"alert certificate unknown (_ssl.c:*)')"
),
value=line,
diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py
index a55efb129..ccf4534dd 100644
--- a/tests/end2end/test_invocations.py
+++ b/tests/end2end/test_invocations.py
@@ -905,6 +905,7 @@ def test_sandboxing(
f"{bpf_text} supports TSYNC": "Yes" if has_seccomp else "No",
f"{yama_text} (Broker)": "Yes" if has_yama else "No",
+ # pylint: disable-next=used-before-assignment
f"{yama_text} (Non-broker)": "Yes" if has_yama_non_broker else "No",
}
diff --git a/tests/unit/browser/test_pdfjs.py b/tests/unit/browser/test_pdfjs.py
index cb5c26229..867e8e1de 100644
--- a/tests/unit/browser/test_pdfjs.py
+++ b/tests/unit/browser/test_pdfjs.py
@@ -9,7 +9,7 @@ import pytest
from qutebrowser.qt.core import QUrl
from qutebrowser.browser import pdfjs
-from qutebrowser.utils import urlmatch
+from qutebrowser.utils import urlmatch, utils
pytestmark = [pytest.mark.usefixtures('data_tmpdir')]
@@ -154,6 +154,8 @@ def test_read_from_system(names, expected_name, tmpdir):
expected = (b'text2', str(file2))
elif expected_name is None:
expected = (None, None)
+ else:
+ raise utils.Unreachable(expected_name)
assert pdfjs._read_from_system(str(tmpdir), names) == expected
diff --git a/tests/unit/browser/webengine/test_darkmode.py b/tests/unit/browser/webengine/test_darkmode.py
index bda05feb8..2f7021e95 100644
--- a/tests/unit/browser/webengine/test_darkmode.py
+++ b/tests/unit/browser/webengine/test_darkmode.py
@@ -7,6 +7,7 @@ import logging
from typing import List, Tuple
import pytest
+QWebEngineSettings = pytest.importorskip("qutebrowser.qt.webenginecore").QWebEngineSettings
from qutebrowser.config import configdata
from qutebrowser.utils import usertypes, version, utils
@@ -28,6 +29,150 @@ def gentoo_versions():
)
+class TestSetting:
+
+ @pytest.mark.parametrize("value, mapping, expected", [
+ ("val", None, ("key", "val")),
+ (5, None, ("key", "5")),
+ (True, darkmode._BOOLS, ("key", "true")),
+ ("excluded", {"excluded": None}, None),
+ ])
+ def test_chromium_tuple(self, value, mapping, expected):
+ setting = darkmode._Setting(option="opt", chromium_key="key", mapping=mapping)
+ assert setting.chromium_tuple(value) == expected
+
+ def test_with_prefix(self):
+ mapping = {"val": "mapped"}
+ setting = darkmode._Setting(option="opt", chromium_key="key", mapping=mapping)
+ prefixed = setting.with_prefix("prefix")
+ assert prefixed == darkmode._Setting(
+ option="opt", chromium_key="prefixkey", mapping=mapping
+ )
+
+
+class TestDefinition:
+
+ @pytest.fixture
+ def setting1(self) -> darkmode._Setting:
+ return darkmode._Setting("opt1", "key1")
+
+ @pytest.fixture
+ def setting2(self) -> darkmode._Setting:
+ return darkmode._Setting("opt2", "key2")
+
+ @pytest.fixture
+ def setting3(self) -> darkmode._Setting:
+ return darkmode._Setting("opt3", "key3")
+
+ @pytest.fixture
+ def definition(
+ self, setting1: darkmode._Setting, setting2: darkmode._Setting
+ ) -> darkmode._Definition:
+ return darkmode._Definition(setting1, setting2, mandatory=set(), prefix="")
+
+ def _get_settings(self, definition: darkmode._Definition) -> List[darkmode._Setting]:
+ return [setting for _key, setting in definition.prefixed_settings()]
+
+ @pytest.mark.parametrize("prefix", ["", "prefix"])
+ def test_prefixed_settings(
+ self,
+ prefix: str,
+ definition: darkmode._Definition,
+ setting1: darkmode._Setting,
+ setting2: darkmode._Setting,
+ ):
+ assert definition.prefix == "" # default value
+ definition.prefix = prefix
+ prefixed = self._get_settings(definition)
+ assert prefixed == [setting1.with_prefix(prefix), setting2.with_prefix(prefix)]
+
+ def test_switch_names(
+ self,
+ definition: darkmode._Definition,
+ setting1: darkmode._Setting,
+ setting2: darkmode._Setting,
+ setting3: darkmode._Setting,
+ ):
+ switch_names = {
+ setting1.option: "opt1-switch",
+ None: "default-switch",
+ }
+ definition = darkmode._Definition(
+ setting1,
+ setting2,
+ setting3,
+ mandatory=set(),
+ prefix="",
+ switch_names=switch_names,
+ )
+ settings = list(definition.prefixed_settings())
+ assert settings == [
+ ("opt1-switch", setting1),
+ ("default-switch", setting2),
+ ("default-switch", setting3),
+ ]
+
+ def test_copy_remove_setting(
+ self,
+ definition: darkmode._Definition,
+ setting1: darkmode._Setting,
+ setting2: darkmode._Setting,
+ ):
+ copy = definition.copy_remove_setting(setting2.option)
+ orig_settings = self._get_settings(definition)
+ copy_settings = self._get_settings(copy)
+ assert orig_settings == [setting1, setting2]
+ assert copy_settings == [setting1]
+
+ def test_copy_remove_setting_not_found(self, definition: darkmode._Definition):
+ with pytest.raises(ValueError, match="Setting not-found not found in "):
+ definition.copy_remove_setting("not-found")
+
+ def test_copy_add_setting(
+ self,
+ definition: darkmode._Definition,
+ setting1: darkmode._Setting,
+ setting2: darkmode._Setting,
+ setting3: darkmode._Setting,
+ ):
+ copy = definition.copy_add_setting(setting3)
+ orig_settings = self._get_settings(definition)
+ copy_settings = self._get_settings(copy)
+ assert orig_settings == [setting1, setting2]
+ assert copy_settings == [setting1, setting2, setting3]
+
+ def test_copy_add_setting_already_exists(
+ self,
+ definition: darkmode._Definition,
+ setting1: darkmode._Setting,
+ setting2: darkmode._Setting,
+ ):
+ copy = definition.copy_add_setting(setting2)
+ orig_settings = self._get_settings(definition)
+ copy_settings = self._get_settings(copy)
+ assert orig_settings == [setting1, setting2]
+ assert copy_settings == [setting1, setting2, setting2]
+
+ def test_copy_replace_setting(
+ self,
+ definition: darkmode._Definition,
+ setting1: darkmode._Setting,
+ setting2: darkmode._Setting,
+ ):
+ replaced = darkmode._Setting(setting2.option, setting2.chromium_key + "-replaced")
+ copy = definition.copy_replace_setting(setting2.option, replaced.chromium_key)
+ orig_settings = self._get_settings(definition)
+ copy_settings = self._get_settings(copy)
+ assert orig_settings == [setting1, setting2]
+ assert copy_settings == [setting1, replaced]
+
+ def test_copy_replace_setting_not_found(
+ self, definition: darkmode._Definition, setting3: darkmode._Setting
+ ):
+ with pytest.raises(ValueError, match="Setting opt3 not found in "):
+ definition.copy_replace_setting(setting3.option, setting3.chromium_key)
+
+
@pytest.mark.parametrize('value, webengine_version, expected', [
# Auto
("auto", "5.15.2", [("preferredColorScheme", "2")]), # QTBUG-89753
@@ -122,16 +267,49 @@ QT_64_SETTINGS = {
'dark-mode-settings': [
('InversionAlgorithm', '1'),
('ImagePolicy', '2'),
+ ('ForegroundBrightnessThreshold', '100'), # name changed
+ ],
+}
+
+
+QT_66_SETTINGS = {
+ 'blink-settings': [('forceDarkModeEnabled', 'true')],
+ 'dark-mode-settings': [
+ ('InversionAlgorithm', '1'),
+ ('ImagePolicy', '2'),
('ForegroundBrightnessThreshold', '100'),
+ ("ImageClassifierPolicy", "0"), # added
],
}
+QT_67_SETTINGS = {
+ # blink-settings removed
+ 'dark-mode-settings': [
+ ('InversionAlgorithm', '1'),
+ ('ImagePolicy', '2'),
+ ('ForegroundBrightnessThreshold', '100'),
+ ("ImageClassifierPolicy", "0"),
+ ],
+}
-@pytest.mark.parametrize('qversion, expected', [
- ('5.15.2', QT_515_2_SETTINGS),
- ('5.15.3', QT_515_3_SETTINGS),
- ('6.4', QT_64_SETTINGS),
-])
+
+@pytest.mark.parametrize(
+ "qversion, expected",
+ [
+ ("5.15.2", QT_515_2_SETTINGS),
+ ("5.15.3", QT_515_3_SETTINGS),
+ ("6.4", QT_64_SETTINGS),
+ ("6.6", QT_66_SETTINGS),
+ pytest.param(
+ "6.7",
+ QT_67_SETTINGS,
+ marks=pytest.mark.skipif(
+ not hasattr(QWebEngineSettings.WebAttribute, "ForceDarkMode"),
+ reason="needs PyQt 6.7",
+ ),
+ ),
+ ],
+)
def test_qt_version_differences(config_stub, qversion, expected):
settings = {
'enabled': True,
@@ -213,6 +391,16 @@ def test_variant(webengine_version, expected):
assert darkmode._variant(versions) == expected
+def test_variant_qt67() -> None:
+ versions = version.WebEngineVersions.from_pyqt("6.7.0")
+ # We can't monkeypatch the enum, so compare against the real situation
+ if hasattr(QWebEngineSettings.WebAttribute, "ForceDarkMode"):
+ expected = darkmode.Variant.qt_67
+ else:
+ expected = darkmode.Variant.qt_66
+ assert darkmode._variant(versions) == expected
+
+
def test_variant_gentoo_workaround(gentoo_versions):
assert darkmode._variant(gentoo_versions) == darkmode.Variant.qt_515_3
@@ -257,8 +445,9 @@ def test_options(configdata_init):
if not name.startswith('colors.webpage.darkmode.'):
continue
- assert not opt.supports_pattern, name
- assert opt.restart, name
+ if name != 'colors.webpage.darkmode.enabled':
+ assert not opt.supports_pattern, name
+ assert opt.restart, name
if opt.backends:
# On older Qt versions, this is an empty list.
diff --git a/tests/unit/completion/test_completer.py b/tests/unit/completion/test_completer.py
index add81c04d..5a012c634 100644
--- a/tests/unit/completion/test_completer.py
+++ b/tests/unit/completion/test_completer.py
@@ -55,7 +55,7 @@ def completion_widget_stub():
def completer_obj(qtbot, status_command_stub, config_stub, monkeypatch, stubs,
completion_widget_stub):
"""Create the completer used for testing."""
- monkeypatch.setattr(completer, 'QTimer', stubs.InstaTimer)
+ monkeypatch.setattr(completer.usertypes, 'Timer', stubs.InstaTimer)
config_stub.val.completion.show = 'auto'
return completer.Completer(cmd=status_command_stub, win_id=0,
parent=completion_widget_stub)
diff --git a/tests/unit/completion/test_completionwidget.py b/tests/unit/completion/test_completionwidget.py
index 4ab9dfca0..387339f4f 100644
--- a/tests/unit/completion/test_completionwidget.py
+++ b/tests/unit/completion/test_completionwidget.py
@@ -7,7 +7,7 @@
from unittest import mock
import pytest
-from qutebrowser.qt.core import QRect
+from qutebrowser.qt.core import QRect, QItemSelectionModel
from qutebrowser.completion import completionwidget
from qutebrowser.completion.models import completionmodel, listcategory
@@ -285,6 +285,23 @@ def test_completion_show(show, rows, quick_complete, completionview, model,
assert not completionview.isVisible()
+def test_completion_selection_clear_no_model(completionview):
+ completionview.show()
+ completionview.on_clear_completion_selection()
+ assert completionview.isVisible() is False
+
+
+def test_completion_selection_clear_with_model(completionview, mocker):
+ selmod = mock.Mock(spec=QItemSelectionModel)
+ mocker.patch.object(completionview, "selectionModel", return_value=selmod)
+ completionview.show()
+ completionview.on_clear_completion_selection()
+
+ assert completionview.isVisible() is False
+ selmod.clearSelection.assert_called_once()
+ selmod.clearCurrentIndex.assert_called_once()
+
+
def test_completion_item_del(completionview, model):
"""Test that completion_item_del invokes delete_cur_item in the model."""
func = mock.Mock(spec=[])
diff --git a/tests/unit/completion/test_models.py b/tests/unit/completion/test_models.py
index e0bce8f04..ad73d9eb5 100644
--- a/tests/unit/completion/test_models.py
+++ b/tests/unit/completion/test_models.py
@@ -29,7 +29,7 @@ from qutebrowser.completion import completer
from qutebrowser.completion.models import (
configmodel, listcategory, miscmodels, urlmodel, filepathcategory)
from qutebrowser.config import configdata, configtypes
-from qutebrowser.utils import usertypes
+from qutebrowser.utils import usertypes, utils
from qutebrowser.mainwindow import tabbedbrowser
@@ -417,6 +417,8 @@ def test_filesystem_completion(qtmodeltester, config_stub, info,
base = '~'
expected_1 = str(pathlib.Path('~') / 'file1.txt')
expected_2 = str(pathlib.Path('~') / 'file2.txt')
+ else:
+ raise utils.Unreachable(method)
config_stub.val.completion.open_categories = ['filesystem']
model = urlmodel.url(info=info)
diff --git a/tests/unit/config/test_configcommands.py b/tests/unit/config/test_configcommands.py
index 10d9e2292..f9dca3e74 100644
--- a/tests/unit/config/test_configcommands.py
+++ b/tests/unit/config/test_configcommands.py
@@ -840,6 +840,8 @@ class TestBind:
func = functools.partial(commands.bind, 0)
elif command == 'unbind':
func = commands.unbind
+ else:
+ raise utils.Unreachable(command)
with pytest.raises(cmdutils.CommandError, match=expected):
func(*args, **kwargs)
diff --git a/tests/unit/config/test_configfiles.py b/tests/unit/config/test_configfiles.py
index cde0a180b..7a47b6cb0 100644
--- a/tests/unit/config/test_configfiles.py
+++ b/tests/unit/config/test_configfiles.py
@@ -399,6 +399,7 @@ class TestYaml:
yaml._save()
if not insert and old_config is None:
+ data = {} # unused
lines = []
else:
data = autoconfig.read()
diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py
index bcd257ed7..09fc4fa75 100644
--- a/tests/unit/config/test_configtypes.py
+++ b/tests/unit/config/test_configtypes.py
@@ -1602,6 +1602,8 @@ class TestDict:
valtype=configtypes.String(),
required_keys=['one', 'two'])
message = 'Required keys .*'
+ else:
+ raise utils.Unreachable(kind)
if ok:
expectation = testutils.nop_contextmanager()
diff --git a/tests/unit/keyinput/test_modeparsers.py b/tests/unit/keyinput/test_modeparsers.py
index 4ba11ed58..6a83d614b 100644
--- a/tests/unit/keyinput/test_modeparsers.py
+++ b/tests/unit/keyinput/test_modeparsers.py
@@ -115,10 +115,12 @@ class TestHintKeyParser:
seq = keyutils.KeySequence.parse(keychain)
assert len(seq) == 2
+ # pylint: disable-next=no-member
match = keyparser.handle(seq[0].to_event())
assert match == QKeySequence.SequenceMatch.PartialMatch
assert hintmanager.keystr == prefix
+ # pylint: disable-next=no-member
match = keyparser.handle(seq[1].to_event())
assert match == QKeySequence.SequenceMatch.ExactMatch
assert hintmanager.keystr == hint
@@ -132,10 +134,12 @@ class TestHintKeyParser:
seq = keyutils.KeySequence.parse('ασ')
assert len(seq) == 2
+ # pylint: disable-next=no-member
match = keyparser.handle(seq[0].to_event())
assert match == QKeySequence.SequenceMatch.PartialMatch
assert hintmanager.keystr == 'a'
+ # pylint: disable-next=no-member
match = keyparser.handle(seq[1].to_event())
assert match == QKeySequence.SequenceMatch.ExactMatch
assert hintmanager.keystr == 'as'
diff --git a/tests/unit/misc/test_guiprocess.py b/tests/unit/misc/test_guiprocess.py
index d1bc6e7c1..7c4ff1a5d 100644
--- a/tests/unit/misc/test_guiprocess.py
+++ b/tests/unit/misc/test_guiprocess.py
@@ -9,7 +9,7 @@ import logging
import signal
import pytest
-from qutebrowser.qt.core import QProcess, QUrl
+from qutebrowser.qt.core import QProcess, QUrl, Qt
from qutebrowser.misc import guiprocess
from qutebrowser.utils import usertypes, utils, version
@@ -534,6 +534,7 @@ def test_str(proc, py_proc):
def test_cleanup(proc, py_proc, qtbot):
+ proc._cleanup_timer.setTimerType(Qt.TimerType.CoarseTimer)
proc._cleanup_timer.setInterval(100)
with qtbot.wait_signal(proc._cleanup_timer.timeout):
diff --git a/tests/unit/misc/test_pakjoy.py b/tests/unit/misc/test_pakjoy.py
index 2dcfbd5b1..59185a380 100644
--- a/tests/unit/misc/test_pakjoy.py
+++ b/tests/unit/misc/test_pakjoy.py
@@ -177,7 +177,7 @@ class TestFindWebengineResources:
def test_nowhere(self, fallback_path: pathlib.Path):
"""Test we raise if we can't find the resources."""
with pytest.raises(
- binparsing.ParseError, match="Couldn't find webengine resources dir"
+ FileNotFoundError, match="Couldn't find webengine resources dir, candidates:\n*"
):
pakjoy._find_webengine_resources()
diff --git a/tests/unit/test_qt_machinery.py b/tests/unit/test_qt_machinery.py
index 25fc83ffd..cf7990393 100644
--- a/tests/unit/test_qt_machinery.py
+++ b/tests/unit/test_qt_machinery.py
@@ -9,7 +9,7 @@ import sys
import html
import argparse
import typing
-from typing import Any, Optional, List, Dict, Union
+from typing import Any, Optional, List, Dict, Union, Type
import dataclasses
import pytest
@@ -45,14 +45,14 @@ def undo_init(monkeypatch: pytest.MonkeyPatch) -> None:
@pytest.mark.parametrize(
- "exception",
+ "exception, base",
[
- machinery.Unavailable(),
- machinery.NoWrapperAvailableError(machinery.SelectionInfo()),
+ (machinery.Unavailable(), ModuleNotFoundError),
+ (machinery.NoWrapperAvailableError(machinery.SelectionInfo()), ImportError),
],
)
-def test_importerror_exceptions(exception: Exception):
- with pytest.raises(ImportError):
+def test_importerror_exceptions(exception: Exception, base: Type[Exception]):
+ with pytest.raises(base):
raise exception
diff --git a/tests/unit/utils/test_qtutils.py b/tests/unit/utils/test_qtutils.py
index c7af3162c..0a3afa416 100644
--- a/tests/unit/utils/test_qtutils.py
+++ b/tests/unit/utils/test_qtutils.py
@@ -758,8 +758,10 @@ class TestPyQIODevice:
# pylint: enable=no-member,useless-suppression
else:
pytest.skip("Needs os.SEEK_HOLE or os.SEEK_DATA available.")
+
pyqiodev.open(QIODevice.OpenModeFlag.ReadOnly)
with pytest.raises(io.UnsupportedOperation):
+ # pylint: disable=possibly-used-before-assignment
pyqiodev.seek(0, whence)
@pytest.mark.flaky
@@ -1116,13 +1118,13 @@ class TestQObjRepr:
assert qtutils.qobj_repr(obj) == expected
def test_class_name(self):
- obj = QTimer()
+ obj = QTimer() # misc: ignore
hidden = sip.cast(obj, QObject)
expected = f"<{self._py_repr(hidden)}, className='QTimer'>"
assert qtutils.qobj_repr(hidden) == expected
def test_both(self):
- obj = QTimer()
+ obj = QTimer() # misc: ignore
obj.setObjectName("Pomodoro")
hidden = sip.cast(obj, QObject)
expected = f"<{self._py_repr(hidden)}, objectName='Pomodoro', className='QTimer'>"
diff --git a/tests/unit/utils/test_version.py b/tests/unit/utils/test_version.py
index 38134b40e..5d2863100 100644
--- a/tests/unit/utils/test_version.py
+++ b/tests/unit/utils/test_version.py
@@ -899,21 +899,45 @@ class TestWebEngineVersions:
webengine=utils.VersionNumber(5, 15, 2),
chromium=None,
source='UA'),
- "QtWebEngine 5.15.2",
+ (
+ "QtWebEngine 5.15.2\n"
+ " (source: UA)"
+ ),
),
(
version.WebEngineVersions(
webengine=utils.VersionNumber(5, 15, 2),
chromium='87.0.4280.144',
source='UA'),
- "QtWebEngine 5.15.2, based on Chromium 87.0.4280.144",
+ (
+ "QtWebEngine 5.15.2\n"
+ " based on Chromium 87.0.4280.144\n"
+ " (source: UA)"
+ ),
),
(
version.WebEngineVersions(
webengine=utils.VersionNumber(5, 15, 2),
chromium='87.0.4280.144',
source='faked'),
- "QtWebEngine 5.15.2, based on Chromium 87.0.4280.144 (from faked)",
+ (
+ "QtWebEngine 5.15.2\n"
+ " based on Chromium 87.0.4280.144\n"
+ " (source: faked)"
+ ),
+ ),
+ (
+ version.WebEngineVersions(
+ webengine=utils.VersionNumber(5, 15, 2),
+ chromium='87.0.4280.144',
+ chromium_security='9000.1',
+ source='faked'),
+ (
+ "QtWebEngine 5.15.2\n"
+ " based on Chromium 87.0.4280.144\n"
+ " with security patches up to 9000.1 (plus any distribution patches)\n"
+ " (source: faked)"
+ ),
),
])
def test_str(self, version, expected):
@@ -950,6 +974,7 @@ class TestWebEngineVersions:
expected = version.WebEngineVersions(
webengine=utils.VersionNumber(5, 15, 2),
chromium='83.0.4103.122',
+ chromium_security='86.0.4240.183',
source='UA',
)
assert version.WebEngineVersions.from_ua(ua) == expected
@@ -959,21 +984,27 @@ class TestWebEngineVersions:
expected = version.WebEngineVersions(
webengine=utils.VersionNumber(5, 15, 2),
chromium='83.0.4103.122',
+ chromium_security='86.0.4240.183',
source='ELF',
)
assert version.WebEngineVersions.from_elf(elf_version) == expected
- @pytest.mark.parametrize('pyqt_version, chromium_version', [
- ('5.15.2', '83.0.4103.122'),
- ('5.15.3', '87.0.4280.144'),
- ('5.15.4', '87.0.4280.144'),
- ('5.15.5', '87.0.4280.144'),
- ('6.2.0', '90.0.4430.228'),
- ('6.3.0', '94.0.4606.126'),
+ @pytest.mark.parametrize('pyqt_version, chromium_version, security_version', [
+ ('5.15.2', '83.0.4103.122', '86.0.4240.183'),
+ ('5.15.3', '87.0.4280.144', '88.0.4324.150'),
+ ('5.15.4', '87.0.4280.144', None),
+ ('5.15.5', '87.0.4280.144', None),
+ ('5.15.6', '87.0.4280.144', None),
+ ('5.15.7', '87.0.4280.144', '94.0.4606.61'),
+ ('6.2.0', '90.0.4430.228', '93.0.4577.63'),
+ ('6.2.99', '90.0.4430.228', None),
+ ('6.3.0', '94.0.4606.126', '99.0.4844.84'),
+ ('6.99.0', None, None),
])
- def test_from_pyqt(self, freezer, pyqt_version, chromium_version):
- if freezer and pyqt_version in ['5.15.3', '5.15.4', '5.15.5']:
+ def test_from_pyqt(self, freezer, pyqt_version, chromium_version, security_version):
+ if freezer and utils.VersionNumber(5, 15, 3) <= utils.VersionNumber.parse(pyqt_version) < utils.VersionNumber(6):
chromium_version = '83.0.4103.122'
+ security_version = '86.0.4240.183'
expected_pyqt_version = '5.15.2'
else:
expected_pyqt_version = pyqt_version
@@ -981,6 +1012,7 @@ class TestWebEngineVersions:
expected = version.WebEngineVersions(
webengine=utils.VersionNumber.parse(expected_pyqt_version),
chromium=chromium_version,
+ chromium_security=security_version,
source='PyQt',
)
assert version.WebEngineVersions.from_pyqt(pyqt_version) == expected
@@ -1024,6 +1056,39 @@ class TestWebEngineVersions:
assert inferred == real
+ def test_real_chromium_security_version(self, qapp):
+ """Check the API for reading the chromium security patch version."""
+ try:
+ from qutebrowser.qt.webenginecore import (
+ qWebEngineChromiumVersion,
+ qWebEngineChromiumSecurityPatchVersion,
+ )
+ except ImportError:
+ pytest.skip("Requires QtWebEngine 6.3+")
+
+ base = utils.VersionNumber.parse(qWebEngineChromiumVersion())
+ security = utils.VersionNumber.parse(qWebEngineChromiumSecurityPatchVersion())
+ assert security >= base
+
+ def test_chromium_security_version_dict(self, qapp):
+ """Check if we infer the QtWebEngine security version properly.
+
+ Note this test mostly tests that our overview in version.py (also
+ intended for human readers) is accurate. The code we call here is never
+ going to be called in real-life situations, as the API is available.
+ """
+ try:
+ from qutebrowser.qt.webenginecore import (
+ qWebEngineVersion,
+ qWebEngineChromiumSecurityPatchVersion,
+ )
+ except ImportError:
+ pytest.skip("Requires QtWebEngine 6.3+")
+
+ inferred = version.WebEngineVersions.from_webengine(
+ qWebEngineVersion(), source="API")
+ assert inferred.chromium_security == qWebEngineChromiumSecurityPatchVersion()
+
class FakeQSslSocket:
@@ -1294,7 +1359,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 1.2.3 (from faked)'
+ substitutions['backend'] = 'QtWebEngine 1.2.3\n (source: faked)'
if params.known_distribution:
patches['distribution'] = lambda: version.DistributionInfo(
diff --git a/tests/unit/utils/usertypes/test_timer.py b/tests/unit/utils/usertypes/test_timer.py
index c02f160b6..6aabc8c04 100644
--- a/tests/unit/utils/usertypes/test_timer.py
+++ b/tests/unit/utils/usertypes/test_timer.py
@@ -4,6 +4,9 @@
"""Tests for Timer."""
+import logging
+import fnmatch
+
import pytest
from qutebrowser.qt.core import QObject
@@ -65,3 +68,63 @@ def test_timeout_set_interval(qtbot):
with qtbot.wait_signal(t.timeout, timeout=3000):
t.setInterval(200)
t.start()
+
+
+@pytest.mark.parametrize(
+ "elapsed_ms, expected",
+ [
+ (0, False),
+ (1, False),
+ (600, True),
+ (999, True),
+ (1000, True),
+ ],
+)
+def test_early_timeout_check(qtbot, mocker, elapsed_ms, expected):
+ time_mock = mocker.patch("time.monotonic", autospec=True)
+
+ t = usertypes.Timer()
+ t.setInterval(1000) # anything long enough to not actually fire
+ time_mock.return_value = 0 # assigned to _start_time in start()
+ t.start()
+ time_mock.return_value = elapsed_ms / 1000 # used for `elapsed`
+
+ assert t.check_timeout_validity() is expected
+
+ t.stop()
+
+
+def test_early_timeout_handler(qtbot, mocker, caplog):
+ time_mock = mocker.patch("time.monotonic", autospec=True)
+
+ t = usertypes.Timer(name="t")
+ t.setInterval(3)
+ t.setSingleShot(True)
+ time_mock.return_value = 0
+ with caplog.at_level(logging.WARNING):
+ with qtbot.wait_signal(t.timeout, timeout=10):
+ t.start()
+ time_mock.return_value = 1 / 1000
+
+ assert len(caplog.messages) == 1
+ assert fnmatch.fnmatch(
+ caplog.messages[-1],
+ "Timer t (id *) triggered too early: interval 3 but only 0.001s passed",
+ )
+
+
+def test_early_manual_fire(qtbot, mocker, caplog):
+ """Same as above but start() never gets called."""
+ time_mock = mocker.patch("time.monotonic", autospec=True)
+
+ t = usertypes.Timer(name="t")
+ t.setInterval(3)
+ t.setSingleShot(True)
+ time_mock.return_value = 0
+ with caplog.at_level(logging.WARNING):
+ with qtbot.wait_signal(t.timeout, timeout=10):
+ t.timeout.emit()
+ time_mock.return_value = 1 / 1000
+
+ assert len(caplog.messages) == 0
+ assert t.check_timeout_validity()