From 7961cf73553847ea265a388b736fffac77dae66a Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 26 May 2021 09:34:37 +0200 Subject: RIP Freenode --- tests/end2end/data/downloads/mhtml/complex/complex.html | 2 +- tests/end2end/data/downloads/mhtml/complex/complex.mht | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'tests') diff --git a/tests/end2end/data/downloads/mhtml/complex/complex.html b/tests/end2end/data/downloads/mhtml/complex/complex.html index b298aa37c..d44e9be0f 100644 --- a/tests/end2end/data/downloads/mhtml/complex/complex.html +++ b/tests/end2end/data/downloads/mhtml/complex/complex.html @@ -91,7 +91,7 @@
...the IRC channel for qutebrowser is #qutebrowser on - irc.freenode.net + irc.libera.chat
diff --git a/tests/end2end/data/downloads/mhtml/complex/complex.mht b/tests/end2end/data/downloads/mhtml/complex/complex.mht index 0467da22f..a458f4dcb 100644 --- a/tests/end2end/data/downloads/mhtml/complex/complex.mht +++ b/tests/end2end/data/downloads/mhtml/complex/complex.mht @@ -108,7 +108,7 @@ aster/doc/contributing.asciidoc"> =20
...the IRC channel for qutebrowser is #qutebrowser on - irc.freenode.net + irc.libera.chat
=20
-- cgit v1.2.3-54-g00ecf From 40477e826c9ec73a8f99177df645094be3ef5ed3 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 May 2021 15:46:35 +0200 Subject: log: Handle JSONLogger in change_console_formatter Fixes #6482 --- qutebrowser/utils/log.py | 14 +++++++++----- tests/end2end/test_invocations.py | 11 +++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) (limited to 'tests') diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index b6f1f3e9b..2fb64eddd 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -363,12 +363,16 @@ def change_console_formatter(level: int) -> None: level: The numeric logging level """ assert console_handler is not None + old_formatter = console_handler.formatter - old_formatter = cast(ColoredFormatter, console_handler.formatter) - console_fmt = get_console_format(level) - console_formatter = ColoredFormatter(console_fmt, DATEFMT, '{', - use_colors=old_formatter.use_colors) - console_handler.setFormatter(console_formatter) + if isinstance(old_formatter, ColoredFormatter): + console_fmt = get_console_format(level) + console_formatter = ColoredFormatter( + console_fmt, DATEFMT, '{', use_colors=old_formatter.use_colors) + console_handler.setFormatter(console_formatter) + else: + # Same format for all levels + assert isinstance(old_formatter, JSONFormatter), old_formatter def qt_message_handler(msg_type: QtCore.QtMsgType, diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index 1ce22b7ea..076ebb2bd 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -743,3 +743,14 @@ def test_unavailable_backend(request, quteproc_new): message=('*qutebrowser tried to start with the Qt* backend but failed ' 'because * could not be imported.*')) line.expected = True + + +def test_json_logging_without_debug(request, quteproc_new): + args = _base_args(request.config) + ['--temp-basedir', ':quit'] + args.remove('--debug') + args.remove('about:blank') # interfers with :quit at the end + + quteproc_new.exit_expected = True + quteproc_new.start(args) + assert not quteproc_new.is_running() + assert not quteproc_new.captured_log -- cgit v1.2.3-54-g00ecf From 1830f784df18057f5e07a59256cc73b5fea91a86 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 28 May 2021 16:50:58 +0200 Subject: Fix tests/lint We need to set XDG_RUNTIME_DIR properly in the tests so that the log is empty. --- qutebrowser/utils/log.py | 2 +- tests/end2end/test_invocations.py | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'tests') diff --git a/qutebrowser/utils/log.py b/qutebrowser/utils/log.py index 2fb64eddd..9cd07e2e3 100644 --- a/qutebrowser/utils/log.py +++ b/qutebrowser/utils/log.py @@ -33,7 +33,7 @@ import json import inspect import argparse from typing import (TYPE_CHECKING, Any, Iterator, Mapping, MutableSequence, - Optional, Set, Tuple, Union, cast) + Optional, Set, Tuple, Union) from PyQt5 import QtCore # Optional imports diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index 076ebb2bd..bf3d65ae6 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -55,7 +55,16 @@ def _base_args(config): @pytest.fixture -def temp_basedir_env(tmp_path, short_tmpdir): +def runtime_tmpdir(short_tmpdir): + """A directory suitable for XDG_RUNTIME_DIR.""" + runtime_dir = short_tmpdir / 'rt' + runtime_dir.ensure(dir=True) + runtime_dir.chmod(0o700) + return runtime_dir + + +@pytest.fixture +def temp_basedir_env(tmp_path, runtime_tmpdir): """Return a dict of environment variables that fakes --temp-basedir. We can't run --basedir or --temp-basedir for some tests, so we mess with @@ -63,12 +72,8 @@ def temp_basedir_env(tmp_path, short_tmpdir): """ data_dir = tmp_path / 'data' config_dir = tmp_path / 'config' - runtime_dir = short_tmpdir / 'rt' cache_dir = tmp_path / 'cache' - runtime_dir.ensure(dir=True) - runtime_dir.chmod(0o700) - lines = [ '[general]', 'quickstart-done = 1', @@ -83,7 +88,7 @@ def temp_basedir_env(tmp_path, short_tmpdir): env = { 'XDG_DATA_HOME': str(data_dir), 'XDG_CONFIG_HOME': str(config_dir), - 'XDG_RUNTIME_DIR': str(runtime_dir), + 'XDG_RUNTIME_DIR': str(runtime_tmpdir), 'XDG_CACHE_HOME': str(cache_dir), } return env @@ -745,12 +750,12 @@ def test_unavailable_backend(request, quteproc_new): line.expected = True -def test_json_logging_without_debug(request, quteproc_new): +def test_json_logging_without_debug(request, quteproc_new, runtime_tmpdir): args = _base_args(request.config) + ['--temp-basedir', ':quit'] args.remove('--debug') args.remove('about:blank') # interfers with :quit at the end quteproc_new.exit_expected = True - quteproc_new.start(args) + quteproc_new.start(args, env={'XDG_RUNTIME_DIR': str(runtime_tmpdir)}) assert not quteproc_new.is_running() assert not quteproc_new.captured_log -- cgit v1.2.3-54-g00ecf From 33596cfa4abb70df87551600d4c1eeb79a27c106 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 29 May 2021 00:24:25 +0200 Subject: tests: Fix test_system_default_rendering with Noto Sans Mono --- tests/unit/config/test_configutils.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'tests') diff --git a/tests/unit/config/test_configutils.py b/tests/unit/config/test_configutils.py index 4d3082a92..e06463bb4 100644 --- a/tests/unit/config/test_configutils.py +++ b/tests/unit/config/test_configutils.py @@ -405,5 +405,9 @@ class TestFontFamilies: if info.family() == fallback_family: return + if info.family() == 'Noto Sans Mono': + # WORKAROUND for https://bugreports.qt.io/browse/QTBUG-94090 + return + # If we didn't fall back, we should've gotten a fixed-pitch font. assert info.fixedPitch(), info.family() -- cgit v1.2.3-54-g00ecf From 03fa9383833c6262b08a5f7c4930143e39327173 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sat, 29 May 2021 23:23:08 +0200 Subject: tests: Update import path for pytestqt SignalBlocker --- tests/end2end/fixtures/testprocess.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'tests') diff --git a/tests/end2end/fixtures/testprocess.py b/tests/end2end/fixtures/testprocess.py index 33b154e9a..96e700390 100644 --- a/tests/end2end/fixtures/testprocess.py +++ b/tests/end2end/fixtures/testprocess.py @@ -25,7 +25,7 @@ import warnings import dataclasses import pytest -import pytestqt.plugin +import pytestqt.wait_signal from PyQt5.QtCore import (pyqtSlot, pyqtSignal, QProcess, QObject, QElapsedTimer, QProcessEnvironment) from PyQt5.QtTest import QSignalSpy @@ -198,8 +198,7 @@ class Process(QObject): Should be used in a contextmanager. """ - blocker = pytestqt.plugin.SignalBlocker(timeout=timeout, - raising=raising) + blocker = pytestqt.wait_signal.SignalBlocker(timeout=timeout, raising=raising) blocker.connect(signal) return blocker -- cgit v1.2.3-54-g00ecf From 996487c43e4fcc265b541f9eca1e7930e3c5cf05 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 30 May 2021 10:57:41 +0200 Subject: Validate encoding for header settings Also needed to add encoding check support to FormatString. --- qutebrowser/config/configdata.yml | 2 ++ qutebrowser/config/configtypes.py | 41 +++++++++++++++++++---------------- tests/unit/config/test_configtypes.py | 5 +++++ 3 files changed, 29 insertions(+), 19 deletions(-) (limited to 'tests') diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml index 80732d43d..b85e84be2 100644 --- a/qutebrowser/config/configdata.yml +++ b/qutebrowser/config/configdata.yml @@ -582,6 +582,7 @@ content.headers.accept_language: type: name: String none_ok: true + encoding: ascii supports_pattern: true default: en-US,en;q=0.9 desc: >- @@ -643,6 +644,7 @@ content.headers.user_agent: Safari/{webkit_version}' type: name: FormatString + encoding: ascii fields: - os_info - webkit_version diff --git a/qutebrowser/config/configtypes.py b/qutebrowser/config/configtypes.py index c157fba41..d3d5e3fb8 100644 --- a/qutebrowser/config/configtypes.py +++ b/qutebrowser/config/configtypes.py @@ -86,6 +86,21 @@ _UnsetNone = Union[None, usertypes.Unset] _StrUnsetNone = Union[str, _UnsetNone] +def _validate_encoding(encoding: Optional[str], value: str) -> None: + """Check if the given value fits into the given encoding. + + Raises ValidationError if not. + """ + if encoding is None: + return + + try: + value.encode(encoding) + except UnicodeEncodeError as e: + msg = f"{value!r} contains non-{encoding} characters: {e}" + raise configexc.ValidationError(value, msg) + + class ValidValues: """Container for valid values for a given type. @@ -377,6 +392,7 @@ class String(BaseType): maxlen: Maximum length (inclusive). forbidden: Forbidden chars in the string. regex: A regex used to validate the string. + encoding: The encoding the value needs to fit in. completions: completions to be used, or None """ @@ -407,24 +423,6 @@ class String(BaseType): self.encoding = encoding self.regex = regex - def _validate_encoding(self, value: str) -> None: - """Check if the given value fits into the configured encoding. - - Raises ValidationError if not. - - Args: - value: The value to check. - """ - if self.encoding is None: - return - - try: - value.encode(self.encoding) - except UnicodeEncodeError as e: - msg = "{!r} contains non-{} characters: {}".format( - value, self.encoding, e) - raise configexc.ValidationError(value, msg) - def to_py(self, value: _StrUnset) -> _StrUnsetNone: self._basic_py_validation(value, str) if isinstance(value, usertypes.Unset): @@ -432,7 +430,7 @@ class String(BaseType): elif not value: return None - self._validate_encoding(value) + _validate_encoding(self.encoding, value) self._validate_valid_values(value) if self.forbidden is not None and any(c in value @@ -1544,6 +1542,7 @@ class FormatString(BaseType): Attributes: fields: Which replacements are allowed in the format string. + encoding: Which encoding the string should fit into. completions: completions to be used, or None """ @@ -1551,11 +1550,13 @@ class FormatString(BaseType): self, *, fields: Iterable[str], none_ok: bool = False, + encoding: str = None, completions: _Completions = None, ) -> None: super().__init__( none_ok=none_ok, completions=completions) self.fields = fields + self.encoding = encoding self._completions = completions def to_py(self, value: _StrUnset) -> _StrUnsetNone: @@ -1565,6 +1566,8 @@ class FormatString(BaseType): elif not value: return None + _validate_encoding(self.encoding, value) + try: value.format(**{k: '' for k in self.fields}) except (KeyError, IndexError, AttributeError) as e: diff --git a/tests/unit/config/test_configtypes.py b/tests/unit/config/test_configtypes.py index 3e1d15099..66b152937 100644 --- a/tests/unit/config/test_configtypes.py +++ b/tests/unit/config/test_configtypes.py @@ -1839,6 +1839,11 @@ class TestFormatString: with pytest.raises(configexc.ValidationError): typ.to_py(val) + def test_invalid_encoding(self, klass): + typ = klass(fields=[], encoding='ascii') + with pytest.raises(configexc.ValidationError): + typ.to_py('fooäbar') + @pytest.mark.parametrize('value', [ None, ['one', 'two'], -- cgit v1.2.3-54-g00ecf From c74d1075620f54d8904b9ae822299ba1221450f4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Sun, 30 May 2021 12:53:19 +0200 Subject: Handle un-encodable initial text for editor --- qutebrowser/misc/editor.py | 2 +- tests/unit/misc/test_editor.py | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'tests') diff --git a/qutebrowser/misc/editor.py b/qutebrowser/misc/editor.py index 5741c6b47..d561a7b96 100644 --- a/qutebrowser/misc/editor.py +++ b/qutebrowser/misc/editor.py @@ -131,7 +131,7 @@ class ExternalEditor(QObject): raise ValueError("Already editing a file!") try: self._filename = self._create_tempfile(text, 'qutebrowser-editor-') - except OSError as e: + except (OSError, UnicodeEncodeError) as e: message.error("Failed to create initial file: {}".format(e)) return diff --git a/tests/unit/misc/test_editor.py b/tests/unit/misc/test_editor.py index cd21308f8..8e5597a0e 100644 --- a/tests/unit/misc/test_editor.py +++ b/tests/unit/misc/test_editor.py @@ -148,6 +148,17 @@ class TestFileHandling: assert msg.text.startswith("Failed to create initial file: ") assert editor._proc is None + def test_encode_error(self, message_mock, editor, caplog, config_stub): + """Test file handling when the initial text can't be encoded.""" + config_stub.val.editor.encoding = 'ascii' + + with caplog.at_level(logging.ERROR): + editor.edit("fooäbar") + + msg = message_mock.getmsg(usertypes.MessageLevel.error) + assert msg.text.startswith("Failed to create initial file: ") + assert editor._proc is None + def test_double_edit(self, editor): editor.edit("") with pytest.raises(ValueError): -- cgit v1.2.3-54-g00ecf From b4b65b8cd158aecea11e7d074d941f8c3908ab66 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 1 Jun 2021 09:19:23 +0200 Subject: Make dark mode tests work correctly on ARM/aarch64 Fixes #6489 --- tests/end2end/test_invocations.py | 72 +++++++++++++++++++++++++++++++-------- 1 file changed, 58 insertions(+), 14 deletions(-) (limited to 'tests') diff --git a/tests/end2end/test_invocations.py b/tests/end2end/test_invocations.py index bf3d65ae6..97c04eb0d 100644 --- a/tests/end2end/test_invocations.py +++ b/tests/end2end/test_invocations.py @@ -26,6 +26,7 @@ import logging import importlib import re import json +import platform import pytest from PyQt5.QtCore import QProcess, QPoint @@ -39,6 +40,14 @@ ascii_locale = pytest.mark.skipif(sys.hexversion >= 0x03070000, "locale with LC_ALL=C") +# For some reason (some floating point rounding differences?), color values are +# slightly different (and wrong!) on ARM machines. We adjust our expected values +# accordingly, since we don't really care about the exact value, we just want to +# know that the underlying Chromium is respecting our preferences. +# FIXME what to do about 32-bit ARM? +IS_ARM = platform.machine() == 'aarch64' + + def _base_args(config): """Get the arguments to pass with every invocation.""" args = ['--debug', '--json-logging', '--no-err-windows'] @@ -517,7 +526,8 @@ def test_preferred_colorscheme_with_dark_mode( # No workaround known. expected_text = 'Light preference detected.' # light website color, inverted by darkmode - expected_color = testutils.Color(127, 127, 127) + expected_color = (testutils.Color(123, 125, 123) if IS_ARM + else testutils.Color(127, 127, 127)) xfail = "Chromium bug 1177973" elif qtwe_version == utils.VersionNumber(5, 15, 2): # Our workaround breaks when dark mode is enabled... @@ -529,7 +539,8 @@ def test_preferred_colorscheme_with_dark_mode( # 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 + expected_color = (testutils.Color(33, 32, 33) if IS_ARM + else testutils.Color(34, 34, 34)) # dark website color xfail = False pos = QPoint(0, 0) @@ -630,30 +641,51 @@ def test_cookies_store(quteproc_new, request, short_tmpdir, store): quteproc_new.wait_for_quit() +# The 'colors' dictionaries in the parametrize decorator below have (QtWebEngine +# version, CPU architecture) as keys. Either of those (or both) can be None to +# say "on all other Qt versions" or "on all other CPU architectures". @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), + ('5.15', None): testutils.Color(18, 18, 18), + ('5.15', 'aarch64'): testutils.Color(16, 16, 16), + ('5.14', None): testutils.Color(27, 27, 27), + ('5.14', 'aarch64'): testutils.Color(24, 24, 24), + (None, None): testutils.Color(0, 0, 0), } ), - ('blank', 'lightness-hsl', {None: testutils.Color(0, 0, 0)}), - ('blank', 'brightness-rgb', {None: testutils.Color(0, 0, 0)}), + ('blank', 'lightness-hsl', {(None, None): testutils.Color(0, 0, 0)}), + ('blank', 'brightness-rgb', {(None, 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), + ('5.15', None): testutils.Color(35, 34, 0), + ('5.15', 'aarch64'): testutils.Color(33, 32, 0), + ('5.14', None): testutils.Color(35, 34, 0), + ('5.14', 'aarch64'): testutils.Color(33, 32, 0), + (None, None): testutils.Color(204, 204, 0), + } + ), + ( + 'yellow', + 'lightness-hsl', + { + (None, None): testutils.Color(204, 204, 0), + (None, 'aarch64'): testutils.Color(206, 207, 0), + }, + ), + ( + 'yellow', + 'brightness-rgb', + { + (None, None): testutils.Color(0, 0, 204), + (None, 'aarch64'): testutils.Color(0, 0, 206), } ), - ('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): @@ -669,7 +701,17 @@ def test_dark_mode(webengine_versions, quteproc_new, request, ver = webengine_versions.webengine minor_version = str(ver.strip_patch()) - expected = colors.get(minor_version, colors[None]) + + arch = platform.machine() + for key in [ + (minor_version, arch), + (minor_version, None), + (None, arch), + (None, None), + ]: + if key in colors: + expected = colors[key] + break quteproc_new.open_path(f'data/darkmode/{filename}.html') @@ -696,9 +738,11 @@ def test_dark_mode_mathml(quteproc_new, request, qtbot): quteproc_new.wait_for_js('Image loaded') # First make sure loading finished by looking outside of the image + expected = testutils.Color(0, 0, 206) if IS_ARM else testutils.Color(0, 0, 204) + quteproc_new.get_screenshot( probe_pos=QPoint(105, 0), - probe_color=testutils.Color(0, 0, 204), + probe_color=expected, ) # Then get the actual formula color, probing again in case it's not displayed yet... -- cgit v1.2.3-54-g00ecf