qutebrowser/tests/unit/misc/test_sessions.py
Florian Bruhin 8ffe591f98 Skip TestSave.test_long_output on Windows.
This seems to segfault unpredictably when exiting pytest and I can't find out
why.

Fixes #895.
2015-08-26 10:06:49 +02:00

829 lines
28 KiB
Python

# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2015 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.misc.sessions."""
import os
import textwrap
import logging
import pytest
import yaml
from PyQt5.QtCore import QUrl, QPoint, QByteArray
from PyQt5.QtWebKitWidgets import QWebView
from qutebrowser.misc import sessions
from qutebrowser.utils import objreg, qtutils
from qutebrowser.browser import tabhistory
from qutebrowser.browser.tabhistory import TabHistoryItem as Item
from qutebrowser.commands import cmdexc
@pytest.fixture
def sess_man():
"""Fixture providing a SessionManager with no session dir."""
return sessions.SessionManager(base_path=None)
class TestInit:
@pytest.yield_fixture(autouse=True)
def cleanup(self):
yield
objreg.delete('session-manager')
def test_no_standarddir(self, monkeypatch):
monkeypatch.setattr('qutebrowser.misc.sessions.standarddir.data',
lambda: None)
sessions.init()
manager = objreg.get('session-manager')
assert manager._base_path is None
@pytest.mark.parametrize('create_dir', [True, False])
def test_with_standarddir(self, tmpdir, monkeypatch, create_dir):
monkeypatch.setattr('qutebrowser.misc.sessions.standarddir.data',
lambda: str(tmpdir))
session_dir = tmpdir / 'sessions'
if create_dir:
session_dir.ensure(dir=True)
sessions.init()
manager = objreg.get('session-manager')
assert session_dir.exists()
assert manager._base_path == str(session_dir)
def test_did_not_load(sess_man):
assert not sess_man.did_load
class TestExists:
@pytest.mark.parametrize('absolute', [True, False])
def test_existant(self, tmpdir, absolute):
session_dir = tmpdir / 'sessions'
abs_session = tmpdir / 'foo.yml'
rel_session = session_dir / 'foo.yml'
session_dir.ensure(dir=True)
abs_session.ensure()
rel_session.ensure()
man = sessions.SessionManager(str(session_dir))
if absolute:
name = str(abs_session)
else:
name = 'foo'
assert man.exists(name)
@pytest.mark.parametrize('absolute', [True, False])
def test_inexistant(self, tmpdir, absolute):
man = sessions.SessionManager(str(tmpdir))
if absolute:
name = str(tmpdir / 'foo')
else:
name = 'foo'
assert not man.exists(name)
@pytest.mark.parametrize('absolute', [True, False])
def test_no_datadir(self, sess_man, tmpdir, absolute):
abs_session = tmpdir / 'foo.yml'
abs_session.ensure()
if absolute:
assert sess_man.exists(str(abs_session))
else:
assert not sess_man.exists('foo')
class HistTester:
"""Helper object for the hist_tester fixture.
Makes it possible to use tabhistory.TabHistoryItem objects to easily load
data into a QWebHistory, does some basic checks, and provides the
serialized history.
Attributes:
sess_man: The SessionManager which is used.
webview: The WebView where the history is loaded to.
"""
def __init__(self, webview):
self.sess_man = sessions.SessionManager(base_path=None)
self.webview = webview
def get(self, items):
"""Get the serialized history for the given items.
Args:
items: A list of TabHistoryItems.
Return:
A list of serialized items, as dicts.
"""
history = self.webview.page().history()
stream, _data, user_data = tabhistory.serialize(items)
qtutils.deserialize_stream(stream, history)
for i, data in enumerate(user_data):
history.itemAt(i).setUserData(data)
d = self.sess_man._save_tab(self.webview, active=True)
new_history = d['history']
assert len(new_history) == len(items)
return new_history
def get_single(self, item):
"""Convenience method to use get() with a single item."""
ret = self.get([item])
assert len(ret) == 1
return ret[0]
class TestSaveTab:
@pytest.fixture
def hist_tester(self, webview):
"""Helper to test saving of history."""
return HistTester(webview)
@pytest.mark.parametrize('is_active', [True, False])
def test_active(self, sess_man, webview, is_active):
data = sess_man._save_tab(webview, is_active)
if is_active:
assert data['active']
else:
assert 'active' not in data
def test_no_history(self, sess_man, webview):
data = sess_man._save_tab(webview, active=False)
assert not data['history']
def test_single_item(self, hist_tester):
item = Item(url=QUrl('http://www.qutebrowser.org/'),
title='Test title',
active=True)
expected = {
'url': 'http://www.qutebrowser.org/',
'title': 'Test title',
'active': True,
'scroll-pos': {'x': 0, 'y': 0},
'zoom': 1.0
}
hist = hist_tester.get_single(item)
assert hist == expected
def test_original_url(self, hist_tester):
"""Test with an original-url which differs from the URL."""
item = Item(url=QUrl('http://www.example.com/'),
original_url=QUrl('http://www.example.org/'),
title='Test title',
active=True)
hist = hist_tester.get_single(item)
assert hist['url'] == 'http://www.example.com/'
assert hist['original-url'] == 'http://www.example.org/'
def test_multiple_items(self, hist_tester):
items = [
Item(url=QUrl('http://www.qutebrowser.org/'), title='test 1'),
Item(url=QUrl('http://www.example.com/'), title='test 2',
active=True),
Item(url=QUrl('http://www.example.org/'), title='test 3'),
]
expected = [
{
'url': 'http://www.qutebrowser.org/',
'title': 'test 1',
},
{
'url': 'http://www.example.com/',
'title': 'test 2',
'active': True,
'scroll-pos': {'x': 0, 'y': 0},
'zoom': 1.0
},
{
'url': 'http://www.example.org/',
'title': 'test 3',
},
]
hist = hist_tester.get(items)
assert hist == expected
@pytest.mark.parametrize('factor', [-1.0, 0.0, 1.5])
def test_zoom(self, hist_tester, factor):
"""Test zoom."""
items = [
Item(url=QUrl('http://www.example.com/'), title='Test title',
active=True),
Item(url=QUrl('http://www.example.com/'), title='Test title',
user_data={'zoom': factor}),
]
hist_tester.webview.setZoomFactor(factor)
hist = hist_tester.get(items)
assert hist[0]['zoom'] == factor
assert hist[1]['zoom'] == factor
@pytest.mark.parametrize('pos_x, pos_y', [
(0.0, 0.0), (1.0, 1.0), (0.0, 1.0), (1.0, 0.0),
])
def test_scroll_current(self, hist_tester, pos_x, pos_y):
"""Test scroll position on the current URL."""
items = [
Item(url=QUrl('http://www.example.com/'), title='Test title',
active=True),
Item(url=QUrl('http://www.example.com/'), title='Test title',
user_data={'scroll-pos': QPoint(pos_x, pos_y)}),
]
frame = hist_tester.webview.page().mainFrame()
text = '{}\n'.format('x' * 100) * 100
frame.setHtml('<html><body>{}</body></html>'.format(text))
frame.setScrollPosition(QPoint(pos_x, pos_y))
hist = hist_tester.get(items)
assert hist[0]['scroll-pos'] == {'x': pos_x, 'y': pos_y}
assert hist[1]['scroll-pos'] == {'x': pos_x, 'y': pos_y}
class FakeMainWindow:
"""Helper class for the fake_main_window fixture.
A fake MainWindow which provides a saveGeometry method.
"""
def __init__(self, geometry, win_id):
self._geometry = QByteArray(geometry)
self.win_id = win_id
def saveGeometry(self):
return self._geometry
class FakeTabbedBrowser:
"""A fake tabbed-browser which contains some widgets."""
def __init__(self, widgets):
self._widgets = widgets
def widgets(self):
return self._widgets
def currentIndex(self):
return 1
@pytest.yield_fixture
def fake_windows(win_registry, stubs, monkeypatch, qtbot):
"""Fixture which provides two fake main windows and tabbedbrowsers."""
win_registry.add_window(1)
win0 = FakeMainWindow(b'fake-geometry-0', win_id=0)
win1 = FakeMainWindow(b'fake-geometry-1', win_id=1)
objreg.register('main-window', win0, scope='window', window=0)
objreg.register('main-window', win1, scope='window', window=1)
webview0 = QWebView()
qtbot.add_widget(webview0)
webview1 = QWebView()
qtbot.add_widget(webview1)
webview2 = QWebView()
qtbot.add_widget(webview2)
webview3 = QWebView()
qtbot.add_widget(webview3)
browser0 = FakeTabbedBrowser([webview0, webview1])
browser1 = FakeTabbedBrowser([webview2, webview3])
objreg.register('tabbed-browser', browser0, scope='window', window=0)
objreg.register('tabbed-browser', browser1, scope='window', window=1)
qapp = stubs.FakeQApplication(active_window=win0)
monkeypatch.setattr('qutebrowser.misc.sessions.QApplication', qapp)
yield browser0, browser1
objreg.delete('main-window', scope='window', window=0)
objreg.delete('main-window', scope='window', window=1)
objreg.delete('tabbed-browser', scope='window', window=0)
objreg.delete('tabbed-browser', scope='window', window=1)
class TestSaveAll:
def test_no_history(self, sess_man):
assert not objreg.window_registry
data = sess_man._save_all()
assert not data['windows']
def test_normal(self, fake_windows, sess_man):
"""Test with some windows and tabs set up."""
data = sess_man._save_all()
win1 = {
'active': True,
'geometry': b'fake-geometry-0',
'tabs': [
{'history': []},
{'active': True, 'history': []},
],
}
win2 = {
'geometry': b'fake-geometry-1',
'tabs': [
{'history': []},
{'active': True, 'history': []},
],
}
expected = {'windows': [win1, win2]}
assert data == expected
def test_no_active_window(self, sess_man, fake_windows, stubs,
monkeypatch):
qapp = stubs.FakeQApplication(active_window=None)
monkeypatch.setattr('qutebrowser.misc.sessions.QApplication', qapp)
sess_man._save_all()
@pytest.mark.parametrize('arg, config, current, expected', [
('foo', None, None, 'foo'),
(sessions.default, 'foo', None, 'foo'),
(sessions.default, None, 'foo', 'foo'),
(sessions.default, None, None, 'default'),
])
def test_get_session_name(config_stub, sess_man, arg, config, current,
expected):
config_stub.data = {'general': {'session-default-name': config}}
sess_man._current = current
assert sess_man._get_session_name(arg) == expected
class TestSave:
@pytest.yield_fixture
def state_config(self):
state = {'general': {}}
objreg.register('state-config', state)
yield state
objreg.delete('state-config')
@pytest.yield_fixture
def fake_history(self, win_registry, stubs, monkeypatch, webview):
"""Fixture which provides a window with a fake history."""
win = FakeMainWindow(b'fake-geometry-0', win_id=0)
objreg.register('main-window', win, scope='window', window=0)
browser = FakeTabbedBrowser([webview])
objreg.register('tabbed-browser', browser, scope='window', window=0)
qapp = stubs.FakeQApplication(active_window=win)
monkeypatch.setattr('qutebrowser.misc.sessions.QApplication', qapp)
def set_data(items):
history = browser.widgets()[0].page().history()
stream, _data, user_data = tabhistory.serialize(items)
qtutils.deserialize_stream(stream, history)
for i, data in enumerate(user_data):
history.itemAt(i).setUserData(data)
yield set_data
objreg.delete('main-window', scope='window', window=0)
objreg.delete('tabbed-browser', scope='window', window=0)
def test_no_config_storage(self, sess_man):
with pytest.raises(sessions.SessionError) as excinfo:
sess_man.save('foo')
assert str(excinfo.value) == "No data storage configured."
def test_simple_dump(self, sess_man, tmpdir):
session_path = tmpdir / 'foo.yml'
name = sess_man.save(str(session_path))
assert name == str(session_path)
data = session_path.read_text('utf-8')
assert data == 'windows: []\n'
def test_update_completion_signal(self, sess_man, tmpdir, qtbot):
session_path = tmpdir / 'foo.yml'
blocker = qtbot.waitSignal(sess_man.update_completion)
sess_man.save(str(session_path))
assert blocker.signal_triggered
def test_no_state_config(self, sess_man, tmpdir, state_config):
session_path = tmpdir / 'foo.yml'
sess_man.save(str(session_path))
assert 'session' not in state_config['general']
def test_last_window_session_none(self, sess_man, tmpdir):
session_path = tmpdir / 'foo.yml'
with pytest.raises(AssertionError):
sess_man.save(str(session_path), last_window=True)
assert not session_path.exists()
def test_last_window_session(self, sess_man, tmpdir):
sess_man.save_last_window_session()
session_path = tmpdir / 'foo.yml'
sess_man.save(str(session_path), last_window=True)
data = session_path.read_text('utf-8')
assert data == 'windows: []\n'
@pytest.mark.parametrize('exception', [
OSError('foo'), UnicodeEncodeError('ascii', '', 0, 2, 'foo'),
yaml.YAMLError('foo')])
def test_fake_exception(self, mocker, sess_man, tmpdir, exception):
mocker.patch('qutebrowser.misc.sessions.yaml.dump',
side_effect=exception)
with pytest.raises(sessions.SessionError) as excinfo:
sess_man.save(str(tmpdir / 'foo.yml'))
assert str(excinfo.value) == str(exception)
assert not tmpdir.listdir()
def test_directory(self, sess_man, tmpdir):
"""Test with a directory given as session file."""
with pytest.raises(sessions.SessionError) as excinfo:
sess_man.save(str(tmpdir))
assert str(excinfo.value) in ["Filename refers to a directory",
"Commit failed!"]
assert not tmpdir.listdir()
def test_load_next_time(self, tmpdir, state_config, sess_man):
session_path = tmpdir / 'foo.yml'
sess_man.save(str(session_path), load_next_time=True)
assert state_config['general']['session'] == str(session_path)
def test_utf_8_valid(self, tmpdir, sess_man, fake_history):
"""Make sure data containing valid UTF8 gets saved correctly."""
session_path = tmpdir / 'foo.yml'
fake_history([Item(QUrl('http://www.qutebrowser.org/'), 'foo☃bar',
active=True)])
sess_man.save(str(session_path))
data = session_path.read_text('utf-8')
assert 'title: foo☃bar' in data
def test_utf_8_invalid(self, tmpdir, sess_man, fake_history):
"""Make sure data containing invalid UTF8 raises SessionError."""
session_path = tmpdir / 'foo.yml'
fake_history([Item(QUrl('http://www.qutebrowser.org/'), '\ud800',
active=True)])
try:
sess_man.save(str(session_path))
except sessions.SessionError:
# This seems to happen on some systems only?!
pass
else:
data = session_path.read_text('utf-8')
assert r'title: "\uD800"' in data
def _set_data(self, browser, tab_id, items):
"""Helper function for test_long_output."""
history = browser.widgets()[tab_id].page().history()
stream, _data, user_data = tabhistory.serialize(items)
qtutils.deserialize_stream(stream, history)
for i, data in enumerate(user_data):
history.itemAt(i).setUserData(data)
@pytest.mark.skipif(
os.name == 'nt', reason="Test segfaults on Windows, see "
"https://github.com/The-Compiler/qutebrowser/issues/895")
def test_long_output(self, fake_windows, tmpdir, sess_man):
session_path = tmpdir / 'foo.yml'
items1 = [
Item(QUrl('http://www.qutebrowser.org/'), 'test title 1'),
Item(QUrl('http://www.example.org/'), 'test title 2',
original_url=QUrl('http://www.example.com/'), active=True),
]
items2 = [
Item(QUrl('http://www.example.com/?q=foo+bar'), 'test title 3'),
Item(QUrl('http://www.example.com/?q=test%20foo'), 'test title 4',
active=True),
]
items3 = []
items4 = [
Item(QUrl('http://www.github.com/The-Compiler/qutebrowser'),
'test title 5', active=True),
]
self._set_data(fake_windows[0], 0, items1)
self._set_data(fake_windows[0], 1, items2)
self._set_data(fake_windows[1], 0, items3)
self._set_data(fake_windows[1], 1, items4)
expected = """
windows:
- active: true
geometry: !!binary |
ZmFrZS1nZW9tZXRyeS0w
tabs:
- history:
- title: test title 1
url: http://www.qutebrowser.org/
- active: true
original-url: http://www.example.com/
scroll-pos:
x: 0
y: 0
title: test title 2
url: http://www.example.org/
zoom: 1.0
- active: true
history:
- title: test title 3
url: http://www.example.com/?q=foo+bar
- active: true
scroll-pos:
x: 0
y: 0
title: test title 4
url: http://www.example.com/?q=test%20foo
zoom: 1.0
- geometry: !!binary |
ZmFrZS1nZW9tZXRyeS0x
tabs:
- history: []
- active: true
history:
- active: true
scroll-pos:
x: 0
y: 0
title: test title 5
url: http://www.github.com/The-Compiler/qutebrowser
zoom: 1.0
"""
sess_man.save(str(session_path))
data = session_path.read_text('utf-8')
assert data == textwrap.dedent(expected.strip('\n'))
class FakeWebView:
"""A QWebView fake which provides a "page" with a load_history method.
Attributes:
loaded_history: The history which has been loaded by load_history, or
None.
raise_error: The exception to raise on load_history, or None.
"""
def __init__(self):
self.loaded_history = None
self.raise_error = None
def page(self):
return self
def load_history(self, data):
self.loaded_history = data
if self.raise_error is not None:
raise self.raise_error # pylint: disable=raising-bad-type
@pytest.fixture
def fake_webview():
return FakeWebView()
class TestLoadTab:
def test_no_history(self, sess_man, fake_webview):
sess_man._load_tab(fake_webview, {'history': []})
assert fake_webview.loaded_history == []
def test_load_fail(self, sess_man, fake_webview):
fake_webview.raise_error = ValueError
with pytest.raises(sessions.SessionError):
sess_man._load_tab(fake_webview, {'history': []})
@pytest.mark.parametrize('key, val, expected', [
('zoom', 1.23, 1.23),
('scroll-pos', {'x': 23, 'y': 42}, QPoint(23, 42)),
])
@pytest.mark.parametrize('in_main_data', [True, False])
def test_user_data(self, sess_man, fake_webview, key, val, expected,
in_main_data):
item = {'url': 'http://www.example.com/', 'title': 'foo'}
if in_main_data:
# This information got saved in the main data instead of saving it
# per item - make sure the old format can still be read
# https://github.com/The-Compiler/qutebrowser/issues/728
d = {'history': [item], key: val}
else:
item[key] = val
d = {'history': [item]}
sess_man._load_tab(fake_webview, d)
assert len(fake_webview.loaded_history) == 1
assert fake_webview.loaded_history[0].user_data[key] == expected
@pytest.mark.parametrize('original_url', ['http://example.org/', None])
def test_urls(self, sess_man, fake_webview, original_url):
url = 'http://www.example.com/'
item = {'url': url, 'title': 'foo'}
if original_url is None:
expected = QUrl(url)
else:
item['original-url'] = original_url
expected = QUrl(original_url)
d = {'history': [item]}
sess_man._load_tab(fake_webview, d)
assert len(fake_webview.loaded_history) == 1
loaded_item = fake_webview.loaded_history[0]
assert loaded_item.url == QUrl(url)
assert loaded_item.original_url == expected
class TestDelete:
def test_existing(self, sess_man, tmpdir):
sess = tmpdir / 'foo.yml'
sess.ensure()
sess_man.delete(str(sess))
assert not tmpdir.listdir()
def test_update_completion_signal(self, sess_man, qtbot, tmpdir):
sess = tmpdir / 'foo.yml'
sess.ensure()
blocker = qtbot.waitSignal(sess_man.update_completion)
sess_man.delete(str(sess))
assert blocker.signal_triggered
def test_not_existing(self, sess_man, qtbot, tmpdir):
sess = tmpdir / 'foo.yml'
with pytest.raises(sessions.SessionError):
sess_man.delete(str(sess))
class TestListSessions:
def test_no_base_path(self, sess_man):
assert not sess_man.list_sessions()
def test_no_sessions(self, tmpdir):
sess_man = sessions.SessionManager(str(tmpdir))
assert not sess_man.list_sessions()
def test_with_sessions(self, tmpdir):
(tmpdir / 'foo.yml').ensure()
(tmpdir / 'bar.yml').ensure()
sess_man = sessions.SessionManager(str(tmpdir))
assert sorted(sess_man.list_sessions()) == ['bar', 'foo']
def test_with_other_files(self, tmpdir):
(tmpdir / 'foo.yml').ensure()
(tmpdir / 'bar.html').ensure()
sess_man = sessions.SessionManager(str(tmpdir))
assert sess_man.list_sessions() == ['foo']
class TestSessionSave:
def test_normal_save(self, sess_man, tmpdir, fake_windows):
sess_file = tmpdir / 'foo.yml'
sess_man.session_save(0, str(sess_file), quiet=True)
assert sess_file.read_text('utf-8').startswith('windows:')
def test_internal_without_force(self, tmpdir):
sess_man = sessions.SessionManager(str(tmpdir))
with pytest.raises(cmdexc.CommandError) as excinfo:
sess_man.session_save(0, '_foo')
expected_text = ("_foo is an internal session, use --force to "
"save anyways.")
assert str(excinfo.value) == expected_text
assert not (tmpdir / '_foo.yml').exists()
def test_internal_with_force(self, tmpdir, fake_windows):
sess_man = sessions.SessionManager(str(tmpdir))
sess_man.session_save(0, '_foo', force=True, quiet=True)
assert (tmpdir / '_foo.yml').exists()
def test_current_unset(self, tmpdir):
sess_man = sessions.SessionManager(str(tmpdir))
with pytest.raises(cmdexc.CommandError) as excinfo:
sess_man.session_save(0, current=True)
assert str(excinfo.value) == "No session loaded currently!"
def test_current_set(self, tmpdir, fake_windows):
sess_man = sessions.SessionManager(str(tmpdir))
sess_man._current = 'foo'
sess_man.session_save(0, current=True, quiet=True)
assert (tmpdir / 'foo.yml').exists()
def test_saving_error(self, sess_man, tmpdir):
with pytest.raises(cmdexc.CommandError) as excinfo:
sess_man.session_save(0, str(tmpdir))
assert str(excinfo.value).startswith('Error while saving session: ')
def test_message(self, sess_man, tmpdir, message_mock, fake_windows):
message_mock.patch('qutebrowser.misc.sessions.message')
sess_path = str(tmpdir / 'foo.yml')
sess_man.session_save(0, sess_path)
expected_text = 'Saved session {}.'.format(sess_path)
assert message_mock.getmsg(immediate=True).text == expected_text
def test_message_quiet(self, sess_man, tmpdir, message_mock, fake_windows):
message_mock.patch('qutebrowser.misc.sessions.message')
sess_path = str(tmpdir / 'foo.yml')
sess_man.session_save(0, sess_path, quiet=True)
assert not message_mock.messages
class TestSessionDelete:
def test_internal_without_force(self, tmpdir):
sess_man = sessions.SessionManager(str(tmpdir))
sess_file = tmpdir / '_foo.yml'
sess_file.ensure()
with pytest.raises(cmdexc.CommandError) as excinfo:
sess_man.session_delete('_foo')
expected_text = ("_foo is an internal session, use --force to "
"delete anyways.")
assert str(excinfo.value) == expected_text
assert sess_file.exists()
def test_internal_with_force(self, tmpdir):
sess_man = sessions.SessionManager(str(tmpdir))
sess_file = tmpdir / '_foo.yml'
sess_file.ensure()
sess_man.session_delete('_foo', force=True)
assert not tmpdir.listdir()
def test_normal_delete(self, tmpdir):
sess_man = sessions.SessionManager(str(tmpdir))
sess_file = tmpdir / 'foo.yml'
sess_file.ensure()
sess_man.session_delete('foo')
assert not tmpdir.listdir()
def test_session_not_found(self, tmpdir):
sess_man = sessions.SessionManager(str(tmpdir))
with pytest.raises(cmdexc.CommandError) as excinfo:
sess_man.session_delete('foo')
assert str(excinfo.value) == "Session foo not found!"
@pytest.mark.posix
def test_deletion_error(self, caplog, tmpdir):
sess_man = sessions.SessionManager(str(tmpdir))
(tmpdir / 'foo.yml').ensure()
tmpdir.chmod(0o555) # unwritable
with pytest.raises(cmdexc.CommandError) as excinfo:
with caplog.atLevel(logging.ERROR):
sess_man.session_delete('foo')
assert str(excinfo.value).startswith('Error while deleting session: ')
records = caplog.records()
assert len(records) == 1
assert records[0].message == 'Error while deleting session!'