Make sure the config is available before the QApplication

See #2589
This commit is contained in:
Florian Bruhin 2017-09-17 20:06:35 +02:00
parent 21a10fcb17
commit 6f1b8bd1d9
5 changed files with 95 additions and 55 deletions

View File

@ -73,6 +73,9 @@ def run(args):
log.init.debug("Initializing directories...")
standarddir.init(args)
log.init.debug("Initializing config...")
config.early_init()
global qApp
qApp = Application(args)
qApp.setOrganizationName("qutebrowser")
@ -408,6 +411,7 @@ def _init_modules(args, crash_handler):
save_manager = savemanager.SaveManager(qApp)
objreg.register('save-manager', save_manager)
save_manager.add_saveable('version', _save_version)
config.late_init(save_manager)
log.init.debug("Initializing network...")
networkmanager.init()
@ -419,10 +423,6 @@ def _init_modules(args, crash_handler):
readline_bridge = readline.ReadlineBridge()
objreg.register('readline-bridge', readline_bridge)
log.init.debug("Initializing config...")
config.init(qApp)
save_manager.init_autosave()
log.init.debug("Initializing sql...")
try:
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))

View File

@ -39,6 +39,8 @@ key_instance = None
# Keeping track of all change filters to validate them later.
_change_filters = []
# Errors which happened during init, so we can show a message box.
_init_errors = []
class change_filter: # pylint: disable=invalid-name
@ -382,6 +384,14 @@ class Config(QObject):
self._mutables = []
self._yaml = yaml_config
def init_save_manager(self, save_manager):
"""Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before
the save_manager exists.
"""
self._yaml.init_save_manager(save_manager)
def _set_value(self, opt, value):
"""Set the given option to the given value."""
if objects.backend is not None:
@ -634,18 +644,14 @@ class StyleSheetObserver(QObject):
instance.changed.connect(self._update_stylesheet)
def init(parent=None):
"""Initialize the config.
Args:
parent: The parent to pass to QObjects which get initialized.
"""
def early_init():
"""Initialize the part of the config which works without a QApplication."""
configdata.init()
yaml_config = configfiles.YamlConfig()
global val, instance, key_instance
instance = Config(yaml_config=yaml_config, parent=parent)
instance = Config(yaml_config=yaml_config)
val = ConfigContainer(instance)
key_instance = KeyConfig(instance)
@ -666,12 +672,7 @@ def init(parent=None):
raise configexc.ConfigFileErrors('config.py', config_api.errors)
except configexc.ConfigFileErrors as e:
log.config.exception("Error while loading config.py")
errbox = msgbox.msgbox(parent=None,
title="Error while reading config",
text=e.to_html(),
icon=QMessageBox.Warning,
plain_text=False)
errbox.exec_()
_init_errors.append(e)
try:
if getattr(config_api, 'load_autoconfig', True):
@ -683,12 +684,23 @@ def init(parent=None):
desc = configexc.ConfigErrorDesc("Error", e)
raise configexc.ConfigFileErrors('autoconfig.yml', [desc])
except configexc.ConfigFileErrors as e:
log.config.exception("Error while loading autoconfig.yml")
log.config.exception("Error while loading config.py")
_init_errors.append(e)
configfiles.init()
def late_init(save_manager):
"""Initialize the rest of the config after the QApplication is created."""
global _init_errors
for err in _init_errors:
errbox = msgbox.msgbox(parent=None,
title="Error while reading config",
text=e.to_html(),
text=err.to_html(),
icon=QMessageBox.Warning,
plain_text=False)
errbox.exec_()
_init_errors = []
configfiles.init()
instance.init_save_manager(save_manager)
objreg.get('state-config').init_save_manager(save_manager)

View File

@ -39,7 +39,6 @@ class StateConfig(configparser.ConfigParser):
def __init__(self):
super().__init__()
save_manager = objreg.get('save-manager')
self._filename = os.path.join(standarddir.data(), 'state')
self.read(self._filename, encoding='utf-8')
for sect in ['general', 'geometry']:
@ -49,6 +48,13 @@ class StateConfig(configparser.ConfigParser):
pass
# See commit a98060e020a4ba83b663813a4b9404edb47f28ad.
self['general'].pop('fooled', None)
def init_save_manager(self, save_manager):
"""Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before
the save_manager exists.
"""
save_manager.add_saveable('state-config', self._save)
def _save(self):
@ -68,12 +74,18 @@ class YamlConfig:
VERSION = 1
def __init__(self):
save_manager = objreg.get('save-manager')
self._filename = os.path.join(standarddir.config(auto=True),
'autoconfig.yml')
save_manager.add_saveable('yaml-config', self._save)
self.values = {}
def init_save_manager(self, save_manager):
"""Make sure the config gets saved properly.
We do this outside of __init__ because the config gets created before
the save_manager exists.
"""
save_manager.add_saveable('yaml-config', self._save)
def _save(self):
"""Save the changed settings to the YAML file."""
data = {'config_version': self.VERSION, 'global': self.values}

View File

@ -113,19 +113,12 @@ class SaveManager(QObject):
self.saveables = collections.OrderedDict()
self._save_timer = usertypes.Timer(self, name='save-timer')
self._save_timer.timeout.connect(self.autosave)
self._set_autosave_interval()
config.instance.changed.connect(self._set_autosave_interval)
def __repr__(self):
return utils.get_repr(self, saveables=self.saveables)
def init_autosave(self):
"""Initialize auto-saving.
We don't do this in __init__ because the config needs to be initialized
first, but the config needs the save manager.
"""
self._set_autosave_interval()
config.instance.changed.connect(self._set_autosave_interval)
@config.change_filter('auto_save.interval')
def _set_autosave_interval(self):
"""Set the auto-save interval."""

View File

@ -873,6 +873,7 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir,
monkeypatch.setattr(config, 'instance', None)
monkeypatch.setattr(config, 'key_instance', None)
monkeypatch.setattr(config, '_change_filters', [])
monkeypatch.setattr(config, '_init_errors', [])
yield
for obj in ['config-commands', 'state-config']:
try:
@ -885,8 +886,8 @@ def init_patch(qapp, fake_save_manager, monkeypatch, config_tmpdir,
@pytest.mark.parametrize('config_py', [True, 'error', False])
@pytest.mark.parametrize('invalid_yaml', ['42', 'unknown', False])
# pylint: disable=too-many-branches
def test_init(init_patch, fake_save_manager, config_tmpdir, mocker, caplog,
load_autoconfig, config_py, invalid_yaml):
def test_early_init(init_patch, config_tmpdir, caplog,
load_autoconfig, config_py, invalid_yaml):
# Prepare files
autoconfig_file = config_tmpdir / 'autoconfig.yml'
config_py_file = config_tmpdir / 'config.py'
@ -910,36 +911,32 @@ def test_init(init_patch, fake_save_manager, config_tmpdir, mocker, caplog,
config_py_file.write_text('\n'.join(config_py_lines),
'utf-8', ensure=True)
msgbox_mock = mocker.patch('qutebrowser.config.config.msgbox.msgbox',
autospec=True)
with caplog.at_level(logging.ERROR):
config.init()
config.early_init()
# Check error messages
expected_errors = []
if config_py == 'error':
expected_errors.append("Errors occurred while reading config.py:")
expected_errors.append(
"Errors occurred while reading config.py:\n"
" While setting 'foo': No option 'foo'")
if invalid_yaml and (load_autoconfig or not config_py):
expected_errors.append("Errors occurred while reading autoconfig.yml:")
if expected_errors:
assert len(expected_errors) == len(msgbox_mock.call_args_list)
comparisons = zip(
expected_errors,
[call[1]['text'] for call in msgbox_mock.call_args_list])
for expected, actual in comparisons:
assert actual.strip().startswith(expected)
else:
assert not msgbox_mock.called
error = "Errors occurred while reading autoconfig.yml:\n"
if invalid_yaml == '42':
error += " While loading data: Toplevel object is not a dict"
elif invalid_yaml == 'unknown':
error += " Error: No option 'colors.foobar'"
else:
assert False, invalid_yaml
expected_errors.append(error)
actual_errors = [str(err) for err in config._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)
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)
# Check config values
if config_py and load_autoconfig and not invalid_yaml:
@ -955,7 +952,33 @@ def test_init(init_patch, fake_save_manager, config_tmpdir, mocker, caplog,
assert config.instance._values == {'colors.hints.fg': 'magenta'}
def test_init_invalid_change_filter(init_patch):
def test_early_init_invalid_change_filter(init_patch):
config.change_filter('foobar')
with pytest.raises(configexc.NoOptionError):
config.init()
config.early_init()
@pytest.mark.parametrize('errors', [True, False])
def test_late_init(init_patch, monkeypatch, fake_save_manager, mocker, errors):
config.early_init()
if errors:
err = configexc.ConfigErrorDesc("Error text", Exception("Exception"))
errs = configexc.ConfigFileErrors("config.py", [err])
monkeypatch.setattr(config, '_init_errors', [errs])
msgbox_mock = mocker.patch('qutebrowser.config.config.msgbox.msgbox',
autospec=True)
config.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 '<b>Error text</b>: Exception' in text
else:
assert not msgbox_mock.called