1# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: 2 3# Copyright 2014-2021 Florian Bruhin (The Compiler) <mail@qutebrowser.org> 4# 5# This file is part of qutebrowser. 6# 7# qutebrowser is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# qutebrowser is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with qutebrowser. If not, see <https://www.gnu.org/licenses/>. 19 20# pylint: disable=unused-import,wildcard-import,unused-wildcard-import 21 22"""The qutebrowser test suite conftest file.""" 23 24import os 25import pathlib 26import sys 27import warnings 28 29import pytest 30import hypothesis 31from PyQt5.QtCore import PYQT_VERSION 32 33pytest.register_assert_rewrite('helpers') 34 35from helpers import logfail 36from helpers.logfail import fail_on_logging 37from helpers.messagemock import message_mock 38from helpers.fixtures import * # noqa: F403 39from helpers import testutils 40from qutebrowser.utils import qtutils, standarddir, usertypes, utils, version 41from qutebrowser.misc import objects, earlyinit 42from qutebrowser.qt import sip 43 44import qutebrowser.app # To register commands 45 46 47_qute_scheme_handler = None 48 49 50# Set hypothesis settings 51hypothesis.settings.register_profile( 52 'default', hypothesis.settings( 53 deadline=600, 54 suppress_health_check=[hypothesis.HealthCheck.function_scoped_fixture], 55 ) 56) 57hypothesis.settings.register_profile( 58 'ci', hypothesis.settings( 59 deadline=None, 60 suppress_health_check=[ 61 hypothesis.HealthCheck.function_scoped_fixture, 62 hypothesis.HealthCheck.too_slow, 63 ] 64 ) 65) 66hypothesis.settings.load_profile('ci' if testutils.ON_CI else 'default') 67 68 69def _apply_platform_markers(config, item): 70 """Apply a skip marker to a given item.""" 71 markers = [ 72 ('posix', 73 pytest.mark.skipif, 74 not utils.is_posix, 75 "Requires a POSIX os"), 76 ('windows', 77 pytest.mark.skipif, 78 not utils.is_windows, 79 "Requires Windows"), 80 ('linux', 81 pytest.mark.skipif, 82 not utils.is_linux, 83 "Requires Linux"), 84 ('mac', 85 pytest.mark.skipif, 86 not utils.is_mac, 87 "Requires macOS"), 88 ('not_mac', 89 pytest.mark.skipif, 90 utils.is_mac, 91 "Skipped on macOS"), 92 ('not_frozen', 93 pytest.mark.skipif, 94 getattr(sys, 'frozen', False), 95 "Can't be run when frozen"), 96 ('not_flatpak', 97 pytest.mark.skipif, 98 version.is_flatpak(), 99 "Can't be run with Flatpak"), 100 ('frozen', 101 pytest.mark.skipif, 102 not getattr(sys, 'frozen', False), 103 "Can only run when frozen"), 104 ('ci', 105 pytest.mark.skipif, 106 not testutils.ON_CI, 107 "Only runs on CI."), 108 ('no_ci', 109 pytest.mark.skipif, 110 testutils.ON_CI, 111 "Skipped on CI."), 112 ('unicode_locale', 113 pytest.mark.skipif, 114 sys.getfilesystemencoding() == 'ascii', 115 "Skipped because of ASCII locale"), 116 ] 117 118 for searched_marker, new_marker_kind, condition, default_reason in markers: 119 marker = item.get_closest_marker(searched_marker) 120 if not marker or not condition: 121 continue 122 123 if 'reason' in marker.kwargs: 124 reason = '{}: {}'.format(default_reason, marker.kwargs['reason']) 125 del marker.kwargs['reason'] 126 else: 127 reason = default_reason + '.' 128 new_marker = new_marker_kind(condition, *marker.args, 129 reason=reason, **marker.kwargs) 130 item.add_marker(new_marker) 131 132 133def pytest_collection_modifyitems(config, items): 134 """Handle custom markers. 135 136 pytest hook called after collection has been performed. 137 138 Adds a marker named "gui" which can be used to filter gui tests from the 139 command line. 140 141 For example: 142 143 pytest -m "not gui" # run all tests except gui tests 144 pytest -m "gui" # run only gui tests 145 146 It also handles the platform specific markers by translating them to skipif 147 markers. 148 149 Args: 150 items: list of _pytest.main.Node items, where each item represents 151 a python test that will be executed. 152 153 Reference: 154 https://pytest.org/latest/plugins.html 155 """ 156 remaining_items = [] 157 deselected_items = [] 158 159 for item in items: 160 deselected = False 161 162 if 'qapp' in getattr(item, 'fixturenames', ()): 163 item.add_marker('gui') 164 165 if hasattr(item, 'module'): 166 test_basedir = pathlib.Path(__file__).parent 167 module_path = pathlib.Path(item.module.__file__) 168 module_root_dir = module_path.relative_to(test_basedir).parts[0] 169 170 assert module_root_dir in ['end2end', 'unit', 'helpers', 171 'test_conftest.py'] 172 if module_root_dir == 'end2end': 173 item.add_marker(pytest.mark.end2end) 174 175 _apply_platform_markers(config, item) 176 if list(item.iter_markers('xfail_norun')): 177 item.add_marker(pytest.mark.xfail(run=False)) 178 179 if deselected: 180 deselected_items.append(item) 181 else: 182 remaining_items.append(item) 183 184 config.hook.pytest_deselected(items=deselected_items) 185 items[:] = remaining_items 186 187 188def pytest_ignore_collect(path): 189 """Ignore BDD tests if we're unable to run them.""" 190 fspath = pathlib.Path(path) 191 skip_bdd = hasattr(sys, 'frozen') 192 rel_path = fspath.relative_to(pathlib.Path(__file__).parent) 193 return rel_path == pathlib.Path('end2end') / 'features' and skip_bdd 194 195 196@pytest.fixture(scope='session') 197def qapp_args(): 198 """Make QtWebEngine unit tests run on older Qt versions + newer kernels.""" 199 seccomp_args = testutils.seccomp_args(qt_flag=False) 200 if seccomp_args: 201 return [sys.argv[0]] + seccomp_args 202 return [] 203 204 205@pytest.fixture(scope='session') 206def qapp(qapp): 207 """Change the name of the QApplication instance.""" 208 qapp.setApplicationName('qute_test') 209 return qapp 210 211 212def pytest_addoption(parser): 213 parser.addoption('--qute-delay', action='store', default=0, type=int, 214 help="Delay between qutebrowser commands.") 215 parser.addoption('--qute-profile-subprocs', action='store_true', 216 default=False, help="Run cProfile for subprocesses.") 217 parser.addoption('--qute-bdd-webengine', action='store_true', 218 help='Use QtWebEngine for BDD tests') 219 220 221def pytest_configure(config): 222 webengine_arg = config.getoption('--qute-bdd-webengine') 223 webengine_env = os.environ.get('QUTE_BDD_WEBENGINE', 'false') 224 config.webengine = webengine_arg or webengine_env == 'true' 225 # Fail early if QtWebEngine is not available 226 if config.webengine: 227 import PyQt5.QtWebEngineWidgets 228 earlyinit.configure_pyqt() 229 230 231@pytest.fixture(scope='session', autouse=True) 232def check_display(request): 233 if utils.is_linux and not os.environ.get('DISPLAY', ''): 234 raise Exception("No display and no Xvfb available!") 235 236 237@pytest.fixture(autouse=True) 238def set_backend(monkeypatch, request): 239 """Make sure the backend global is set.""" 240 if not request.config.webengine and version.qWebKitVersion: 241 backend = usertypes.Backend.QtWebKit 242 else: 243 backend = usertypes.Backend.QtWebEngine 244 monkeypatch.setattr(objects, 'backend', backend) 245 246 247@pytest.fixture(autouse=True) 248def apply_fake_os(monkeypatch, request): 249 fake_os = request.node.get_closest_marker('fake_os') 250 if not fake_os: 251 return 252 253 name = fake_os.args[0] 254 mac = False 255 windows = False 256 linux = False 257 posix = False 258 259 if name == 'unknown': 260 pass 261 elif name == 'mac': 262 mac = True 263 posix = True 264 elif name == 'windows': 265 windows = True 266 elif name == 'linux': 267 linux = True 268 posix = True 269 elif name == 'posix': 270 posix = True 271 else: 272 raise ValueError("Invalid fake_os {}".format(name)) 273 274 monkeypatch.setattr(utils, 'is_mac', mac) 275 monkeypatch.setattr(utils, 'is_linux', linux) 276 monkeypatch.setattr(utils, 'is_windows', windows) 277 monkeypatch.setattr(utils, 'is_posix', posix) 278 279 280@pytest.fixture(scope='session', autouse=True) 281def check_yaml_c_exts(): 282 """Make sure PyYAML C extensions are available on CI. 283 284 Not available yet with a nightly Python, see: 285 https://github.com/yaml/pyyaml/issues/416 286 """ 287 if testutils.ON_CI and sys.version_info[:2] != (3, 10): 288 from yaml import CLoader 289 290 291@pytest.hookimpl(tryfirst=True, hookwrapper=True) 292def pytest_runtest_makereport(item, call): 293 """Make test information available in fixtures. 294 295 See https://pytest.org/latest/example/simple.html#making-test-result-information-available-in-fixtures 296 """ 297 outcome = yield 298 rep = outcome.get_result() 299 setattr(item, "rep_" + rep.when, rep) 300 301 302@pytest.hookimpl(hookwrapper=True) 303def pytest_terminal_summary(terminalreporter): 304 """Group benchmark results on CI.""" 305 if testutils.ON_CI: 306 terminalreporter.write_line( 307 testutils.gha_group_begin('Benchmark results')) 308 yield 309 terminalreporter.write_line(testutils.gha_group_end()) 310 else: 311 yield 312