qutebrowser/tests/unit/browser/webkit/test_history.py
2016-11-15 22:25:51 +01:00

415 lines
13 KiB
Python

# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
# Copyright 2016 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 the global page history."""
import logging
import pytest
import hypothesis
from hypothesis import strategies
from PyQt5.QtCore import QUrl
from qutebrowser.browser import history
from qutebrowser.utils import objreg, urlutils
class FakeWebHistory:
"""A fake WebHistory object."""
def __init__(self, history_dict):
self.history_dict = history_dict
@pytest.fixture(autouse=True)
def prerequisites(config_stub, fake_save_manager):
"""Make sure everything is ready to initialize a WebHistory."""
config_stub.data = {'general': {'private-browsing': False}}
@pytest.fixture()
def hist(tmpdir):
return history.WebHistory(hist_dir=str(tmpdir), hist_name='history')
def test_async_read_twice(monkeypatch, qtbot, tmpdir, caplog):
(tmpdir / 'filled-history').write('\n'.join([
'12345 http://example.com/ title',
'67890 http://example.com/',
'12345 http://qutebrowser.org/ blah',
]))
hist = history.WebHistory(hist_dir=str(tmpdir), hist_name='filled-history')
next(hist.async_read())
with pytest.raises(StopIteration):
next(hist.async_read())
expected = "Ignoring async_read() because reading is started."
assert len(caplog.records) == 1
assert caplog.records[0].msg == expected
@pytest.mark.parametrize('redirect', [True, False])
def test_adding_item_during_async_read(qtbot, hist, redirect):
"""Check what happens when adding URL while reading the history."""
url = QUrl('http://www.example.com/')
with qtbot.assertNotEmitted(hist.add_completion_item), \
qtbot.assertNotEmitted(hist.item_added):
hist.add_url(url, redirect=redirect, atime=12345)
if redirect:
with qtbot.assertNotEmitted(hist.add_completion_item):
with qtbot.waitSignal(hist.async_read_done):
list(hist.async_read())
else:
with qtbot.waitSignals([hist.add_completion_item,
hist.async_read_done], order='strict'):
list(hist.async_read())
assert not hist._temp_history
expected = history.Entry(url=url, atime=12345, redirect=redirect, title="")
assert list(hist.history_dict.values()) == [expected]
def test_private_browsing(qtbot, tmpdir, fake_save_manager, config_stub):
"""Make sure no data is saved at all with private browsing."""
config_stub.data = {'general': {'private-browsing': True}}
private_hist = history.WebHistory(hist_dir=str(tmpdir),
hist_name='history')
# Before initial read
with qtbot.assertNotEmitted(private_hist.add_completion_item), \
qtbot.assertNotEmitted(private_hist.item_added):
private_hist.add_url(QUrl('http://www.example.com/'))
assert not private_hist._temp_history
# read
with qtbot.assertNotEmitted(private_hist.add_completion_item), \
qtbot.assertNotEmitted(private_hist.item_added):
with qtbot.waitSignals([private_hist.async_read_done], order='strict'):
list(private_hist.async_read())
# after read
with qtbot.assertNotEmitted(private_hist.add_completion_item), \
qtbot.assertNotEmitted(private_hist.item_added):
private_hist.add_url(QUrl('http://www.example.com/'))
assert not private_hist._temp_history
assert not private_hist._new_history
assert not private_hist.history_dict
def test_iter(hist):
list(hist.async_read())
url = QUrl('http://www.example.com/')
hist.add_url(url, atime=12345)
entry = history.Entry(url=url, atime=12345, redirect=False, title="")
assert list(hist) == [entry]
def test_len(hist):
assert len(hist) == 0
list(hist.async_read())
url = QUrl('http://www.example.com/')
hist.add_url(url)
assert len(hist) == 1
@pytest.mark.parametrize('line', [
'12345 http://example.com/ title', # with title
'67890 http://example.com/', # no title
'12345 http://qutebrowser.org/ ', # trailing space
' ',
'',
])
def test_read(hist, tmpdir, line):
(tmpdir / 'filled-history').write(line + '\n')
hist = history.WebHistory(hist_dir=str(tmpdir), hist_name='filled-history')
list(hist.async_read())
def test_updated_entries(hist, tmpdir):
(tmpdir / 'filled-history').write('12345 http://example.com/\n'
'67890 http://example.com/\n')
hist = history.WebHistory(hist_dir=str(tmpdir), hist_name='filled-history')
list(hist.async_read())
assert hist.history_dict['http://example.com/'].atime == 67890
hist.add_url(QUrl('http://example.com/'), atime=99999)
assert hist.history_dict['http://example.com/'].atime == 99999
def test_invalid_read(hist, tmpdir, caplog):
(tmpdir / 'filled-history').write('foobar\n12345 http://example.com/')
hist = history.WebHistory(hist_dir=str(tmpdir), hist_name='filled-history')
with caplog.at_level(logging.WARNING):
list(hist.async_read())
entries = list(hist.history_dict.values())
assert len(entries) == 1
assert len(caplog.records) == 1
msg = "Invalid history entry 'foobar': 2 or 3 fields expected!"
assert caplog.records[0].msg == msg
def test_get_recent(hist, tmpdir):
(tmpdir / 'filled-history').write('12345 http://example.com/')
hist = history.WebHistory(hist_dir=str(tmpdir), hist_name='filled-history')
list(hist.async_read())
hist.add_url(QUrl('http://www.qutebrowser.org/'), atime=67890)
lines = hist.get_recent()
expected = ['12345 http://example.com/',
'67890 http://www.qutebrowser.org/']
assert lines == expected
def test_save(hist, tmpdir):
hist_file = tmpdir / 'filled-history'
hist_file.write('12345 http://example.com/\n')
hist = history.WebHistory(hist_dir=str(tmpdir), hist_name='filled-history')
list(hist.async_read())
hist.add_url(QUrl('http://www.qutebrowser.org/'), atime=67890)
hist.save()
lines = hist_file.read().splitlines()
expected = ['12345 http://example.com/',
'67890 http://www.qutebrowser.org/']
assert lines == expected
hist.add_url(QUrl('http://www.the-compiler.org/'), atime=99999)
hist.save()
expected.append('99999 http://www.the-compiler.org/')
lines = hist_file.read().splitlines()
assert lines == expected
def test_clear(qtbot, hist, tmpdir):
hist_file = tmpdir / 'filled-history'
hist_file.write('12345 http://example.com/\n')
hist = history.WebHistory(hist_dir=str(tmpdir), hist_name='filled-history')
list(hist.async_read())
hist.add_url(QUrl('http://www.qutebrowser.org/'))
with qtbot.waitSignal(hist.cleared):
hist.clear()
assert not hist_file.read()
assert not hist.history_dict
assert not hist._new_history
hist.add_url(QUrl('http://www.the-compiler.org/'), atime=67890)
hist.save()
lines = hist_file.read().splitlines()
assert lines == ['67890 http://www.the-compiler.org/']
def test_add_item(qtbot, hist):
list(hist.async_read())
url = 'http://www.example.com/'
with qtbot.waitSignals([hist.add_completion_item, hist.item_added],
order='strict'):
hist.add_url(QUrl(url), atime=12345, title="the title")
entry = history.Entry(url=QUrl(url), redirect=False, atime=12345,
title="the title")
assert hist.history_dict[url] == entry
def test_add_item_redirect(qtbot, hist):
list(hist.async_read())
url = 'http://www.example.com/'
with qtbot.assertNotEmitted(hist.add_completion_item):
with qtbot.waitSignal(hist.item_added):
hist.add_url(QUrl(url), redirect=True, atime=12345)
entry = history.Entry(url=QUrl(url), redirect=True, atime=12345, title="")
assert hist.history_dict[url] == entry
def test_add_item_redirect_update(qtbot, tmpdir):
"""A redirect update added should override a non-redirect one."""
url = 'http://www.example.com/'
hist_file = tmpdir / 'filled-history'
hist_file.write('12345 {}\n'.format(url))
hist = history.WebHistory(hist_dir=str(tmpdir), hist_name='filled-history')
list(hist.async_read())
with qtbot.assertNotEmitted(hist.add_completion_item):
with qtbot.waitSignal(hist.item_added):
hist.add_url(QUrl(url), redirect=True, atime=67890)
entry = history.Entry(url=QUrl(url), redirect=True, atime=67890, title="")
assert hist.history_dict[url] == entry
@pytest.mark.parametrize('line, expected', [
(
# old format without title
'12345 http://example.com/',
history.Entry(atime=12345, url=QUrl('http://example.com/'), title='',)
),
(
# trailing space without title
'12345 http://example.com/ ',
history.Entry(atime=12345, url=QUrl('http://example.com/'), title='',)
),
(
# new format with title
'12345 http://example.com/ this is a title',
history.Entry(atime=12345, url=QUrl('http://example.com/'),
title='this is a title')
),
(
# weird NUL bytes
'\x0012345 http://example.com/',
history.Entry(atime=12345, url=QUrl('http://example.com/'), title=''),
),
(
# redirect flag
'12345-r http://example.com/ this is a title',
history.Entry(atime=12345, url=QUrl('http://example.com/'),
title='this is a title', redirect=True)
),
])
def test_entry_parse_valid(line, expected):
entry = history.Entry.from_str(line)
assert entry == expected
@pytest.mark.parametrize('line', [
'12345', # one field
'12345 ::', # invalid URL
'xyz http://www.example.com/', # invalid timestamp
'12345-x http://www.example.com/', # invalid flags
'12345-r-r http://www.example.com/', # double flags
])
def test_entry_parse_invalid(line):
with pytest.raises(ValueError):
history.Entry.from_str(line)
@hypothesis.given(strategies.text())
def test_entry_parse_hypothesis(text):
"""Make sure parsing works or gives us ValueError."""
try:
history.Entry.from_str(text)
except ValueError:
pass
@pytest.mark.parametrize('entry, expected', [
# simple
(
history.Entry(12345, QUrl('http://example.com/'), "the title"),
"12345 http://example.com/ the title",
),
# timestamp as float
(
history.Entry(12345.678, QUrl('http://example.com/'), "the title"),
"12345 http://example.com/ the title",
),
# no title
(
history.Entry(12345.678, QUrl('http://example.com/'), ""),
"12345 http://example.com/",
),
# redirect flag
(
history.Entry(12345.678, QUrl('http://example.com/'), "",
redirect=True),
"12345-r http://example.com/",
),
])
def test_entry_str(entry, expected):
assert str(entry) == expected
@pytest.fixture
def hist_interface():
# pylint: disable=invalid-name
QtWebKit = pytest.importorskip('PyQt5.QtWebKit')
from qutebrowser.browser.webkit import webkithistory
QWebHistoryInterface = QtWebKit.QWebHistoryInterface
# pylint: enable=invalid-name
entry = history.Entry(atime=0, url=QUrl('http://www.example.com/'),
title='example')
history_dict = {'http://www.example.com/': entry}
fake_hist = FakeWebHistory(history_dict)
interface = webkithistory.WebHistoryInterface(fake_hist)
QWebHistoryInterface.setDefaultInterface(interface)
yield
QWebHistoryInterface.setDefaultInterface(None)
def test_history_interface(qtbot, webview, hist_interface):
html = b"<a href='about:blank'>foo</a>"
url = urlutils.data_url('text/html', html)
with qtbot.waitSignal(webview.loadFinished):
webview.load(url)
@pytest.mark.parametrize('backend', ['webengine', 'webkit'])
def test_init(backend, qapp, tmpdir, monkeypatch, fake_save_manager,
fake_args):
if backend == 'webkit':
pytest.importorskip('PyQt5.QtWebKitWidgets')
fake_args.backend = backend
monkeypatch.setattr(history.standarddir, 'data', lambda: str(tmpdir))
history.init(qapp)
hist = objreg.get('web-history')
assert hist.parent() is qapp
try:
from PyQt5.QtWebKit import QWebHistoryInterface
except ImportError:
QWebHistoryInterface = None
if backend == 'webkit':
default_interface = QWebHistoryInterface.defaultInterface()
assert default_interface._history is hist
else:
assert backend == 'webengine'
if QWebHistoryInterface is None:
default_interface = None
else:
default_interface = QWebHistoryInterface.defaultInterface()
# For this to work, nothing can ever have called setDefaultInterface
# before (so we need to test webengine before webkit)
assert default_interface is None
assert fake_save_manager.add_saveable.called
objreg.delete('web-history')