Merge branch 'standarddir'
This commit is contained in:
commit
36ca819cb3
@ -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
|
||||
~~~~~~~~~~~~~
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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'
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user