# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: # Copyright 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.config.configinit.""" import sys import logging import unittest.mock import pytest from qutebrowser import qutebrowser from qutebrowser.config import (config, configdata, configexc, configfiles, configinit) from qutebrowser.utils import objreg, usertypes @pytest.fixture def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir, data_tmpdir): monkeypatch.setattr(configdata, 'DATA', None) monkeypatch.setattr(configfiles, 'state', None) monkeypatch.setattr(config, 'instance', None) monkeypatch.setattr(config, 'key_instance', None) monkeypatch.setattr(config, 'change_filters', []) monkeypatch.setattr(configinit, '_init_errors', []) # Make sure we get no SSL warning monkeypatch.setattr(configinit.earlyinit, 'check_backend_ssl_support', lambda _backend: None) yield try: objreg.delete('config-commands') except KeyError: pass @pytest.mark.parametrize('load_autoconfig', [True, False]) # noqa @pytest.mark.parametrize('config_py', [True, 'error', False]) @pytest.mark.parametrize('invalid_yaml', ['42', 'unknown', 'wrong-type', False]) # pylint: disable=too-many-branches def test_early_init(init_patch, config_tmpdir, caplog, fake_args, load_autoconfig, config_py, invalid_yaml): # Prepare files autoconfig_file = config_tmpdir / 'autoconfig.yml' config_py_file = config_tmpdir / 'config.py' if invalid_yaml == '42': text = '42' elif invalid_yaml == 'unknown': text = 'global:\n colors.foobar: magenta\n' elif invalid_yaml == 'wrong-type': text = 'global:\n tabs.position: true\n' else: assert not invalid_yaml text = 'global:\n colors.hints.fg: magenta\n' autoconfig_file.write_text(text, 'utf-8', ensure=True) if config_py: config_py_lines = ['c.colors.hints.bg = "red"'] if not load_autoconfig: config_py_lines.append('config.load_autoconfig = False') if config_py == 'error': config_py_lines.append('c.foo = 42') config_py_file.write_text('\n'.join(config_py_lines), 'utf-8', ensure=True) with caplog.at_level(logging.ERROR): configinit.early_init(fake_args) # Check error messages expected_errors = [] if config_py == 'error': expected_errors.append( "Errors occurred while reading config.py:\n" " While setting 'foo': No option 'foo'") if load_autoconfig or not config_py: error = "Errors occurred while reading autoconfig.yml:\n" if invalid_yaml == '42': error += " While loading data: Toplevel object is not a dict" expected_errors.append(error) elif invalid_yaml == 'wrong-type': error += (" Error: Invalid value 'True' - expected a value of " "type str but got bool.") expected_errors.append(error) actual_errors = [str(err) for err in configinit._init_errors] assert actual_errors == expected_errors # Make sure things have been init'ed objreg.get('config-commands') assert isinstance(config.instance, config.Config) assert isinstance(config.key_instance, config.KeyConfig) # Check config values if config_py and load_autoconfig and not invalid_yaml: assert config.instance._values == { 'colors.hints.bg': 'red', 'colors.hints.fg': 'magenta', } elif config_py: assert config.instance._values == {'colors.hints.bg': 'red'} elif invalid_yaml: assert config.instance._values == {} else: assert config.instance._values == {'colors.hints.fg': 'magenta'} def test_early_init_invalid_change_filter(init_patch, fake_args): config.change_filter('foobar') with pytest.raises(configexc.NoOptionError): configinit.early_init(fake_args) @pytest.mark.parametrize('errors', [True, False]) def test_late_init(init_patch, monkeypatch, fake_save_manager, fake_args, mocker, errors): configinit.early_init(fake_args) if errors: err = configexc.ConfigErrorDesc("Error text", Exception("Exception")) errs = configexc.ConfigFileErrors("config.py", [err]) monkeypatch.setattr(configinit, '_init_errors', [errs]) msgbox_mock = mocker.patch('qutebrowser.config.configinit.msgbox.msgbox', autospec=True) configinit.late_init(fake_save_manager) fake_save_manager.add_saveable.assert_any_call( 'state-config', unittest.mock.ANY) fake_save_manager.add_saveable.assert_any_call( 'yaml-config', unittest.mock.ANY) if errors: assert len(msgbox_mock.call_args_list) == 1 _call_posargs, call_kwargs = msgbox_mock.call_args_list[0] text = call_kwargs['text'].strip() assert text.startswith('Errors occurred while reading config.py:') assert 'Error text: Exception' in text else: assert not msgbox_mock.called class TestQtArgs: @pytest.fixture def parser(self, mocker): """Fixture to provide an argparser. Monkey-patches .exit() of the argparser so it doesn't exit on errors. """ parser = qutebrowser.get_argparser() mocker.patch.object(parser, 'exit', side_effect=Exception) return parser @pytest.mark.parametrize('args, expected', [ # No Qt arguments (['--debug'], [sys.argv[0]]), # Qt flag (['--debug', '--qt-flag', 'reverse'], [sys.argv[0], '--reverse']), # Qt argument with value (['--qt-arg', 'stylesheet', 'foo'], [sys.argv[0], '--stylesheet', 'foo']), # --qt-arg given twice (['--qt-arg', 'stylesheet', 'foo', '--qt-arg', 'geometry', 'bar'], [sys.argv[0], '--stylesheet', 'foo', '--geometry', 'bar']), # --qt-flag given twice (['--qt-flag', 'foo', '--qt-flag', 'bar'], [sys.argv[0], '--foo', '--bar']), ]) def test_qt_args(self, config_stub, args, expected, parser): """Test commandline with no Qt arguments given.""" parsed = parser.parse_args(args) assert configinit.qt_args(parsed) == expected def test_qt_both(self, config_stub, parser): """Test commandline with a Qt argument and flag.""" args = parser.parse_args(['--qt-arg', 'stylesheet', 'foobar', '--qt-flag', 'reverse']) qt_args = configinit.qt_args(args) assert qt_args[0] == sys.argv[0] assert '--reverse' in qt_args assert '--stylesheet' in qt_args assert 'foobar' in qt_args def test_with_settings(self, config_stub, parser): parsed = parser.parse_args(['--qt-flag', 'foo']) config_stub.val.qt_args = ['bar'] assert configinit.qt_args(parsed) == [sys.argv[0], '--foo', '--bar'] @pytest.mark.parametrize('arg, confval, can_import, is_new_webkit, used', [ # overridden by commandline arg ('webkit', 'auto', False, False, usertypes.Backend.QtWebKit), # overridden by config (None, 'webkit', False, False, usertypes.Backend.QtWebKit), # WebKit available but too old (None, 'auto', True, False, usertypes.Backend.QtWebEngine), # WebKit available and new (None, 'auto', True, True, usertypes.Backend.QtWebKit), # WebKit unavailable (None, 'auto', False, False, usertypes.Backend.QtWebEngine), ]) def test_get_backend(monkeypatch, fake_args, config_stub, arg, confval, can_import, is_new_webkit, used): real_import = __import__ def fake_import(name, *args, **kwargs): if name != 'PyQt5.QtWebKit': return real_import(name, *args, **kwargs) if can_import: return None raise ImportError fake_args.backend = arg config_stub.val.backend = confval monkeypatch.setattr(configinit.qtutils, 'is_new_qtwebkit', lambda: is_new_webkit) monkeypatch.setattr('builtins.__import__', fake_import) assert configinit.get_backend(fake_args) == used