# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 2015-2017 Florian Bruhin (The Compiler) # # 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 . """Tests for qutebrowser.utils.version.""" import io import sys import os.path import subprocess import contextlib import builtins import types import importlib import logging import textwrap import pkg_resources import pytest import qutebrowser from qutebrowser.utils import version, usertypes, qtutils from qutebrowser.browser import pdfjs @pytest.mark.parametrize('os_release, expected', [ # No file (None, None), # Invalid file ("\n# foo\n foo=bar=baz", version.DistributionInfo(id=None, parsed=version.Distribution.unknown, version=None, pretty='Unknown')), # Archlinux (""" NAME="Arch Linux" PRETTY_NAME="Arch Linux" ID=arch ID_LIKE=archlinux ANSI_COLOR="0;36" HOME_URL="https://www.archlinux.org/" SUPPORT_URL="https://bbs.archlinux.org/" BUG_REPORT_URL="https://bugs.archlinux.org/" """, version.DistributionInfo( id='arch', parsed=version.Distribution.arch, version=None, pretty='Arch Linux')), # Ubuntu 14.04 (""" NAME="Ubuntu" VERSION="14.04.5 LTS, Trusty Tahr" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 14.04.5 LTS" VERSION_ID="14.04" """, version.DistributionInfo( id='ubuntu', parsed=version.Distribution.ubuntu, version=pkg_resources.parse_version('14.4'), pretty='Ubuntu 14.04.5 LTS')), # Ubuntu 17.04 (""" NAME="Ubuntu" VERSION="17.04 (Zesty Zapus)" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 17.04" VERSION_ID="17.04" """, version.DistributionInfo( id='ubuntu', parsed=version.Distribution.ubuntu, version=pkg_resources.parse_version('17.4'), pretty='Ubuntu 17.04')), # Debian Jessie (""" PRETTY_NAME="Debian GNU/Linux 8 (jessie)" NAME="Debian GNU/Linux" VERSION_ID="8" VERSION="8 (jessie)" ID=debian """, version.DistributionInfo( id='debian', parsed=version.Distribution.debian, version=pkg_resources.parse_version('8'), pretty='Debian GNU/Linux 8 (jessie)')), # Void Linux (""" NAME="void" ID="void" DISTRIB_ID="void" PRETTY_NAME="void" """, version.DistributionInfo( id='void', parsed=version.Distribution.void, version=None, pretty='void')), # Gentoo (""" NAME=Gentoo ID=gentoo PRETTY_NAME="Gentoo/Linux" """, version.DistributionInfo( id='gentoo', parsed=version.Distribution.gentoo, version=None, pretty='Gentoo/Linux')), # Fedora (""" NAME=Fedora VERSION="25 (Twenty Five)" ID=fedora VERSION_ID=25 PRETTY_NAME="Fedora 25 (Twenty Five)" """, version.DistributionInfo( id='fedora', parsed=version.Distribution.fedora, version=pkg_resources.parse_version('25'), pretty='Fedora 25 (Twenty Five)')), # OpenSUSE (""" NAME="openSUSE Leap" VERSION="42.2" ID=opensuse ID_LIKE="suse" VERSION_ID="42.2" PRETTY_NAME="openSUSE Leap 42.2" """, version.DistributionInfo( id='opensuse', parsed=version.Distribution.opensuse, version=pkg_resources.parse_version('42.2'), pretty='openSUSE Leap 42.2')), # Linux Mint (""" NAME="Linux Mint" VERSION="18.1 (Serena)" ID=linuxmint ID_LIKE=ubuntu PRETTY_NAME="Linux Mint 18.1" VERSION_ID="18.1" """, version.DistributionInfo( id='linuxmint', parsed=version.Distribution.linuxmint, version=pkg_resources.parse_version('18.1'), pretty='Linux Mint 18.1')), # Manjaro (""" NAME="Manjaro Linux" ID=manjaro PRETTY_NAME="Manjaro Linux" """, version.DistributionInfo( id='manjaro', parsed=version.Distribution.manjaro, version=None, pretty='Manjaro Linux')), ]) def test_distribution(tmpdir, monkeypatch, os_release, expected): os_release_file = tmpdir / 'os-release' if os_release is not None: os_release_file.write(textwrap.dedent(os_release)) monkeypatch.setenv('QUTE_FAKE_OS_RELEASE', str(os_release_file)) assert version.distribution() == expected class GitStrSubprocessFake: """Object returned by the git_str_subprocess_fake fixture. This provides a function which is used to patch _git_str_subprocess. Attributes: retval: The value to return when called. Needs to be set before func is called. """ UNSET = object() def __init__(self): self.retval = self.UNSET def func(self, gitpath): """Function called instead of _git_str_subprocess. Checks whether the path passed is what we expected, and returns self.retval. """ if self.retval is self.UNSET: raise ValueError("func got called without retval being set!") retval = self.retval self.retval = self.UNSET gitpath = os.path.normpath(gitpath) expected = os.path.abspath(os.path.join( os.path.dirname(qutebrowser.__file__), os.pardir)) assert gitpath == expected return retval class TestGitStr: """Tests for _git_str().""" @pytest.fixture def commit_file_mock(self, mocker): """Fixture providing a mock for utils.read_file for git-commit-id. On fixture teardown, it makes sure it got called with git-commit-id as argument. """ mocker.patch('qutebrowser.utils.version.subprocess', side_effect=AssertionError) m = mocker.patch('qutebrowser.utils.version.utils.read_file') yield m m.assert_called_with('git-commit-id') @pytest.fixture def git_str_subprocess_fake(self, mocker, monkeypatch): """Fixture patching _git_str_subprocess with a GitStrSubprocessFake.""" mocker.patch('qutebrowser.utils.version.subprocess', side_effect=AssertionError) fake = GitStrSubprocessFake() monkeypatch.setattr(version, '_git_str_subprocess', fake.func) return fake def test_frozen_ok(self, commit_file_mock, monkeypatch): """Test with sys.frozen=True and a successful git-commit-id read.""" monkeypatch.setattr(version.sys, 'frozen', True, raising=False) commit_file_mock.return_value = 'deadbeef' assert version._git_str() == 'deadbeef' def test_frozen_oserror(self, caplog, commit_file_mock, monkeypatch): """Test with sys.frozen=True and OSError when reading git-commit-id.""" monkeypatch.setattr(version.sys, 'frozen', True, raising=False) commit_file_mock.side_effect = OSError with caplog.at_level(logging.ERROR, 'misc'): assert version._git_str() is None @pytest.mark.not_frozen def test_normal_successful(self, git_str_subprocess_fake): """Test with git returning a successful result.""" git_str_subprocess_fake.retval = 'c0ffeebabe' assert version._git_str() == 'c0ffeebabe' @pytest.mark.frozen def test_normal_successful_frozen(self, git_str_subprocess_fake): """Test with git returning a successful result.""" # The value is defined in scripts/freeze_tests.py. assert version._git_str() == 'fake-frozen-git-commit' def test_normal_error(self, commit_file_mock, git_str_subprocess_fake): """Test without repo (but git-commit-id).""" git_str_subprocess_fake.retval = None commit_file_mock.return_value = '1b4d1dea' assert version._git_str() == '1b4d1dea' def test_normal_path_oserror(self, mocker, git_str_subprocess_fake, caplog): """Test with things raising OSError.""" m = mocker.patch('qutebrowser.utils.version.os') m.path.join.side_effect = OSError mocker.patch('qutebrowser.utils.version.utils.read_file', side_effect=OSError) with caplog.at_level(logging.ERROR, 'misc'): assert version._git_str() is None @pytest.mark.not_frozen def test_normal_path_nofile(self, monkeypatch, caplog, git_str_subprocess_fake, commit_file_mock): """Test with undefined __file__ but available git-commit-id.""" monkeypatch.delattr(version, '__file__') commit_file_mock.return_value = '0deadcode' with caplog.at_level(logging.ERROR, 'misc'): assert version._git_str() == '0deadcode' assert len(caplog.records) == 1 assert caplog.records[0].message == "Error while getting git path" def _has_git(): """Check if git is installed.""" try: subprocess.check_call(['git', '--version'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except (OSError, subprocess.CalledProcessError): return False else: return True # Decorator for tests needing git, so they get skipped when it's unavailable. needs_git = pytest.mark.skipif(not _has_git(), reason='Needs git installed.') class TestGitStrSubprocess: """Tests for _git_str_subprocess.""" @pytest.fixture def git_repo(self, tmpdir): """A fixture to create a temporary git repo. Some things are tested against a real repo so we notice if something in git would change, or we call git incorrectly. """ def _git(*args): """Helper closure to call git.""" env = os.environ.copy() env.update({ 'GIT_AUTHOR_NAME': 'qutebrowser testsuite', 'GIT_AUTHOR_EMAIL': 'mail@qutebrowser.org', 'GIT_AUTHOR_DATE': 'Thu 1 Jan 01:00:00 CET 1970', 'GIT_COMMITTER_NAME': 'qutebrowser testsuite', 'GIT_COMMITTER_EMAIL': 'mail@qutebrowser.org', 'GIT_COMMITTER_DATE': 'Thu 1 Jan 01:00:00 CET 1970', }) if os.name == 'nt': # If we don't call this with shell=True it might fail under # some environments on Windows... # http://bugs.python.org/issue24493 subprocess.check_call( 'git -C "{}" {}'.format(tmpdir, ' '.join(args)), env=env, shell=True) else: subprocess.check_call( ['git', '-C', str(tmpdir)] + list(args), env=env) (tmpdir / 'file').write_text("Hello World!", encoding='utf-8') _git('init') _git('add', 'file') _git('commit', '-am', 'foo', '--no-verify', '--no-edit', '--no-post-rewrite', '--quiet', '--no-gpg-sign') _git('tag', 'foobar') return tmpdir @needs_git def test_real_git(self, git_repo): """Test with a real git repository.""" ret = version._git_str_subprocess(str(git_repo)) assert ret == 'foobar (1970-01-01 01:00:00 +0100)' def test_missing_dir(self, tmpdir): """Test with a directory which doesn't exist.""" ret = version._git_str_subprocess(str(tmpdir / 'does-not-exist')) assert ret is None @pytest.mark.parametrize('exc', [ OSError, subprocess.CalledProcessError(1, 'foobar') ]) def test_exception(self, exc, mocker, tmpdir): """Test with subprocess.check_output raising an exception. Args: exc: The exception to raise. """ m = mocker.patch('qutebrowser.utils.version.os') m.path.isdir.return_value = True mocker.patch('qutebrowser.utils.version.subprocess.check_output', side_effect=exc) ret = version._git_str_subprocess(str(tmpdir)) assert ret is None class ReleaseInfoFake: """An object providing fakes for glob.glob/open for test_release_info. Attributes: _files: The files which should be returned, or None if an exception should be raised. A {filename: [lines]} dict. """ def __init__(self, files): self._files = files def glob_fake(self, pattern): """Fake for glob.glob. Verifies the arguments and returns the files listed in self._files, or a single fake file if an exception is expected. """ assert pattern == '/etc/*-release' if self._files is None: return ['fake-file'] else: return sorted(list(self._files)) @contextlib.contextmanager def open_fake(self, filename, mode, encoding): """Fake for open(). Verifies the arguments and returns a StringIO with the content listed in self._files. """ assert mode == 'r' assert encoding == 'utf-8' if self._files is None: raise OSError yield io.StringIO(''.join(self._files[filename])) @pytest.mark.parametrize('files, expected', [ # no files -> no output ({}, []), # empty files are stripped ({'file': ['']}, []), ({'file': []}, []), # newlines at EOL are stripped ( {'file1': ['foo\n', 'bar\n'], 'file2': ['baz\n']}, [('file1', 'foo\nbar'), ('file2', 'baz')] ), # blacklisted lines ( {'file': ['HOME_URL=example.com\n', 'NAME=FOO']}, [('file', 'NAME=FOO')] ), # only blacklisted lines ({'file': ['HOME_URL=example.com']}, []), # broken file (None, []), ]) def test_release_info(files, expected, caplog, monkeypatch): """Test _release_info(). Args: files: The file dict passed to ReleaseInfoFake. expected: The expected _release_info output. """ fake = ReleaseInfoFake(files) monkeypatch.setattr(version.glob, 'glob', fake.glob_fake) monkeypatch.setattr(version, 'open', fake.open_fake, raising=False) with caplog.at_level(logging.ERROR, 'misc'): assert version._release_info() == expected if files is None: assert len(caplog.records) == 1 assert caplog.records[0].message == "Error while reading fake-file." def test_path_info(monkeypatch): """Test _path_info().""" patches = { 'config': lambda: 'CONFIG PATH', 'data': lambda: 'DATA PATH', 'system_data': lambda: 'SYSTEM DATA PATH', 'cache': lambda: 'CACHE PATH', 'download': lambda: 'DOWNLOAD PATH', 'runtime': lambda: 'RUNTIME PATH', } for attr, val in patches.items(): monkeypatch.setattr(version.standarddir, attr, val) pathinfo = version._path_info() assert pathinfo['config'] == 'CONFIG PATH' assert pathinfo['data'] == 'DATA PATH' assert pathinfo['system_data'] == 'SYSTEM DATA PATH' assert pathinfo['cache'] == 'CACHE PATH' assert pathinfo['download'] == 'DOWNLOAD PATH' assert pathinfo['runtime'] == 'RUNTIME PATH' class ImportFake: """A fake for __import__ which is used by the import_fake fixture. Attributes: exists: A dict mapping module names to bools. If True, the import will success. 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.exists. """ def __init__(self): self.exists = { 'sip': True, 'colorama': True, 'pypeg2': True, 'jinja2': True, 'pygments': True, 'yaml': True, 'cssutils': True, 'typing': True, 'PyQt5.QtWebEngineWidgets': True, 'PyQt5.QtWebKitWidgets': True, } self.version_attribute = '__version__' self.version = '1.2.3' self._real_import = builtins.__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.exists: # Not one of the modules to test -> use real import return None elif self.exists[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 importlib.import_module(name) @pytest.fixture def import_fake(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) return fake class TestModuleVersions: """Tests for _module_versions().""" @pytest.mark.usefixtures('import_fake') def test_all_present(self): """Test with all modules present in version 1.2.3.""" expected = ['sip: yes', 'colorama: 1.2.3', 'pypeg2: 1.2.3', 'jinja2: 1.2.3', 'pygments: 1.2.3', 'yaml: 1.2.3', 'cssutils: 1.2.3', 'typing: yes', 'PyQt5.QtWebEngineWidgets: yes', 'PyQt5.QtWebKitWidgets: yes'] assert version._module_versions() == expected @pytest.mark.parametrize('module, idx, expected', [ ('colorama', 1, 'colorama: no'), ('cssutils', 6, 'cssutils: no'), ('typing', 7, 'typing: no'), ]) def test_missing_module(self, module, idx, expected, import_fake): """Test with a module missing. Args: module: The name of the missing module. idx: The index where the given text is expected. expected: The expected text. """ import_fake.exists[module] = False assert version._module_versions()[idx] == expected @pytest.mark.parametrize('value, expected', [ ('VERSION', ['sip: yes', 'colorama: 1.2.3', 'pypeg2: yes', 'jinja2: yes', 'pygments: yes', 'yaml: yes', 'cssutils: yes', 'typing: yes', 'PyQt5.QtWebEngineWidgets: yes', 'PyQt5.QtWebKitWidgets: yes']), ('SIP_VERSION_STR', ['sip: 1.2.3', 'colorama: yes', 'pypeg2: yes', 'jinja2: yes', 'pygments: yes', 'yaml: yes', 'cssutils: yes', 'typing: yes', 'PyQt5.QtWebEngineWidgets: yes', 'PyQt5.QtWebKitWidgets: yes']), (None, ['sip: yes', 'colorama: yes', 'pypeg2: yes', 'jinja2: yes', 'pygments: yes', 'yaml: yes', 'cssutils: yes', 'typing: yes', 'PyQt5.QtWebEngineWidgets: yes', 'PyQt5.QtWebKitWidgets: yes']), ]) def test_version_attribute(self, value, expected, import_fake): """Test with a different version attribute. VERSION is tested for old colorama versions, and None to make sure things still work if some package suddenly doesn't have __version__. Args: value: The name of the version attribute. expected: The expected return value. """ import_fake.version_attribute = value assert version._module_versions() == expected @pytest.mark.parametrize('name, has_version', [ ('sip', False), ('colorama', True), ('pypeg2', True), ('jinja2', True), ('pygments', True), ('yaml', True), ('cssutils', True), ]) def test_existing_attributes(self, name, has_version): """Check if all dependencies have an expected __version__ attribute. The aim of this test is to fail if modules suddenly don't have a __version__ attribute anymore in a newer version. Args: name: The name of the module to check. has_version: Whether a __version__ attribute is expected. """ module = importlib.import_module(name) assert hasattr(module, '__version__') == has_version def test_existing_sip_attribute(self): """Test if sip has a SIP_VERSION_STR attribute. The aim of this test is to fail if that gets missing in some future version of sip. """ import sip assert isinstance(sip.SIP_VERSION_STR, str) class TestOsInfo: """Tests for _os_info.""" def test_linux_fake(self, monkeypatch): """Test with a fake Linux. No args because osver is set to '' if the OS is linux. """ monkeypatch.setattr(version.sys, 'platform', 'linux') monkeypatch.setattr(version, '_release_info', lambda: [('releaseinfo', 'Hello World')]) ret = version._os_info() expected = ['OS Version: ', '', '--- releaseinfo ---', 'Hello World'] assert ret == expected def test_windows_fake(self, monkeypatch): """Test with a fake Windows.""" monkeypatch.setattr(version.sys, 'platform', 'win32') monkeypatch.setattr(version.platform, 'win32_ver', lambda: ('eggs', 'bacon', 'ham', 'spam')) ret = version._os_info() expected = ['OS Version: eggs, bacon, ham, spam'] assert ret == expected @pytest.mark.parametrize('mac_ver, mac_ver_str', [ (('x', ('', '', ''), 'y'), 'x, y'), (('', ('', '', ''), ''), ''), (('x', ('1', '2', '3'), 'y'), 'x, 1.2.3, y'), ]) def test_os_x_fake(self, monkeypatch, mac_ver, mac_ver_str): """Test with a fake OS X. Args: mac_ver: The tuple to set platform.mac_ver() to. mac_ver_str: The expected Mac version string in version._os_info(). """ monkeypatch.setattr(version.sys, 'platform', 'darwin') monkeypatch.setattr(version.platform, 'mac_ver', lambda: mac_ver) ret = version._os_info() expected = ['OS Version: {}'.format(mac_ver_str)] assert ret == expected def test_unknown_fake(self, monkeypatch): """Test with a fake unknown sys.platform.""" monkeypatch.setattr(version.sys, 'platform', 'toaster') ret = version._os_info() expected = ['OS Version: ?'] assert ret == expected @pytest.mark.linux def test_linux_real(self): """Make sure there are no exceptions with a real Linux.""" version._os_info() @pytest.mark.windows def test_windows_real(self): """Make sure there are no exceptions with a real Windows.""" version._os_info() @pytest.mark.osx def test_os_x_real(self): """Make sure there are no exceptions with a real OS X.""" version._os_info() class TestPDFJSVersion: """Tests for _pdfjs_version.""" def test_not_found(self, mocker): mocker.patch('qutebrowser.utils.version.pdfjs.get_pdfjs_res_and_path', side_effect=pdfjs.PDFJSNotFound('/build/pdf.js')) assert version._pdfjs_version() == 'no' def test_unknown(self, monkeypatch): monkeypatch.setattr( 'qutebrowser.utils.version.pdfjs.get_pdfjs_res_and_path', lambda path: (b'foobar', None)) assert version._pdfjs_version() == 'unknown (bundled)' @pytest.mark.parametrize('varname', [ 'PDFJS.version', # older versions 'var pdfjsVersion', # newer versions ]) def test_known(self, monkeypatch, varname): pdfjs_code = textwrap.dedent(""" // Initializing PDFJS global object (if still undefined) if (typeof PDFJS === 'undefined') { (typeof window !== 'undefined' ? window : this).PDFJS = {}; } VARNAME = '1.2.109'; PDFJS.build = '875588d'; (function pdfjsWrapper() { // Use strict in our context only - users might not want it 'use strict'; """.replace('VARNAME', varname)).strip().encode('utf-8') monkeypatch.setattr( 'qutebrowser.utils.version.pdfjs.get_pdfjs_res_and_path', lambda path: (pdfjs_code, '/foo/bar/pdf.js')) assert version._pdfjs_version() == '1.2.109 (/foo/bar/pdf.js)' def test_real_file(self): """Test against the real file if pdfjs was found.""" try: pdfjs.get_pdfjs_res_and_path('build/pdf.js') except pdfjs.PDFJSNotFound: pytest.skip("No pdfjs found") ver = version._pdfjs_version() assert ver.split()[0] not in ['no', 'unknown'], ver class FakeQSslSocket: """Fake for the QSslSocket Qt class. Attributes: _version: What QSslSocket::sslLibraryVersionString() should return. """ def __init__(self, version=None): self._version = version def supportsSsl(self): """Fake for QSslSocket::supportsSsl().""" return True def sslLibraryVersionString(self): """Fake for QSslSocket::sslLibraryVersionString().""" if self._version is None: raise AssertionError("Got called with version None!") return self._version @pytest.mark.parametrize('ua, expected', [ (None, 'unavailable'), # No QWebEngineProfile ('Mozilla/5.0', 'unknown'), ('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) ' 'QtWebEngine/5.8.0 Chrome/53.0.2785.148 Safari/537.36', '53.0.2785.148'), ]) def test_chromium_version(monkeypatch, caplog, ua, expected): if ua is None: monkeypatch.setattr(version, 'QWebEngineProfile', None) else: class FakeWebEngineProfile: def httpUserAgent(self): return ua monkeypatch.setattr(version, 'QWebEngineProfile', FakeWebEngineProfile) with caplog.at_level(logging.ERROR): assert version._chromium_version() == expected def test_chromium_version_unpatched(qapp): pytest.importorskip('PyQt5.QtWebEngineWidgets') assert version._chromium_version() not in ['', 'unknown', 'unavailable'] @pytest.mark.parametrize(['git_commit', 'frozen', 'style', 'with_webkit', 'known_distribution'], [ (True, False, True, True, True), # normal (False, False, True, True, True), # no git commit (True, True, True, True, True), # frozen (True, True, False, True, True), # no style (True, False, True, False, True), # no webkit (True, False, True, 'ng', True), # QtWebKit-NG (True, False, True, True, False), # unknown Linux distribution ]) def test_version_output(git_commit, frozen, style, with_webkit, known_distribution, stubs, monkeypatch): """Test version.version().""" class FakeWebEngineProfile: def httpUserAgent(self): return 'Toaster/4.0.4 Chrome/CHROMIUMVERSION Teapot/4.1.8' import_path = os.path.abspath('/IMPORTPATH') patches = { 'qutebrowser.__file__': os.path.join(import_path, '__init__.py'), 'qutebrowser.__version__': 'VERSION', '_git_str': lambda: ('GIT COMMIT' if git_commit else None), 'platform.python_implementation': lambda: 'PYTHON IMPLEMENTATION', 'platform.python_version': lambda: 'PYTHON VERSION', 'PYQT_VERSION_STR': 'PYQT VERSION', 'earlyinit.qt_version': lambda: 'QT VERSION', '_module_versions': lambda: ['MODULE VERSION 1', 'MODULE VERSION 2'], '_pdfjs_version': lambda: 'PDFJS VERSION', 'QSslSocket': FakeQSslSocket('SSL VERSION'), 'platform.platform': lambda: 'PLATFORM', 'platform.architecture': lambda: ('ARCHITECTURE', ''), '_os_info': lambda: ['OS INFO 1', 'OS INFO 2'], '_path_info': lambda: {'PATH DESC': 'PATH NAME'}, 'QApplication': (stubs.FakeQApplication(style='STYLE') if style else stubs.FakeQApplication(instance=None)), 'QLibraryInfo.location': (lambda _loc: 'QT PATH'), 'sql.version': lambda: 'SQLITE VERSION', } substitutions = { 'git_commit': '\nGit commit: GIT COMMIT' if git_commit else '', 'style': '\nStyle: STYLE' if style else '', 'qt': 'QT VERSION', 'frozen': str(frozen), 'import_path': import_path, } if with_webkit: patches['qWebKitVersion'] = lambda: 'WEBKIT VERSION' patches['objects.backend'] = usertypes.Backend.QtWebKit patches['QWebEngineProfile'] = None if with_webkit == 'ng': backend = 'QtWebKit-NG' patches['qtutils.is_qtwebkit_ng'] = lambda: True else: backend = 'legacy QtWebKit' patches['qtutils.is_qtwebkit_ng'] = lambda: False substitutions['backend'] = backend + ' (WebKit WEBKIT VERSION)' else: monkeypatch.delattr(version, 'qtutils.qWebKitVersion', raising=False) patches['objects.backend'] = usertypes.Backend.QtWebEngine patches['QWebEngineProfile'] = FakeWebEngineProfile substitutions['backend'] = 'QtWebEngine (Chromium CHROMIUMVERSION)' if known_distribution: patches['distribution'] = lambda: version.DistributionInfo( parsed=version.Distribution.arch, version=None, pretty='LINUX DISTRIBUTION', id='arch') substitutions['linuxdist'] = ('\nLinux distribution: ' 'LINUX DISTRIBUTION (arch)') substitutions['osinfo'] = '' else: patches['distribution'] = lambda: None substitutions['linuxdist'] = '' substitutions['osinfo'] = 'OS INFO 1\nOS INFO 2\n' for attr, val in patches.items(): monkeypatch.setattr('qutebrowser.utils.version.' + attr, val) if frozen: monkeypatch.setattr(sys, 'frozen', True, raising=False) else: monkeypatch.delattr(sys, 'frozen', raising=False) template = textwrap.dedent(""" qutebrowser vVERSION{git_commit} Backend: {backend} PYTHON IMPLEMENTATION: PYTHON VERSION Qt: {qt} PyQt: PYQT VERSION MODULE VERSION 1 MODULE VERSION 2 pdf.js: PDFJS VERSION sqlite: SQLITE VERSION SSL: SSL VERSION {style} Platform: PLATFORM, ARCHITECTURE{linuxdist} Frozen: {frozen} Imported from {import_path} Qt library executable path: QT PATH, data path: QT PATH {osinfo} Paths: PATH DESC: PATH NAME """.lstrip('\n')) expected = template.rstrip('\n').format(**substitutions) assert version.version() == expected @pytest.mark.skipif(not qtutils.version_check('5.4'), reason="Needs Qt >= 5.4.") def test_opengl_vendor(): """Simply call version.opengl_vendor() and see if it doesn't crash.""" pytest.importorskip("PyQt5.QtOpenGL") return version.opengl_vendor()