Merge branch 'standarddir'

This commit is contained in:
Florian Bruhin 2017-09-17 11:19:26 +02:00
commit 36ca819cb3
12 changed files with 230 additions and 215 deletions

View File

@ -30,6 +30,8 @@ Breaking changes
- New config system which ignores the old config file.
- The depedency on PyOpenGL (when using QtWebEngine) got removed. Note
that PyQt5.QtOpenGL is still a dependency.
- Migrating QtWebEngine data written by versions before 2016-11-15 (before
v0.9.0) is now not supported anymore.
Major changes
~~~~~~~~~~~~~

View File

@ -11,8 +11,8 @@ Migrating older configurations
------------------------------
qutebrowser does no automatic migration for the new configuration. However,
there's a special link:qute://configdiff[config diff page] which will show you
the changes you did in your old configuration, compared to the old defaults.
there's a special link:qute://configdiff[] page in qutebrowser, which will show
you the changes you did in your old configuration, compared to the old defaults.
Other changes in default settings:
@ -38,31 +38,34 @@ interface or command line. Changes you make this way are immediately active
(with the exception of a few settings, where this is pointed out in the
documentation) and are persisted in an `autoconfig.yml` file.
Using the link:commands.html#set[`:set`] command and command completion, you
can quickly set settings interactively, for example `:set tabs.position left`.
The `autoconfig.yml` file is located in the "config" folder listed on the
link:qute://version[] page. On macOS, the "auto config" folder is used, which is
different from where hand-written config files are kept.
To get more help about a setting, use e.g. `:help tabs.position`.
However, **do not** edit `autoconfig.yml` by hand. Instead, see the next
section.
If you want to customize many settings, you can open the link:qute://settings[]
page by running `:set` without any arguments, where all settings are listed and
customizable.
Using the link:commands.html#set[`:set`] command and command completion, you
can quickly set settings interactively, for example `:set tabs.position left`.
To get more help about a setting, use e.g. `:help tabs.position`.
To bind and unbind keys, you can use the link:commands.html#bind[`:bind`] and
link:commands.html#unbind[`:unbind`] commands:
- Binding a key: `:bind ,v spawn mpv {url}`
- Unbinding: `:unbind ,v`
- Changing an existing binding: `bind --force ,v message-info foo`
- Binding the key chain "`,`, `v`" to the `:spawn mpv {url}` command: `:bind ,v
spawn mpv {url}`
- Unbinding the same key chain: `:unbind ,v`
- Changing an existing binding: `bind --force ,v message-info foo`. Without
`--force`, qutebrowser will show an error because `,v` is already bound.
Key chains starting with a comma are ideal for custom bindings, as the comma key
will never be used in a default keybinding.
The `autoconfig.yml` file is located in the "config" folder listed on the
link:qute://version[] page. On macOS, the "auto config" folder is used, which is
different from where hand-written config files are kept.
**Do not** edit `autoconfig.yml` by hand. Instead, see the next section.
Configuring qutebrowser via config.py
-------------------------------------
@ -77,13 +80,14 @@ link:qute://version[], which is typically `~/.config/qutebrowser/config.py` on
Linux, `~/.qutebrowser/config.py` on macOS, and
`%APPDATA%/qutebrowser/config.py` on Windows.
Two global objects are pre-defined when executing the file: `c` and `config`.
Two global objects are pre-defined when running `config.py`: `c` and `config`.
Changing settings
~~~~~~~~~~~~~~~~~
`c` is a shorthand object to easily set settings like this:
.config.py:
[source,python]
----
c.tabs.position = "left"
@ -105,9 +109,9 @@ accepted values depend on the type of the option. Commonly used are:
previous elements.
* `c.url.start_pages.append("https://www.python.org/")` to add a new value.
Any other config types (e.g. a color) are specified as a string, with the
exception of the `Regex` type which can take either a string (with an `r` prefix
to preserve backslashes) or a Python regex object:
Any other config types (e.g. a color) are specified as a string. The only
exception is the `Regex` type, which can take either a string (with an `r`
prefix to preserve backslashes) or a Python regex object:
- `c.hints.next_regexes.append(r'\bvor\b')`
- `c.hints.prev_regexes.append(re.compile(r'\bzurück\b'))`
@ -122,6 +126,7 @@ Using strings for setting names
If you want to set settings based on their name as a string, use the
`config.set` method:
.config.py:
[source,python]
----
config.set('content.javascript.enabled', False)
@ -147,6 +152,7 @@ setting.
To bind a key:
.config.py:
[source,python]
----
config.bind(',v', 'spawn mpv {url}', mode='normal')
@ -179,6 +185,7 @@ If you want all customization done via `:set`, `:bind` and `:unbind` to be
temporary, you can suppress loading `autoconfig.yml` in your `config.py` by
doing:
.config.py:
[source,python]
----
config.load_autoconfig = False

View File

@ -70,6 +70,9 @@ def run(args):
quitter = Quitter(args)
objreg.register('quitter', quitter)
log.init.debug("Initializing directories...")
standarddir.init(args)
global qApp
qApp = Application(args)
qApp.setOrganizationName("qutebrowser")
@ -77,9 +80,6 @@ def run(args):
qApp.setApplicationVersion(qutebrowser.__version__)
qApp.lastWindowClosed.connect(quitter.on_last_window_closed)
log.init.debug("Initializing directories...")
standarddir.init(args)
if args.version:
print(version.version())
sys.exit(usertypes.Exit.ok)

View File

@ -132,7 +132,7 @@ class WebHistory(sql.SqlTable):
self._do_clear()
else:
message.confirm_async(self._do_clear, title="Clear all browsing "
"history?")
"history?")
def _do_clear(self):
self.delete_all()
@ -177,6 +177,9 @@ class WebHistory(sql.SqlTable):
log.misc.warning("Ignoring invalid URL being added to history")
return
if 'no-sql-history' in objreg.get('args').debug_flags:
return
atime = int(atime) if (atime is not None) else int(time.time())
self.insert({'url': self._format_url(url),
'title': title,

View File

@ -150,7 +150,7 @@ def debug_flag_error(flag):
debug-exit: Turn on debugging of late exit.
pdb-postmortem: Drop into pdb on exceptions.
"""
valid_flags = ['debug-exit', 'pdb-postmortem']
valid_flags = ['debug-exit', 'pdb-postmortem', 'no-sql-history']
if flag in valid_flags:
return flag

View File

@ -249,12 +249,14 @@ def unset_organization():
This is primarily needed in config.py.
"""
qapp = QApplication.instance()
orgname = qapp.organizationName()
qapp.setOrganizationName(None)
if qapp is not None:
orgname = qapp.organizationName()
qapp.setOrganizationName(None)
try:
yield
finally:
qapp.setOrganizationName(orgname)
if qapp is not None:
qapp.setOrganizationName(orgname)
class PyQIODevice(io.BufferedIOBase):

View File

@ -24,7 +24,7 @@ import sys
import shutil
import os.path
from PyQt5.QtCore import QCoreApplication, QStandardPaths
from PyQt5.QtCore import QStandardPaths
from qutebrowser.utils import log, qtutils, debug, usertypes, message
@ -37,6 +37,9 @@ Location = usertypes.enum('Location', ['config', 'auto_config',
'cache', 'download', 'runtime'])
APPNAME = 'qutebrowser'
class EmptyValueError(Exception):
"""Error raised when QStandardPaths returns an empty value."""
@ -53,11 +56,6 @@ def _init_config(args):
path = os.path.join(app_data_path, 'config')
else:
path = _writable_location(typ)
appname = QCoreApplication.instance().applicationName()
if path.split(os.sep)[-1] != appname: # pragma: no branch
# WORKAROUND - see
# https://bugreports.qt.io/browse/QTBUG-38872
path = os.path.join(path, appname)
_create(path)
_locations[Location.config] = path
_locations[Location.auto_config] = path
@ -66,7 +64,7 @@ def _init_config(args):
if sys.platform == 'darwin':
overridden, path = _from_args(typ, args)
if not overridden: # pragma: no branch
path = os.path.expanduser('~/.qutebrowser')
path = os.path.expanduser('~/.' + APPNAME)
_create(path)
_locations[Location.config] = path
@ -88,8 +86,7 @@ def _init_data(args):
overridden, path = _from_args(typ, args)
if not overridden:
if os.name == 'nt':
app_data_path = _writable_location(
QStandardPaths.AppDataLocation)
app_data_path = _writable_location(QStandardPaths.AppDataLocation)
path = os.path.join(app_data_path, 'data')
else:
path = _writable_location(typ)
@ -99,7 +96,7 @@ def _init_data(args):
# system_data
_locations.pop(Location.system_data, None) # Remove old state
if sys.platform.startswith('linux'):
path = "/usr/share/qutebrowser"
path = '/usr/share/' + APPNAME
if os.path.exists(path):
_locations[Location.system_data] = path
@ -123,7 +120,12 @@ def _init_cache(args):
typ = QStandardPaths.CacheLocation
overridden, path = _from_args(typ, args)
if not overridden:
path = _writable_location(typ)
if os.name == 'nt':
# Local, not Roaming!
data_path = _writable_location(QStandardPaths.DataLocation)
path = os.path.join(data_path, 'cache')
else:
path = _writable_location(typ)
_create(path)
_locations[Location.cache] = path
@ -169,6 +171,7 @@ def _init_runtime(args):
path = _writable_location(QStandardPaths.TempLocation)
# This is generic, but per-user.
# _writable_location makes sure we have a qutebrowser-specific subdir.
#
# For TempLocation:
# "The returned value might be application-specific, shared among
@ -176,8 +179,7 @@ def _init_runtime(args):
#
# Unfortunately this path could get too long for sockets (which have a
# maximum length of 104 chars), so we don't add the username here...
appname = QCoreApplication.instance().applicationName()
path = os.path.join(path, appname)
_create(path)
_locations[Location.runtime] = path
@ -187,15 +189,39 @@ def runtime():
def _writable_location(typ):
"""Wrapper around QStandardPaths.writableLocation."""
"""Wrapper around QStandardPaths.writableLocation.
Arguments:
typ: A QStandardPaths::StandardLocation member.
"""
typ_str = debug.qenum_key(QStandardPaths, typ)
# Types we are sure we handle correctly below.
assert typ in [
QStandardPaths.ConfigLocation, QStandardPaths.DataLocation,
QStandardPaths.CacheLocation, QStandardPaths.DownloadLocation,
QStandardPaths.RuntimeLocation, QStandardPaths.TempLocation,
# FIXME old Qt
getattr(QStandardPaths, 'AppDataLocation', object())], typ_str
with qtutils.unset_organization():
path = QStandardPaths.writableLocation(typ)
typ_str = debug.qenum_key(QStandardPaths, typ)
log.misc.debug("writable location for {}: {}".format(typ_str, path))
if not path:
raise EmptyValueError("QStandardPaths returned an empty value!")
# Qt seems to use '/' as path separator even on Windows...
path = path.replace('/', os.sep)
# Add the application name to the given path if needed.
# This is in order for this to work without a QApplication (and thus
# QStandardsPaths not knowing the application name), as well as a
# workaround for https://bugreports.qt.io/browse/QTBUG-38872
if (typ != QStandardPaths.DownloadLocation and
path.split(os.sep)[-1] != APPNAME):
path = os.path.join(path, APPNAME)
return path
@ -267,7 +293,6 @@ def init(args):
_init_dirs(args)
_init_cachedir_tag()
if args is not None and getattr(args, 'basedir', None) is None:
_move_webengine_data()
if sys.platform == 'darwin': # pragma: no cover
_move_macos()
elif os.name == 'nt': # pragma: no cover
@ -304,7 +329,7 @@ def _move_windows():
for f in os.listdir(old_appdata_dir):
if f != 'cache':
_move_data(os.path.join(old_appdata_dir, f),
os.path.join(new_config_dir, f))
os.path.join(new_config_dir, f))
def _init_cachedir_tag():
@ -326,34 +351,6 @@ def _init_cachedir_tag():
log.init.exception("Failed to create CACHEDIR.TAG")
def _move_webengine_data():
"""Move QtWebEngine data from an older location to the new one."""
# Do NOT use _writable_location here as that'd give us a wrong path
old_data_dir = QStandardPaths.writableLocation(QStandardPaths.DataLocation)
new_data_dir = os.path.join(data(), 'webengine')
ok = _move_data(os.path.join(old_data_dir, 'QtWebEngine', 'Default'),
new_data_dir)
if not ok:
return
old_cache_dir = QStandardPaths.writableLocation(
QStandardPaths.CacheLocation)
new_cache_dir = os.path.join(cache(), 'webengine')
ok = _move_data(os.path.join(old_cache_dir, 'QtWebEngine', 'Default'),
new_cache_dir)
if not ok:
return
# Remove e.g.
# ~/.local/share/qutebrowser/qutebrowser/QtWebEngine/Default
if old_data_dir.split(os.sep)[-2:] == ['qutebrowser', 'qutebrowser']:
log.init.debug("Removing {} / {}".format(
old_data_dir, old_cache_dir))
for old_dir in old_data_dir, old_cache_dir:
os.rmdir(os.path.join(old_dir, 'QtWebEngine'))
os.rmdir(old_dir)
def _move_data(old, new):
"""Migrate data from an old to a new directory.

View File

@ -54,6 +54,7 @@ Feature: Javascript stuff
And I wait for "Focus object changed: *" in the log
Then no crash should happen
@flaky
Scenario: Opening window without user interaction with content.javascript.can_open_tabs_automatically set to true
When I open data/hello.txt
And I set content.javascript.can_open_tabs_automatically to true

View File

@ -20,11 +20,19 @@
import logging
import re
import pytest
import pytest_bdd as bdd
bdd.scenarios('history.feature')
@pytest.fixture(autouse=True)
def turn_on_sql_history(quteproc):
"""Make sure SQL writing is enabled for tests in this module."""
quteproc.send_cmd(":debug-pyeval objreg.get('args').debug_flags.remove('no-sql-history')")
quteproc.wait_for_load_finished_url('qute://pyeval')
@bdd.then(bdd.parsers.parse("the history should contain:\n{expected}"))
def check_history(quteproc, httpbin, tmpdir, expected):
path = tmpdir / 'history'

View File

@ -401,7 +401,8 @@ class QuteProc(testprocess.Process):
backend = 'webengine' if self.request.config.webengine else 'webkit'
return ['--debug', '--no-err-windows', '--temp-basedir',
'--json-logging', '--loglevel', 'vdebug',
'--backend', backend, 'about:blank']
'--backend', backend, '--debug-flag', 'no-sql-history',
'about:blank']
def path_to_url(self, path, *, port=None, https=False):
"""Get a URL based on a filename for the localhost webserver.

View File

@ -212,14 +212,14 @@ class TestSocketName:
def test_mac(self, basedir, expected):
socketname = ipc._get_socketname(basedir)
parts = socketname.split(os.sep)
assert parts[-2] == 'qute_test'
assert parts[-2] == 'qutebrowser'
assert parts[-1] == expected
@pytest.mark.linux
@pytest.mark.parametrize('basedir, expected', POSIX_TESTS)
def test_linux(self, basedir, fake_runtime_dir, expected):
socketname = ipc._get_socketname(basedir)
expected_path = str(fake_runtime_dir / 'qute_test' / expected)
expected_path = str(fake_runtime_dir / 'qutebrowser' / expected)
assert socketname == expected_path
def test_other_unix(self):

View File

@ -21,11 +21,13 @@
import os
import sys
import json
import os.path
import types
import textwrap
import collections
import logging
import textwrap
import subprocess
from PyQt5.QtCore import QStandardPaths
import pytest
@ -33,13 +35,23 @@ import pytest
from qutebrowser.utils import standarddir
# Use a different application name for tests to make sure we don't change real
# qutebrowser data if we accidentally access the real path in a test.
APPNAME = 'qute_test'
pytestmark = pytest.mark.usefixtures('qapp')
@pytest.fixture(autouse=True)
def clear_standarddir_cache(monkeypatch):
"""Make sure the standarddir cache is cleared before/after each test."""
def clear_standarddir_cache_and_patch(qapp, monkeypatch):
"""Make sure the standarddir cache is cleared before/after each test.
Also, patch APPNAME to qute_test.
"""
assert qapp.applicationName() == APPNAME
monkeypatch.setattr(standarddir, '_locations', {})
monkeypatch.setattr(standarddir, 'APPNAME', APPNAME)
yield
monkeypatch.setattr(standarddir, '_locations', {})
@ -48,24 +60,27 @@ def test_fake_mac_config(tmpdir, monkeypatch):
"""Test standardir.config on a fake Mac."""
monkeypatch.setattr(sys, 'platform', 'darwin')
monkeypatch.setenv('HOME', str(tmpdir))
expected = str(tmpdir) + '/.qutebrowser' # always with /
expected = str(tmpdir) + '/.qute_test' # always with /
standarddir._init_config(args=None)
assert standarddir.config() == expected
# FIXME:conf needs AppDataLocation
@pytest.mark.qt55
@pytest.mark.parametrize('what', ['data', 'config'])
@pytest.mark.parametrize('what', ['data', 'config', 'cache'])
@pytest.mark.not_mac
def test_fake_windows_data_config(tmpdir, monkeypatch, what):
"""Make sure the config is correct on a fake Windows."""
def test_fake_windows(tmpdir, monkeypatch, what):
"""Make sure the config/data/cache dirs are correct on a fake Windows."""
monkeypatch.setattr(os, 'name', 'nt')
monkeypatch.setattr(standarddir.QStandardPaths, 'writableLocation',
lambda typ: str(tmpdir))
lambda typ: str(tmpdir / APPNAME))
standarddir._init_config(args=None)
standarddir._init_data(args=None)
standarddir._init_cache(args=None)
func = getattr(standarddir, what)
assert func() == str(tmpdir / what)
assert func() == str(tmpdir / APPNAME / what)
class TestWritableLocation:
@ -83,6 +98,8 @@ class TestWritableLocation:
def test_sep(self, monkeypatch):
"""Make sure the right kind of separator is used."""
monkeypatch.setattr(standarddir.os, 'sep', '\\')
monkeypatch.setattr(standarddir.os.path, 'join',
lambda *parts: '\\'.join(parts))
loc = standarddir._writable_location(QStandardPaths.DataLocation)
assert '/' not in loc
assert '\\' in loc
@ -109,13 +126,13 @@ class TestStandardDir:
"""
monkeypatch.setenv(varname, str(tmpdir))
standarddir._init_dirs()
assert func() == str(tmpdir / 'qute_test')
assert func() == str(tmpdir / APPNAME)
@pytest.mark.parametrize('func, subdirs', [
(standarddir.data, ['.local', 'share', 'qute_test']),
(standarddir.config, ['.config', 'qute_test']),
(lambda: standarddir.config(auto=True), ['.config', 'qute_test']),
(standarddir.cache, ['.cache', 'qute_test']),
(standarddir.data, ['.local', 'share', APPNAME]),
(standarddir.config, ['.config', APPNAME]),
(lambda: standarddir.config(auto=True), ['.config', APPNAME]),
(standarddir.cache, ['.cache', APPNAME]),
(standarddir.download, ['Downloads']),
])
@pytest.mark.linux
@ -134,7 +151,7 @@ class TestStandardDir:
monkeypatch.setenv('XDG_RUNTIME_DIR', str(tmpdir / 'does-not-exist'))
monkeypatch.setenv('TMPDIR', str(tmpdir / 'temp'))
standarddir._init_dirs()
assert standarddir.runtime() == str(tmpdir / 'temp' / 'qute_test')
assert standarddir.runtime() == str(tmpdir / 'temp' / APPNAME)
def test_runtimedir_empty_tempdir(self, monkeypatch, tmpdir):
"""With an empty tempdir on non-Linux, we should raise."""
@ -145,10 +162,10 @@ class TestStandardDir:
standarddir._init_runtime(args=None)
@pytest.mark.parametrize('func, elems, expected', [
(standarddir.data, 2, ['qute_test', 'data']),
(standarddir.config, 2, ['qute_test', 'config']),
(lambda: standarddir.config(auto=True), 2, ['qute_test', 'config']),
(standarddir.cache, 2, ['qute_test', 'cache']),
(standarddir.data, 2, [APPNAME, 'data']),
(standarddir.config, 2, [APPNAME, 'config']),
(lambda: standarddir.config(auto=True), 2, [APPNAME, 'config']),
(standarddir.cache, 2, [APPNAME, 'cache']),
(standarddir.download, 1, ['Downloads']),
])
@pytest.mark.windows
@ -157,11 +174,11 @@ class TestStandardDir:
assert func().split(os.sep)[-elems:] == expected
@pytest.mark.parametrize('func, elems, expected', [
(standarddir.data, 2, ['Application Support', 'qute_test']),
(lambda: standarddir.config(auto=True), 1, ['qute_test']),
(standarddir.data, 2, ['Application Support', APPNAME]),
(lambda: standarddir.config(auto=True), 1, [APPNAME]),
(standarddir.config, 0,
os.path.expanduser('~').split(os.sep) + ['.qutebrowser']),
(standarddir.cache, 2, ['Caches', 'qute_test']),
os.path.expanduser('~').split(os.sep) + ['.qute_test']),
(standarddir.cache, 2, ['Caches', APPNAME]),
(standarddir.download, 1, ['Downloads']),
])
@pytest.mark.mac
@ -291,11 +308,11 @@ class TestSystemData:
"""Test system data path."""
def test_system_datadir_exist_linux(self, monkeypatch):
"""Test that /usr/share/qutebrowser is used if path exists."""
"""Test that /usr/share/qute_test is used if path exists."""
monkeypatch.setattr('sys.platform', "linux")
monkeypatch.setattr(os.path, 'exists', lambda path: True)
standarddir._init_dirs()
assert standarddir.data(system=True) == "/usr/share/qutebrowser"
assert standarddir.data(system=True) == "/usr/share/qute_test"
@pytest.mark.linux
def test_system_datadir_not_exist_linux(self, monkeypatch, tmpdir,
@ -317,24 +334,18 @@ class TestSystemData:
# FIXME:conf needs AppDataLocation
@pytest.mark.qt55
class TestDataMigrations:
class TestMoveWindowsAndMacOS:
"""Test moving various data from an old to a new location."""
"""Test other invocations of _move_data."""
@pytest.fixture(autouse=True)
def patch_standardpaths(self, files, tmpdir, monkeypatch):
def patch_standardpaths(self, files, monkeypatch):
locations = {
QStandardPaths.DataLocation: str(files.local_data_dir),
QStandardPaths.CacheLocation: str(tmpdir / 'cache'),
QStandardPaths.AppDataLocation: str(files.roaming_data_dir),
}
monkeypatch.setattr(standarddir.QStandardPaths, 'writableLocation',
locations.get)
monkeypatch.setattr(standarddir, 'data',
lambda: str(tmpdir / 'new_data'))
monkeypatch.setattr(standarddir, 'cache',
lambda: str(tmpdir / 'new_cache'))
monkeypatch.setattr(
standarddir, 'config', lambda auto=False:
str(files.auto_config_dir if auto else files.config_dir))
@ -342,112 +353,15 @@ class TestDataMigrations:
@pytest.fixture
def files(self, tmpdir):
files = collections.namedtuple('Files', [
'old_webengine_data', 'new_webengine_data',
'old_webengine_cache', 'new_webengine_cache',
'auto_config_dir', 'config_dir',
'local_data_dir', 'roaming_data_dir'])
return files(
old_webengine_data=(tmpdir / 'data' / 'QtWebEngine' / 'Default' /
'datafile'),
new_webengine_data=tmpdir / 'new_data' / 'webengine' / 'datafile',
old_webengine_cache=(tmpdir / 'cache' / 'QtWebEngine' / 'Default' /
'cachefile'),
new_webengine_cache=(tmpdir / 'new_cache' / 'webengine' /
'cachefile'),
auto_config_dir=tmpdir / 'auto_config',
config_dir=tmpdir / 'config',
local_data_dir=tmpdir / 'data',
roaming_data_dir=tmpdir / 'roaming-data',
auto_config_dir=tmpdir / 'auto_config' / APPNAME,
config_dir=tmpdir / 'config' / APPNAME,
local_data_dir=tmpdir / 'data' / APPNAME,
roaming_data_dir=tmpdir / 'roaming-data' / APPNAME,
)
def test_no_webengine_dir(self, caplog):
"""Nothing should happen without any QtWebEngine directory."""
standarddir._move_webengine_data()
assert not any(rec.message.startswith('Moving QtWebEngine')
for rec in caplog.records)
def test_moving_data(self, files):
files.old_webengine_data.ensure()
files.old_webengine_cache.ensure()
standarddir._move_webengine_data()
assert not files.old_webengine_data.exists()
assert not files.old_webengine_cache.exists()
assert files.new_webengine_data.exists()
assert files.new_webengine_cache.exists()
@pytest.mark.parametrize('what', ['data', 'cache'])
def test_already_existing(self, files, caplog, what):
files.old_webengine_data.ensure()
files.old_webengine_cache.ensure()
if what == 'data':
files.new_webengine_data.ensure()
old_path = str(files.old_webengine_data.dirname)
new_path = str(files.new_webengine_data.dirname)
else:
files.new_webengine_cache.ensure()
old_path = str(files.old_webengine_cache.dirname)
new_path = str(files.new_webengine_cache.dirname)
with caplog.at_level(logging.ERROR):
standarddir._move_webengine_data()
record = caplog.records[-1]
expected = "Failed to move data from {} as {} is non-empty!".format(
old_path, new_path)
assert record.message == expected
def test_deleting_empty_dirs(self, monkeypatch, tmpdir):
"""When we have a qutebrowser/qutebrowser subfolder, clean it up."""
old_data = tmpdir / 'data' / 'qutebrowser' / 'qutebrowser'
old_cache = tmpdir / 'cache' / 'qutebrowser' / 'qutebrowser'
locations = {
QStandardPaths.DataLocation: str(old_data),
QStandardPaths.CacheLocation: str(old_cache),
}
monkeypatch.setattr(standarddir.QStandardPaths, 'writableLocation',
locations.get)
old_data_file = old_data / 'QtWebEngine' / 'Default' / 'datafile'
old_cache_file = old_cache / 'QtWebEngine' / 'Default' / 'cachefile'
old_data_file.ensure()
old_cache_file.ensure()
standarddir._move_webengine_data()
assert not (tmpdir / 'data' / 'qutebrowser' / 'qutebrowser').exists()
assert not (tmpdir / 'cache' / 'qutebrowser' / 'qutebrowser').exists()
def test_deleting_error(self, files, monkeypatch, mocker, caplog):
"""When there was an error it should be logged."""
mock = mocker.Mock(side_effect=OSError('error'))
monkeypatch.setattr(standarddir.shutil, 'move', mock)
files.old_webengine_data.ensure()
files.old_webengine_cache.ensure()
with caplog.at_level(logging.ERROR):
standarddir._move_webengine_data()
record = caplog.records[-1]
expected = "Failed to move data from {} to {}: error".format(
files.old_webengine_data.dirname, files.new_webengine_data.dirname)
assert record.message == expected
def test_existing_but_empty(self, tmpdir):
"""Make sure moving works with an empty destination dir."""
old_dir = tmpdir / 'old' / 'foo'
new_dir = tmpdir / 'new' / 'foo'
old_file = old_dir / 'file'
new_file = new_dir / 'file'
old_file.ensure()
new_dir.ensure(dir=True)
standarddir._move_data(str(old_dir), str(new_dir))
assert not old_file.exists()
assert new_file.exists()
def test_move_macos(self, files):
"""Test moving configs on macOS."""
(files.auto_config_dir / 'autoconfig.yml').ensure()
@ -476,16 +390,68 @@ class TestDataMigrations:
assert (files.local_data_dir / 'cache' / 'cachefile').exists()
class TestMove:
@pytest.fixture
def dirs(self, tmpdir):
dirs = collections.namedtuple('Dirs', ['old', 'new',
'old_file', 'new_file'])
old_dir = tmpdir / 'old'
new_dir = tmpdir / 'new'
return dirs(old=old_dir, new=new_dir,
old_file=old_dir / 'file', new_file=new_dir / 'file')
def test_no_old_dir(self, dirs, caplog):
"""Nothing should happen without any old directory."""
standarddir._move_data(str(dirs.old), str(dirs.new))
assert not any(rec.message.startswith('Migrating data from')
for rec in caplog.records)
@pytest.mark.parametrize('empty_dest', [True, False])
def test_moving_data(self, dirs, empty_dest):
dirs.old_file.ensure()
if empty_dest:
dirs.new.ensure(dir=True)
standarddir._move_data(str(dirs.old), str(dirs.new))
assert not dirs.old_file.exists()
assert dirs.new_file.exists()
def test_already_existing(self, dirs, caplog):
dirs.old_file.ensure()
dirs.new_file.ensure()
with caplog.at_level(logging.ERROR):
standarddir._move_data(str(dirs.old), str(dirs.new))
record = caplog.records[-1]
expected = "Failed to move data from {} as {} is non-empty!".format(
dirs.old, dirs.new)
assert record.message == expected
def test_deleting_error(self, dirs, monkeypatch, mocker, caplog):
"""When there was an error it should be logged."""
mock = mocker.Mock(side_effect=OSError('error'))
monkeypatch.setattr(standarddir.shutil, 'move', mock)
dirs.old_file.ensure()
with caplog.at_level(logging.ERROR):
standarddir._move_data(str(dirs.old), str(dirs.new))
record = caplog.records[-1]
expected = "Failed to move data from {} to {}: error".format(
dirs.old, dirs.new)
assert record.message == expected
@pytest.mark.parametrize('args_kind', ['basedir', 'normal', 'none'])
def test_init(mocker, tmpdir, args_kind):
"""Do some sanity checks for standarddir.init().
Things like _init_cachedir_tag() and _move_webengine_data() are tested in
more detail in other tests.
Things like _init_cachedir_tag() are tested in more detail in other tests.
"""
assert standarddir._locations == {}
m = mocker.patch('qutebrowser.utils.standarddir._move_webengine_data')
m_windows = mocker.patch('qutebrowser.utils.standarddir._move_windows')
m_mac = mocker.patch('qutebrowser.utils.standarddir._move_macos')
if args_kind == 'normal':
@ -500,7 +466,6 @@ def test_init(mocker, tmpdir, args_kind):
assert standarddir._locations != {}
if args_kind == 'normal':
assert m.called
if sys.platform == 'darwin':
assert not m_windows.called
assert m_mac.called
@ -511,13 +476,12 @@ def test_init(mocker, tmpdir, args_kind):
assert not m_windows.called
assert not m_mac.called
else:
assert not m.called
assert not m_windows.called
assert not m_mac.called
@pytest.mark.linux
def test_downloads_dir_not_crated(monkeypatch, tmpdir):
def test_downloads_dir_not_created(monkeypatch, tmpdir):
"""Make sure ~/Downloads is not created."""
download_dir = tmpdir / 'Downloads'
monkeypatch.setenv('HOME', str(tmpdir))
@ -526,3 +490,33 @@ def test_downloads_dir_not_crated(monkeypatch, tmpdir):
standarddir._init_dirs()
assert standarddir.download() == str(download_dir)
assert not download_dir.exists()
def test_no_qapplication(qapp, tmpdir):
"""Make sure directories with/without QApplication are equal."""
sub_code = """
import sys
import json
sys.path = sys.argv[1:] # make sure we have the same python path
from PyQt5.QtWidgets import QApplication
from qutebrowser.utils import standarddir
assert QApplication.instance() is None
standarddir.APPNAME = 'qute_test'
standarddir._init_dirs()
locations = {k.name: v for k, v in standarddir._locations.items()}
print(json.dumps(locations))
"""
pyfile = tmpdir / 'sub.py'
pyfile.write_text(textwrap.dedent(sub_code), encoding='ascii')
output = subprocess.check_output([sys.executable, str(pyfile)] + sys.path,
universal_newlines=True)
sub_locations = json.loads(output)
standarddir._init_dirs()
locations = {k.name: v for k, v in standarddir._locations.items()}
assert sub_locations == locations