750ef834dc
Looks like the "black screen" issue isn't the only thing going wrong, some people even report segfaults since the vendor check was added.
917 lines
32 KiB
Python
917 lines
32 KiB
Python
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
# 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 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,
|
|
'OpenGL': 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', 'OpenGL: 1.2.3',
|
|
'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', 'OpenGL: 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', 'OpenGL: yes',
|
|
'PyQt5.QtWebEngineWidgets: yes',
|
|
'PyQt5.QtWebKitWidgets: yes']),
|
|
(None, ['sip: yes', 'colorama: yes', 'pypeg2: yes', 'jinja2: yes',
|
|
'pygments: yes', 'yaml: yes', 'cssutils: yes', 'typing: yes',
|
|
'OpenGL: 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
|
|
]) # pylint: disable=too-many-locals
|
|
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'),
|
|
}
|
|
|
|
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
|
|
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()
|