# SPDX-FileCopyrightText: Freya Bruhin (The Compiler) <mail@qutebrowser.org>
#
# SPDX-License-Identifier: GPL-3.0-or-later


import sys
import os
import logging

import pytest

from qutebrowser.qt import machinery
from qutebrowser import qutebrowser
from qutebrowser.config import qtargs, configdata
from qutebrowser.utils import usertypes, version, utils


@pytest.fixture
def parser(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


@pytest.fixture
def version_patcher(monkeypatch):
    """Get a patching function to patch the QtWebEngine version."""
    def run(ver) -> bool:
        """Run patching.

        Return:
            True if we know the associated Chromium version, False otherwise
        """
        versions = version.WebEngineVersions.from_pyqt(ver)
        monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)
        monkeypatch.setattr(version, 'qtwebengine_versions',
                            lambda avoid_init: versions)
        return versions.chromium_major is not None

    return run


@pytest.fixture
def reduce_args(config_stub, version_patcher, monkeypatch):
    """Make sure no --disable-shared-workers/referer argument get added."""
    version_patcher('5.15.3')
    config_stub.val.content.headers.referer = 'always'
    config_stub.val.scrolling.bar = 'never'
    config_stub.val.qt.chromium.experimental_web_platform_features = 'never'
    config_stub.val.qt.workarounds.disable_accelerated_2d_canvas = 'never'
    config_stub.val.qt.workarounds.disable_accessibility = 'never'
    monkeypatch.setattr(qtargs.utils, 'is_mac', False)
    # Avoid WebRTC pipewire feature
    monkeypatch.setattr(qtargs.utils, 'is_linux', False)


@pytest.mark.usefixtures('reduce_args')
class TestQtArgs:

    @pytest.mark.parametrize('args, expected', [
        # No Qt arguments
        (['--debug'], [sys.argv[0]]),
        # Qt flag
        (['--debug', '--qt-flag', 'reverse'], [sys.argv[0], '--reverse']),
        # Qt argument with value
        (['--qt-arg', 'stylesheet', 'foo'],
         [sys.argv[0], '--stylesheet', 'foo']),
        # --qt-arg given twice
        (['--qt-arg', 'stylesheet', 'foo', '--qt-arg', 'geometry', 'bar'],
         [sys.argv[0], '--stylesheet', 'foo', '--geometry', 'bar']),
        # --qt-flag given twice
        (['--qt-flag', 'foo', '--qt-flag', 'bar'],
         [sys.argv[0], '--foo', '--bar']),
    ])
    def test_qt_args(self, request, monkeypatch, config_stub, args, expected, parser):
        """Test commandline with no Qt arguments given."""
        if request.config.webengine:
            expected.append("--touch-events=disabled")  # passed unconditionally
        parsed = parser.parse_args(args)
        assert qtargs.qt_args(parsed) == expected

    def test_qt_both(self, config_stub, parser):
        """Test commandline with a Qt argument and flag."""
        args = parser.parse_args(['--qt-arg', 'stylesheet', 'foobar',
                                  '--qt-flag', 'reverse'])
        qt_args = qtargs.qt_args(args)
        assert qt_args[0] == sys.argv[0]
        assert '--reverse' in qt_args
        assert '--stylesheet' in qt_args
        assert 'foobar' in qt_args

    def test_with_settings(self, config_stub, parser):
        parsed = parser.parse_args(['--qt-flag', 'foo'])
        config_stub.val.qt.args = ['bar']
        args = qtargs.qt_args(parsed)
        assert args[0] == sys.argv[0]
        for arg in ['--foo', '--bar']:
            assert arg in args


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)

    fake = stubs.ImportFake({'qutebrowser.browser.webengine': False}, monkeypatch)
    fake.patch()

    parsed = parser.parse_args([])
    args = qtargs.qt_args(parsed)
    assert args == [sys.argv[0]]


_XFAIL_FUTURE_QT = (
    pytest.mark.xfail(
        utils.VersionNumber(6, 11) not in version.WebEngineVersions._CHROMIUM_VERSIONS,
        reason="Unknown security patch version for Qt 6.11 so far",
    ),
)


@pytest.mark.usefixtures('reduce_args')
class TestWebEngineArgs:

    @pytest.fixture(autouse=True)
    def ensure_webengine(self, monkeypatch):
        """Skip all tests if QtWebEngine is unavailable."""
        pytest.importorskip("qutebrowser.qt.webenginecore")
        monkeypatch.setattr(qtargs.objects, 'backend', usertypes.Backend.QtWebEngine)

    @pytest.mark.parametrize("setting, values", qtargs._WEBENGINE_SETTINGS.items())
    def test_settings_exist(self, setting, values, configdata_init):
        option = configdata.DATA[setting]
        for value in values:
            option.typ.to_py(value)  # for validation

    @pytest.mark.parametrize('backend, qt_version, debug_flag, expected', [
        # QtWebEngine: Enable with -D stack, do nothing without it.
        (usertypes.Backend.QtWebEngine, '5.15.2', True, True),
        (usertypes.Backend.QtWebEngine, '5.15.2', False, None),
        # QtWebKit: Do nothing
        (usertypes.Backend.QtWebKit, '5.15.2', False, None),
    ])
    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 [])
        args = qtargs.qt_args(parsed)

        if expected is None:
            assert '--disable-in-process-stack-traces' not in args
            assert '--enable-in-process-stack-traces' not in args
        elif expected:
            assert '--disable-in-process-stack-traces' not in args
            assert '--enable-in-process-stack-traces' in args
        else:
            assert '--disable-in-process-stack-traces' in args
            assert '--enable-in-process-stack-traces' not in args

    @pytest.mark.parametrize(
        'qt_version, qt6, value, has_arg',
        [
            ('5.15.2', False, 'auto', False),
            ('6.5.3', True, 'auto', True),
            ('6.6.0', True, 'auto', True),
            ('6.7.0', True, 'auto', True),
            ('6.8.1', True, 'auto', True),
            ('6.8.2', True, 'auto', False),
            ('6.5.3', True, 'always', True),
            ('6.5.3', True, 'never', False),
            ('6.8.2', True, 'always', True),
        ],
    )
    def test_accelerated_2d_canvas(
        self,
        parser,
        version_patcher,
        config_stub,
        monkeypatch,
        qt_version,
        qt6,
        value,
        has_arg,
    ):
        version_patcher(qt_version)
        config_stub.val.qt.workarounds.disable_accelerated_2d_canvas = value
        monkeypatch.setattr(machinery, 'IS_QT6', qt6)

        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)
        assert ('--disable-accelerated-2d-canvas' in args) == has_arg

    @pytest.mark.parametrize(
        "qt_version, qt6, value, has_arg",
        [
            ("5.15.2", False, "auto", False),
            # 6.8.5 is broken too, but commercial-only
            ("6.10.0", True, "always", True),
            ("6.10.0", True, "auto", False),
            ("6.10.1", True, "auto", True),
            ("6.10.1", True, "never", False),
            ("6.10.2", True, "always", True),
            ("6.10.2", True, "auto", False),
            pytest.param("6.11.0", True, "always", True, marks=_XFAIL_FUTURE_QT),
            pytest.param("6.11.0", True, "auto", False, marks=_XFAIL_FUTURE_QT),
        ],
    )
    def test_disable_accessibility(
        self,
        parser,
        version_patcher,
        config_stub,
        monkeypatch,
        qt_version,
        qt6,
        value,
        has_arg,
    ):
        version_patcher(qt_version)
        config_stub.val.qt.workarounds.disable_accessibility = value
        monkeypatch.setattr(machinery, 'IS_QT6', qt6)

        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)
        assert ('--disable-renderer-accessibility' in args) == has_arg

    @pytest.mark.parametrize('flags, args', [
        ([], []),
        (['--debug-flag', 'chromium'], ['--enable-logging', '--v=1']),
        (['--debug-flag', 'wait-renderer-process'], ['--renderer-startup-dialog']),
    ])
    def test_chromium_flags(self, monkeypatch, parser, flags, args):
        parsed = parser.parse_args(flags)
        args = qtargs.qt_args(parsed)

        if args:
            for arg in args:
                assert arg in args
        else:
            assert '--enable-logging' not in args
            assert '--v=1' not in args
            assert '--renderer-startup-dialog' not in args

    @pytest.mark.parametrize('config, added', [
        ('none', False),
        ('qt-quick', False),
        ('software-opengl', False),
        ('chromium', True),
    ])
    def test_disable_gpu(self, config, added, config_stub, monkeypatch, parser):
        config_stub.val.qt.force_software_rendering = config
        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)
        assert ('--disable-gpu' in args) == added

    @pytest.mark.parametrize('policy, arg', [
        ('all-interfaces', None),

        ('default-public-and-private-interfaces',
         '--force-webrtc-ip-handling-policy='
         'default_public_and_private_interfaces'),

        ('default-public-interface-only',
         '--force-webrtc-ip-handling-policy='
         'default_public_interface_only'),

        ('disable-non-proxied-udp',
         '--force-webrtc-ip-handling-policy='
         'disable_non_proxied_udp'),
    ])
    def test_webrtc(self, config_stub, monkeypatch, parser, policy, arg):
        config_stub.val.content.webrtc_ip_handling_policy = policy

        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)

        if arg is None:
            assert not any(a.startswith('--force-webrtc-ip-handling-policy=')
                           for a in args)
        else:
            assert arg in args

    @pytest.mark.parametrize('canvas_reading, added', [
        (True, False),  # canvas reading enabled
        (False, True),
    ])
    def test_canvas_reading(self, config_stub, parser, canvas_reading, added):
        config_stub.val.content.canvas_reading = canvas_reading
        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)
        assert ('--disable-reading-from-canvas' in args) == added

    @pytest.mark.parametrize('process_model, added', [
        ('process-per-site-instance', False),
        ('process-per-site', True),
        ('single-process', True),
    ])
    def test_process_model(self, config_stub, parser, process_model, added):
        config_stub.val.qt.chromium.process_model = process_model
        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)

        if added:
            assert '--' + process_model in args
        else:
            assert '--process-per-site' not in args
            assert '--single-process' not in args
            assert '--process-per-site-instance' not in args
            assert '--process-per-tab' not in args

    @pytest.mark.parametrize('low_end_device_mode, arg', [
        ('auto', None),
        ('always', '--enable-low-end-device-mode'),
        ('never', '--disable-low-end-device-mode'),
    ])
    def test_low_end_device_mode(self, config_stub, parser, low_end_device_mode, arg):
        config_stub.val.qt.chromium.low_end_device_mode = low_end_device_mode
        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)

        if arg is None:
            assert '--enable-low-end-device-mode' not in args
            assert '--disable-low-end-device-mode' not in args
        else:
            assert arg in args

    @pytest.mark.parametrize('sandboxing, arg', [
        ('enable-all', None),
        ('disable-seccomp-bpf', '--disable-seccomp-filter-sandbox'),
        ('disable-all', '--no-sandbox'),
    ])
    def test_sandboxing(self, config_stub, parser, sandboxing, arg):
        config_stub.val.qt.chromium.sandboxing = sandboxing
        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)

        remaining_flags = {
            '--no-sandbox',
            '--disable-seccomp-filter-sandbox',
        }
        if arg is not None:
            remaining_flags.remove(arg)

        if arg is not None:
            assert arg in args

        assert not set(args) & remaining_flags

    @pytest.mark.parametrize('qt_version, referer, arg', [
        # 'always' -> no arguments
        ('5.15.2', 'always', None),
        ('5.15.3', 'always', None),

        # 'never' is handled via interceptor
        ('5.15.2', 'never', None),
        ('5.15.3', 'never', None),

        # 'same-domain'
        ('5.15.2', 'same-domain', '--enable-features=ReducedReferrerGranularity'),
        ('5.15.3', 'same-domain', '--enable-features=ReducedReferrerGranularity'),
        # (Not available anymore)
        ('6.2', 'same-domain', None),
        ('6.3', 'same-domain', None),
    ])
    def test_referer(self, config_stub, version_patcher, parser,
                     qt_version, referer, arg):
        version_patcher(qt_version)

        config_stub.val.content.headers.referer = referer
        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)

        # Old QtWebEngine args
        assert '--no-referrers' not in args
        assert '--reduced-referrer-granularity' not in args

        if arg is None:
            assert '--enable-features=ReducedReferrerGranularity' not in args
        else:
            assert arg in args

    @pytest.mark.parametrize('bar, is_mac, added', [
        # Overlay bar enabled
        ('overlay', False, True),
        # No overlay on mac
        ('overlay', True, False),
        # Overlay disabled
        ('when-searching', False, False),
        ('always', False, False),
        ('never', False, False),
    ])
    def test_overlay_scrollbar(self, config_stub, monkeypatch, parser,
                               bar, is_mac, added):
        monkeypatch.setattr(qtargs.utils, 'is_mac', is_mac)

        config_stub.val.scrolling.bar = bar

        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)

        assert ('--enable-features=OverlayScrollbar' in args) == added

    @pytest.mark.parametrize('via_commandline', [True, False])
    @pytest.mark.parametrize('overlay, passed_features, expected_features', [
        (True,
         'CustomFeature',
         'CustomFeature,OverlayScrollbar'),
        (True,
         'CustomFeature1,CustomFeature2',
         'CustomFeature1,CustomFeature2,OverlayScrollbar'),
        (False,
         'CustomFeature',
         'CustomFeature'),
    ])
    def test_overlay_features_flag(self, config_stub, parser,
                                   via_commandline, overlay, passed_features,
                                   expected_features):
        """If enable-features is already specified, we should combine both."""
        config_flag = qtargs._ENABLE_FEATURES.lstrip('-') + passed_features

        config_stub.val.scrolling.bar = 'overlay' if overlay else 'never'
        config_stub.val.qt.args = ([] if via_commandline else [config_flag])

        parsed = parser.parse_args(['--qt-flag', config_flag]
                                   if via_commandline else [])
        args = qtargs.qt_args(parsed)

        overlay_flag = qtargs._ENABLE_FEATURES + 'OverlayScrollbar'
        combined_flag = qtargs._ENABLE_FEATURES + expected_features

        enable_features_args = [
            arg for arg in args
            if arg.startswith(qtargs._ENABLE_FEATURES)
        ]
        assert len(enable_features_args) == 1
        assert combined_flag in args
        assert overlay_flag not in args

    @pytest.mark.parametrize('via_commandline', [True, False])
    @pytest.mark.parametrize('passed_features', [
        ['CustomFeature'],
        ['CustomFeature1', 'CustomFeature2'],
    ])
    def test_disable_features_passthrough(self, config_stub, parser,
                                          via_commandline, passed_features):
        flag = qtargs._DISABLE_FEATURES + ','.join(passed_features)

        config_flag = flag.lstrip('-')
        config_stub.val.qt.args = ([] if via_commandline else [config_flag])
        parsed = parser.parse_args(['--qt-flag', config_flag]
                                   if via_commandline else [])
        args = qtargs.qt_args(parsed)

        disable_features_args = [
            arg for arg in args
            if arg.startswith(qtargs._DISABLE_FEATURES)
        ]
        assert disable_features_args == [flag]

    def test_blink_settings_passthrough(self, parser, config_stub):
        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.15.2', True),
        ('5.15.3', False),
        ('6.2.0', False),
    ])
    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)
        disable_features_args = [
            arg for arg in args
            if arg.startswith(qtargs._DISABLE_FEATURES)
        ]

        expected = ['--disable-features=InstalledApp'] if has_workaround else []
        assert disable_features_args == expected

    @pytest.mark.parametrize('qt_version, disabled', [
        # Qt 6.6
        ('6.6.3', None),
        # Qt 6.7
        ('6.7.0', "DocumentPictureInPictureAPI"),
        ('6.7.1', "DocumentPictureInPictureAPI"),
        ('6.7.2', "DocumentPictureInPictureAPI"),
        ('6.7.3', "DocumentPictureInPictureAPI"),
        # Qt 6.8
        ('6.8.0', "DocumentPictureInPictureAPI"),
        ('6.8.1', "DocumentPictureInPictureAPI"),
        ('6.8.2', "DocumentPictureInPictureAPI"),
        ('6.8.3', "DocumentPictureInPictureAPI"),
        # Qt 6.9
        ('6.9.0', "DocumentPictureInPictureAPI,PermissionElement"),
        ('6.9.1', "DocumentPictureInPictureAPI,PermissionElement"),
        # Qt 6.10
        ('6.10.0', "DocumentPictureInPictureAPI,PermissionElement"),
        ('6.10.1', "DocumentPictureInPictureAPI"),
    ])
    def test_disable_feature_workaround(
        self, parser, version_patcher, qt_version, disabled
    ):
        version_patcher(qt_version)

        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)
        disable_features_args = [
            arg for arg in args
            if arg.startswith(qtargs._DISABLE_FEATURES)
        ]

        expected = [f"--disable-features={disabled}"] if disabled else []
        assert disable_features_args == expected

    @pytest.mark.parametrize('enabled', [True, False])
    def test_media_keys(self, config_stub, parser, enabled):
        config_stub.val.input.media_keys = enabled

        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)

        assert ('--disable-features=HardwareMediaKeyHandling' in args) != enabled

    @pytest.mark.parametrize('variant, expected', [
        (
            '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(
            darkmode, '_variant', lambda _versions: darkmode.Variant[variant])

        config_stub.val.colors.webpage.darkmode.enabled = True

        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)

        for arg in expected:
            assert arg in args

    @pytest.mark.linux
    def test_locale_workaround(self, config_stub, monkeypatch, version_patcher, parser):
        class FakeLocale:

            def bcp47Name(self):
                return 'de-CH'

        monkeypatch.setattr(qtargs.utils, 'is_linux', True)  # patched in reduce_args
        monkeypatch.setattr(qtargs, 'QLocale', FakeLocale)
        version_patcher('5.15.3')

        config_stub.val.qt.workarounds.locale = True
        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)
        assert '--lang=de' in args

    @pytest.mark.parametrize('value, has_arg', [
        ('always', True),
        ('auto', machinery.IS_QT5),
        ('never', False),
    ])
    def test_experimental_web_platform_features(
        self, value, has_arg, parser, config_stub,
    ):
        config_stub.val.qt.chromium.experimental_web_platform_features = value

        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)
        assert ('--enable-experimental-web-platform-features' in args) == has_arg

    @pytest.mark.parametrize("version, expected", [
        ('5.15.2', False),
        ('5.15.9', False),
        ('6.2.4', False),
        ('6.3.1', False),
        ('6.4.0', True),
        ('6.5.0', True),
    ])
    def test_webengine_args(self, version_patcher, parser, version, expected):
        known_chromium = version_patcher(version)
        if not known_chromium:
            pytest.skip("Don't know associated Chromium version")

        parsed = parser.parse_args([])
        args = qtargs.qt_args(parsed)

        flag = "--webEngineArgs"
        if expected:
            assert args[1] == flag
        else:
            assert flag not in args


class TestEnvVars:

    @pytest.mark.parametrize('config_opt, config_val, envvar, expected', [
        ('qt.force_software_rendering', 'software-opengl',
         'QT_XCB_FORCE_SOFTWARE_OPENGL', '1'),
        ('qt.force_software_rendering', 'qt-quick',
         'QT_QUICK_BACKEND', 'software'),
        ('qt.force_software_rendering', 'chromium',
         'QT_WEBENGINE_DISABLE_NOUVEAU_WORKAROUND', '1'),
        ('qt.force_platform', 'toaster', 'QT_QPA_PLATFORM', 'toaster'),
        ('qt.force_platformtheme', 'lxde', 'QT_QPA_PLATFORMTHEME', 'lxde'),
        ('window.hide_decoration', True,
         'QT_WAYLAND_DISABLE_WINDOWDECORATION', '1')
    ])
    def test_env_vars(self, monkeypatch, config_stub,
                      config_opt, config_val, envvar, expected):
        """Check settings which set an environment variable."""
        monkeypatch.setattr(qtargs.objects, 'backend',
                            usertypes.Backend.QtWebEngine)
        monkeypatch.setenv(envvar, '')  # to make sure it gets restored
        monkeypatch.delenv(envvar)

        config_stub.set_obj(config_opt, config_val)
        qtargs.init_envvars()

        assert os.environ[envvar] == expected

    @pytest.mark.parametrize('init_val, config_val', [
        (   # Test changing a set variable
            {'QT_SCALE_FACTOR': '2'},
            {'QT_SCALE_FACTOR': '4'},
        ),
        (   # Test setting an unset variable
            {'QT_SCALE_FACTOR': None},
            {'QT_SCALE_FACTOR': '3'},
        ),
        (   # Test unsetting a variable which is set
            {'QT_SCALE_FACTOR': '3'},
            {'QT_SCALE_FACTOR': None},
        ),
        (   # Test unsetting a variable which is unset
            {'QT_SCALE_FACTOR': None},
            {'QT_SCALE_FACTOR': None},
        ),
        (   # Test setting multiple variables
            {'QT_SCALE_FACTOR': '0', 'QT_PLUGIN_PATH': '/usr/bin', 'QT_NEWVAR': None},
            {'QT_SCALE_FACTOR': '3', 'QT_PLUGIN_PATH': '/tmp/', 'QT_NEWVAR': 'newval'},
        )
    ])
    def test_environ_settings(self, monkeypatch, config_stub,
                              init_val, config_val):
        """Test setting environment variables using qt.environ."""
        for var, val in init_val.items():
            if val is None:
                monkeypatch.setenv(var, '0')
                monkeypatch.delenv(var, raising=False)
            else:
                monkeypatch.setenv(var, val)

        config_stub.val.qt.environ = config_val
        qtargs.init_envvars()

        for var, result in config_val.items():
            if result is None:
                assert var not in os.environ
            else:
                assert os.environ[var] == result

    @pytest.mark.parametrize('new_qt', [True, False])
    def test_highdpi(self, monkeypatch, config_stub, new_qt):
        """Test HighDPI environment variables."""
        monkeypatch.setattr(qtargs.objects, 'backend',
                            usertypes.Backend.QtWebEngine)
        monkeypatch.setattr(qtargs.qtutils, 'version_check',
                            lambda version, exact=False, compiled=True:
                            new_qt)

        envvar = 'QT_ENABLE_HIGHDPI_SCALING'
        monkeypatch.setenv(envvar, '')  # to make sure it gets restored
        monkeypatch.delenv(envvar)

        config_stub.set_obj('qt.highdpi', True)
        qtargs.init_envvars()

        assert os.environ[envvar] == '1'

    def test_env_vars_webkit(self, monkeypatch, config_stub):
        monkeypatch.setattr(qtargs.objects, 'backend',
                            usertypes.Backend.QtWebKit)
        qtargs.init_envvars()

    @pytest.mark.parametrize('backend, value, expected', [
        (usertypes.Backend.QtWebKit, None, None),
        (usertypes.Backend.QtWebKit, '--test', None),

        (usertypes.Backend.QtWebEngine, None, None),
        (usertypes.Backend.QtWebEngine, '', "''"),
        (usertypes.Backend.QtWebEngine, '--xyz', "'--xyz'"),
    ])
    def test_qtwe_flags_warning(self, monkeypatch, config_stub, caplog,
                                backend, value, expected):
        monkeypatch.setattr(qtargs.objects, 'backend', backend)
        if value is None:
            monkeypatch.delenv('QTWEBENGINE_CHROMIUM_FLAGS', raising=False)
        else:
            monkeypatch.setenv('QTWEBENGINE_CHROMIUM_FLAGS', value)

        with caplog.at_level(logging.WARNING):
            qtargs.init_envvars()

        if expected is None:
            assert not caplog.messages
        else:
            assert len(caplog.messages) == 1
            msg = caplog.messages[0]
            assert msg.startswith(f'You have QTWEBENGINE_CHROMIUM_FLAGS={expected} set')
