qutebrowser/tests/unit/utils/test_version.py

946 lines
32 KiB
Python
Raw Normal View History

# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2017-05-09 21:37:03 +02:00
# Copyright 2015-2017 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# 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 <http://www.gnu.org/licenses/>.
"""Tests for qutebrowser.utils.version."""
import io
import sys
import collections
import os.path
import subprocess
import contextlib
import builtins
import types
import importlib
import logging
import textwrap
2017-05-20 23:07:42 +02:00
import pkg_resources
2017-09-19 22:18:02 +02:00
import attr
import pytest
import qutebrowser
from qutebrowser.utils import version, usertypes, utils
2016-01-05 18:41:53 +01:00
from qutebrowser.browser import pdfjs
2017-05-20 23:07:42 +02:00
@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')),
2017-08-08 20:19:33 +02:00
# Funtoo
("""
ID="funtoo"
NAME="Funtoo GNU/Linux"
PRETTY_NAME="Linux"
""",
version.DistributionInfo(
id='funtoo', parsed=version.Distribution.gentoo,
version=None, pretty='Funtoo GNU/Linux')),
2017-05-20 23:07:42 +02:00
])
2017-05-30 07:37:10 +02:00
def test_distribution(tmpdir, monkeypatch, os_release, expected):
2017-05-20 23:07:42 +02:00
os_release_file = tmpdir / 'os-release'
if os_release is not None:
os_release_file.write(textwrap.dedent(os_release))
2017-05-30 07:37:10 +02:00
monkeypatch.setenv('QUTE_FAKE_OS_RELEASE', str(os_release_file))
assert version.distribution() == expected
2017-05-20 23:07:42 +02:00
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
2015-06-28 22:58:48 +02:00
@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'
2015-06-28 22:58:48 +02:00
@pytest.mark.frozen
2015-06-18 23:15:08 +02:00
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
2015-06-28 22:58:48 +02:00
@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 utils.is_windows:
# 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.
"""
2015-07-24 13:53:26 +02:00
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', [
2016-07-06 23:47:59 +02:00
# no files -> no output
({}, []),
2016-07-06 23:47:59 +02:00
# empty files are stripped
({'file': ['']}, []),
({'file': []}, []),
# newlines at EOL are stripped
(
{'file1': ['foo\n', 'bar\n'], 'file2': ['baz\n']},
2016-07-06 23:47:59 +02:00
[('file1', 'foo\nbar'), ('file2', 'baz')]
),
2016-07-06 23:47:59 +02:00
# 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."
@pytest.mark.parametrize('equal', [True, False])
def test_path_info(monkeypatch, equal):
"""Test _path_info().
Args:
equal: Whether system data / data and system config / config are equal.
"""
patches = {
'config': lambda auto=False:
'AUTO CONFIG PATH' if auto and not equal
else 'CONFIG PATH',
'data': lambda system=False:
'SYSTEM DATA PATH' if system and not equal
else 'DATA PATH',
'cache': lambda: 'CACHE PATH',
'runtime': lambda: 'RUNTIME PATH',
}
2017-09-19 22:18:02 +02:00
for name, val in patches.items():
monkeypatch.setattr(version.standarddir, name, val)
pathinfo = version._path_info()
assert pathinfo['config'] == 'CONFIG PATH'
assert pathinfo['data'] == 'DATA PATH'
assert pathinfo['cache'] == 'CACHE PATH'
assert pathinfo['runtime'] == 'RUNTIME PATH'
if equal:
assert 'auto config' not in pathinfo
assert 'system data' not in pathinfo
else:
assert pathinfo['auto config'] == 'AUTO CONFIG PATH'
assert pathinfo['system data'] == 'SYSTEM DATA PATH'
class ImportFake:
"""A fake for __import__ which is used by the import_fake fixture.
Attributes:
modules: 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. modules.
"""
def __init__(self):
self.modules = collections.OrderedDict([
('sip', True),
('colorama', True),
('pypeg2', True),
('jinja2', True),
('pygments', True),
('yaml', True),
('cssutils', True),
2017-09-19 22:18:02 +02:00
('attr', True),
('PyQt5.QtWebEngineWidgets', True),
('PyQt5.QtWebKitWidgets', True),
])
self.no_version_attribute = ['sip', 'PyQt5.QtWebEngineWidgets',
'PyQt5.QtWebKitWidgets']
self.version_attribute = '__version__'
self.version = '1.2.3'
self._real_import = builtins.__import__
self._real_importlib_import = importlib.import_module
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.modules:
# Not one of the modules to test -> use real import
return None
elif self.modules[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 self._real_importlib_import(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()."""
def test_all_present(self, import_fake):
"""Test with all modules present in version 1.2.3."""
expected = []
for name in import_fake.modules:
if name in import_fake.no_version_attribute:
expected.append('{}: yes'.format(name))
else:
expected.append('{}: 1.2.3'.format(name))
assert version._module_versions() == expected
@pytest.mark.parametrize('module, idx, expected', [
('colorama', 1, 'colorama: no'),
('cssutils', 6, 'cssutils: 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.modules[module] = False
assert version._module_versions()[idx] == expected
@pytest.mark.parametrize('attribute, expected_modules', [
('VERSION', ['colorama']),
('SIP_VERSION_STR', ['sip']),
(None, []),
])
def test_version_attribute(self, attribute, expected_modules, 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:
attribute: The name of the version attribute.
expected: The expected return value.
"""
import_fake.version_attribute = attribute
expected = []
for name in import_fake.modules:
if name in expected_modules:
expected.append('{}: 1.2.3'.format(name))
else:
expected.append('{}: yes'.format(name))
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),
2017-09-19 22:18:02 +02:00
('attr', 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."""
2017-09-20 11:28:19 +02:00
@pytest.mark.fake_os('linux')
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, '_release_info',
lambda: [('releaseinfo', 'Hello World')])
ret = version._os_info()
expected = ['OS Version: ', '',
'--- releaseinfo ---', 'Hello World']
assert ret == expected
2017-09-20 11:28:19 +02:00
@pytest.mark.fake_os('windows')
def test_windows_fake(self, monkeypatch):
"""Test with a fake Windows."""
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
2017-09-20 11:28:19 +02:00
@pytest.mark.fake_os('mac')
@pytest.mark.parametrize('mac_ver, mac_ver_str', [
(('x', ('', '', ''), 'y'), 'x, y'),
(('', ('', '', ''), ''), ''),
(('x', ('1', '2', '3'), 'y'), 'x, 1.2.3, y'),
])
2017-07-08 11:12:43 +02:00
def test_mac_fake(self, monkeypatch, mac_ver, mac_ver_str):
"""Test with a fake macOS.
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.platform, 'mac_ver', lambda: mac_ver)
ret = version._os_info()
expected = ['OS Version: {}'.format(mac_ver_str)]
assert ret == expected
2017-09-20 11:28:19 +02:00
@pytest.mark.fake_os('unknown')
def test_unknown_fake(self):
"""Test with a fake unknown platform."""
ret = version._os_info()
expected = ['OS Version: ?']
assert ret == expected
2015-06-28 22:58:48 +02:00
@pytest.mark.linux
def test_linux_real(self):
"""Make sure there are no exceptions with a real Linux."""
version._os_info()
2015-06-28 22:58:48 +02:00
@pytest.mark.windows
def test_windows_real(self):
"""Make sure there are no exceptions with a real Windows."""
version._os_info()
2017-07-08 11:12:43 +02:00
@pytest.mark.mac
def test_mac_real(self):
"""Make sure there are no exceptions with a real macOS."""
version._os_info()
2016-01-05 18:41:53 +01:00
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'))
2016-01-05 18:41:53 +01:00
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)'
2016-01-05 18:41:53 +01:00
@pytest.mark.parametrize('varname', [
'PDFJS.version', # older versions
'var pdfjsVersion', # newer versions
])
def test_known(self, monkeypatch, varname):
2016-01-05 18:41:53 +01:00
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';
2016-01-05 18:41:53 +01:00
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)'
2016-01-05 18:41:53 +01:00
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
2016-01-05 18:41:53 +01:00
class FakeQSslSocket:
"""Fake for the QSslSocket Qt class.
Attributes:
_version: What QSslSocket::sslLibraryVersionString() should return.
_support: Whether SSL is supported.
"""
def __init__(self, version=None, support=True):
self._version = version
self._support = support
def supportsSsl(self):
"""Fake for QSslSocket::supportsSsl()."""
return self._support
def sslLibraryVersionString(self):
"""Fake for QSslSocket::sslLibraryVersionString()."""
if self._version is None:
2015-10-04 15:41:42 +02:00
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']
2017-09-19 22:18:02 +02:00
@attr.s
class VersionParams:
2017-09-19 22:18:02 +02:00
name = attr.ib()
git_commit = attr.ib(True)
frozen = attr.ib(False)
style = attr.ib(True)
with_webkit = attr.ib(True)
known_distribution = attr.ib(True)
ssl_support = attr.ib(True)
@pytest.mark.parametrize('params', [
2017-07-06 12:25:11 +02:00
VersionParams('normal'),
VersionParams('no-git-commit', git_commit=False),
VersionParams('frozen', frozen=True),
VersionParams('no-style', style=False),
VersionParams('no-webkit', with_webkit=False),
VersionParams('unknown-dist', known_distribution=False),
VersionParams('no-ssl', ssl_support=False),
2017-07-07 15:21:18 +02:00
], ids=lambda param: param.name)
def test_version_output(params, 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 params.git_commit else None),
'platform.python_implementation': lambda: 'PYTHON IMPLEMENTATION',
'platform.python_version': lambda: 'PYTHON VERSION',
'PYQT_VERSION_STR': 'PYQT VERSION',
2017-06-19 09:42:49 +02:00
'earlyinit.qt_version': lambda: 'QT VERSION',
'_module_versions': lambda: ['MODULE VERSION 1', 'MODULE VERSION 2'],
2016-01-05 18:41:53 +01:00
'_pdfjs_version': lambda: 'PDFJS VERSION',
'QSslSocket': FakeQSslSocket('SSL VERSION', params.ssl_support),
'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 params.style else
2016-07-07 09:02:43 +02:00
stubs.FakeQApplication(instance=None)),
'QLibraryInfo.location': (lambda _loc: 'QT PATH'),
2017-02-20 14:33:38 +01:00
'sql.version': lambda: 'SQLITE VERSION',
}
substitutions = {
'git_commit': '\nGit commit: GIT COMMIT' if params.git_commit else '',
'style': '\nStyle: STYLE' if params.style else '',
'qt': 'QT VERSION',
'frozen': str(params.frozen),
'import_path': import_path,
}
if params.with_webkit:
patches['qWebKitVersion'] = lambda: 'WEBKIT VERSION'
patches['objects.backend'] = usertypes.Backend.QtWebKit
patches['QWebEngineProfile'] = None
2017-09-18 21:15:14 +02:00
substitutions['backend'] = 'new QtWebKit (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 params.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'
substitutions['ssl'] = 'SSL VERSION' if params.ssl_support else 'no'
2017-09-19 22:18:02 +02:00
for name, val in patches.items():
monkeypatch.setattr('qutebrowser.utils.version.' + name, val)
if params.frozen:
monkeypatch.setattr(sys, 'frozen', True, raising=False)
else:
monkeypatch.delattr(sys, 'frozen', raising=False)
template = textwrap.dedent("""
2017-02-05 17:16:47 +01:00
qutebrowser vVERSION{git_commit}
Backend: {backend}
PYTHON IMPLEMENTATION: PYTHON VERSION
2016-07-06 23:47:59 +02:00
Qt: {qt}
PyQt: PYQT VERSION
2016-07-06 23:47:59 +02:00
MODULE VERSION 1
MODULE VERSION 2
pdf.js: PDFJS VERSION
2017-02-20 14:33:38 +01:00
sqlite: SQLITE VERSION
QtNetwork SSL: {ssl}
2016-07-06 23:47:59 +02:00
{style}
Platform: PLATFORM, ARCHITECTURE{linuxdist}
2016-07-06 23:47:59 +02:00
Frozen: {frozen}
Imported from {import_path}
Qt library executable path: QT PATH, data path: QT PATH
{osinfo}
2016-09-29 06:35:47 +02:00
Paths:
PATH DESC: PATH NAME
""".lstrip('\n'))
expected = template.rstrip('\n').format(**substitutions)
2016-07-06 23:47:59 +02:00
assert version.version() == expected
def test_opengl_vendor():
"""Simply call version.opengl_vendor() and see if it doesn't crash."""
pytest.importorskip("PyQt5.QtOpenGL")
return version.opengl_vendor()