qutebrowser/tests/unit/browser/test_history.py

487 lines
19 KiB
Python
Raw Normal View History

2016-06-09 13:15:52 +02:00
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
2019-02-23 06:34:17 +01:00
# Copyright 2016-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
2016-06-09 13:15:52 +02:00
#
# 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
2016-06-09 21:13:25 +02:00
2016-06-09 13:15:52 +02:00
import pytest
from PyQt5.QtCore import QUrl
from qutebrowser.browser import history
from qutebrowser.utils import objreg, urlutils, usertypes
2018-11-29 14:09:06 +01:00
from qutebrowser.api import cmdutils
2017-10-09 16:17:55 +02:00
from qutebrowser.misc import sql
2016-06-09 13:15:52 +02:00
@pytest.fixture(autouse=True)
2017-09-17 11:49:42 +02:00
def prerequisites(config_stub, fake_save_manager, init_sql, fake_args):
"""Make sure everything is ready to initialize a WebHistory."""
2017-09-17 11:49:42 +02:00
fake_args.debug_flags = []
config_stub.data = {'general': {'private-browsing': False}}
2016-06-09 21:13:25 +02:00
2018-08-31 22:24:57 +02:00
class TestSpecialMethods:
2018-09-06 20:04:11 +02:00
def test_iter(self, web_history):
2018-08-31 22:24:57 +02:00
urlstr = 'http://www.example.com/'
url = QUrl(urlstr)
2018-09-06 20:04:11 +02:00
web_history.add_url(url, atime=12345)
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
assert list(web_history) == [(urlstr, '', 12345, False)]
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
def test_len(self, web_history):
assert len(web_history) == 0
2018-08-31 22:24:57 +02:00
url = QUrl('http://www.example.com/')
2018-09-06 20:04:11 +02:00
web_history.add_url(url)
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
assert len(web_history) == 1
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
def test_contains(self, web_history):
web_history.add_url(QUrl('http://www.example.com/'),
title='Title', atime=12345)
assert 'http://www.example.com/' in web_history
assert 'www.example.com' not in web_history
assert 'Title' not in web_history
assert 12345 not in web_history
2018-08-31 22:24:57 +02:00
class TestGetting:
2018-09-06 20:04:11 +02:00
def test_get_recent(self, web_history):
web_history.add_url(QUrl('http://www.qutebrowser.org/'), atime=67890)
web_history.add_url(QUrl('http://example.com/'), atime=12345)
assert list(web_history.get_recent()) == [
2018-08-31 22:24:57 +02:00
('http://www.qutebrowser.org/', '', 67890, False),
('http://example.com/', '', 12345, False),
]
2018-09-06 20:04:11 +02:00
def test_entries_between(self, web_history):
web_history.add_url(QUrl('http://www.example.com/1'), atime=12345)
web_history.add_url(QUrl('http://www.example.com/2'), atime=12346)
web_history.add_url(QUrl('http://www.example.com/3'), atime=12347)
web_history.add_url(QUrl('http://www.example.com/4'), atime=12348)
web_history.add_url(QUrl('http://www.example.com/5'), atime=12348)
web_history.add_url(QUrl('http://www.example.com/6'), atime=12349)
web_history.add_url(QUrl('http://www.example.com/7'), atime=12350)
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
times = [x.atime for x in web_history.entries_between(12346, 12349)]
2018-08-31 22:24:57 +02:00
assert times == [12349, 12348, 12348, 12347]
2018-09-06 20:04:11 +02:00
def test_entries_before(self, web_history):
web_history.add_url(QUrl('http://www.example.com/1'), atime=12346)
web_history.add_url(QUrl('http://www.example.com/2'), atime=12346)
web_history.add_url(QUrl('http://www.example.com/3'), atime=12347)
web_history.add_url(QUrl('http://www.example.com/4'), atime=12348)
web_history.add_url(QUrl('http://www.example.com/5'), atime=12348)
web_history.add_url(QUrl('http://www.example.com/6'), atime=12348)
web_history.add_url(QUrl('http://www.example.com/7'), atime=12349)
web_history.add_url(QUrl('http://www.example.com/8'), atime=12349)
2018-08-31 22:24:57 +02:00
times = [x.atime for x in
2018-09-06 20:04:11 +02:00
web_history.entries_before(12348, limit=3, offset=2)]
2018-08-31 22:24:57 +02:00
assert times == [12348, 12347, 12346]
class TestDelete:
2018-09-06 20:04:11 +02:00
def test_clear(self, qtbot, tmpdir, web_history, mocker):
web_history.add_url(QUrl('http://example.com/'))
web_history.add_url(QUrl('http://www.qutebrowser.org/'))
2018-08-31 22:24:57 +02:00
m = mocker.patch('qutebrowser.browser.history.message.confirm_async',
new=mocker.Mock, spec=[])
2018-09-06 20:04:11 +02:00
web_history.clear()
2018-08-31 22:24:57 +02:00
assert m.called
2018-09-06 20:04:11 +02:00
def test_clear_force(self, qtbot, tmpdir, web_history):
web_history.add_url(QUrl('http://example.com/'))
web_history.add_url(QUrl('http://www.qutebrowser.org/'))
web_history.clear(force=True)
assert not len(web_history)
assert not len(web_history.completion)
2018-08-31 22:24:57 +02:00
@pytest.mark.parametrize('raw, escaped', [
('http://example.com/1', 'http://example.com/1'),
('http://example.com/1 2', 'http://example.com/1%202'),
])
2018-09-06 20:04:11 +02:00
def test_delete_url(self, web_history, raw, escaped):
web_history.add_url(QUrl('http://example.com/'), atime=0)
web_history.add_url(QUrl(escaped), atime=0)
web_history.add_url(QUrl('http://example.com/2'), atime=0)
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
before = set(web_history)
completion_before = set(web_history.completion)
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
web_history.delete_url(QUrl(raw))
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
diff = before.difference(set(web_history))
2018-08-31 22:24:57 +02:00
assert diff == {(escaped, '', 0, False)}
2018-09-06 20:04:11 +02:00
completion_diff = completion_before.difference(
set(web_history.completion))
2018-08-31 22:24:57 +02:00
assert completion_diff == {(raw, '', 0)}
class TestAdd:
@pytest.fixture()
def mock_time(self, mocker):
m = mocker.patch('qutebrowser.browser.history.time')
m.time.return_value = 12345
return 12345
@pytest.mark.parametrize(
'url, atime, title, redirect, history_url, completion_url', [
('http://www.example.com', 12346, 'the title', False,
'http://www.example.com', 'http://www.example.com'),
('http://www.example.com', 12346, 'the title', True,
'http://www.example.com', None),
('http://www.example.com/sp ce', 12346, 'the title', False,
'http://www.example.com/sp%20ce', 'http://www.example.com/sp ce'),
('https://user:pass@example.com', 12346, 'the title', False,
'https://user@example.com', 'https://user@example.com'),
]
)
2018-09-06 20:04:11 +02:00
def test_add_url(self, qtbot, web_history,
2018-08-31 22:24:57 +02:00
url, atime, title, redirect, history_url, completion_url):
2018-09-06 20:04:11 +02:00
web_history.add_url(QUrl(url), atime=atime, title=title,
redirect=redirect)
assert list(web_history) == [(history_url, title, atime, redirect)]
2018-08-31 22:24:57 +02:00
if completion_url is None:
2018-09-06 20:04:11 +02:00
assert not len(web_history.completion)
2018-08-31 22:24:57 +02:00
else:
2018-09-06 20:04:11 +02:00
expected = [(completion_url, title, atime)]
assert list(web_history.completion) == expected
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
def test_no_sql_web_history(self, web_history, fake_args):
2018-08-31 22:28:18 +02:00
fake_args.debug_flags = 'no-sql-history'
2018-09-06 20:04:11 +02:00
web_history.add_url(QUrl('https://www.example.com/'), atime=12346,
title='Hello World', redirect=False)
assert not list(web_history)
2018-08-31 22:28:18 +02:00
2018-09-06 20:04:11 +02:00
def test_invalid(self, qtbot, web_history, caplog):
2018-08-31 22:24:57 +02:00
with caplog.at_level(logging.WARNING):
2018-09-06 20:04:11 +02:00
web_history.add_url(QUrl())
assert not list(web_history)
assert not list(web_history.completion)
2018-08-31 22:24:57 +02:00
@pytest.mark.parametrize('environmental', [True, False])
@pytest.mark.parametrize('completion', [True, False])
2018-09-06 20:04:11 +02:00
def test_error(self, monkeypatch, web_history, message_mock, caplog,
2018-08-31 22:24:57 +02:00
environmental, completion):
def raise_error(url, replace=False):
if environmental:
raise sql.SqlEnvironmentError("Error message")
else:
raise sql.SqlBugError("Error message")
2018-08-31 22:24:57 +02:00
if completion:
2018-09-06 20:04:11 +02:00
monkeypatch.setattr(web_history.completion, 'insert', raise_error)
2018-08-31 22:24:57 +02:00
else:
2018-09-06 20:04:11 +02:00
monkeypatch.setattr(web_history, 'insert', raise_error)
2018-08-31 22:24:57 +02:00
if environmental:
with caplog.at_level(logging.ERROR):
2018-09-06 20:04:11 +02:00
web_history.add_url(QUrl('https://www.example.org/'))
2018-08-31 22:24:57 +02:00
msg = message_mock.getmsg(usertypes.MessageLevel.error)
assert msg.text == "Failed to write history: Error message"
else:
with pytest.raises(sql.SqlBugError):
2018-09-06 20:04:11 +02:00
web_history.add_url(QUrl('https://www.example.org/'))
2018-08-31 22:24:57 +02:00
@pytest.mark.parametrize('level, url, req_url, expected', [
(logging.DEBUG, 'a.com', 'a.com', [('a.com', 'title', 12345, False)]),
(logging.DEBUG, 'a.com', 'b.com', [('a.com', 'title', 12345, False),
('b.com', 'title', 12345, True)]),
(logging.WARNING, 'a.com', '', [('a.com', 'title', 12345, False)]),
(logging.WARNING, '', '', []),
(logging.WARNING, 'data:foo', '', []),
(logging.WARNING, 'a.com', 'data:foo', []),
])
2018-09-06 20:04:11 +02:00
def test_from_tab(self, web_history, caplog, mock_time,
2018-08-31 22:24:57 +02:00
level, url, req_url, expected):
with caplog.at_level(level):
2018-09-06 20:04:11 +02:00
web_history.add_from_tab(QUrl(url), QUrl(req_url), 'title')
assert set(web_history) == set(expected)
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
def test_exclude(self, web_history, config_stub):
"""Excluded URLs should be in the history but not completion."""
config_stub.val.completion.web_history.exclude = ['*.example.org']
url = QUrl('http://www.example.org/')
2018-09-06 20:04:11 +02:00
web_history.add_from_tab(url, url, 'title')
assert list(web_history)
assert not list(web_history.completion)
2018-08-31 22:24:57 +02:00
class TestHistoryInterface:
2018-08-31 22:24:57 +02:00
@pytest.fixture
2018-09-06 20:04:11 +02:00
def hist_interface(self, web_history):
2018-08-31 22:24:57 +02:00
# pylint: disable=invalid-name
QtWebKit = pytest.importorskip('PyQt5.QtWebKit')
from qutebrowser.browser.webkit import webkithistory
QWebHistoryInterface = QtWebKit.QWebHistoryInterface
# pylint: enable=invalid-name
2018-09-06 20:04:11 +02:00
web_history.add_url(url=QUrl('http://www.example.com/'),
title='example')
interface = webkithistory.WebHistoryInterface(web_history)
2018-08-31 22:24:57 +02:00
QWebHistoryInterface.setDefaultInterface(interface)
yield
QWebHistoryInterface.setDefaultInterface(None)
2018-08-31 22:24:57 +02:00
def test_history_interface(self, 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)
2018-08-31 22:24:57 +02:00
class TestInit:
2018-08-31 22:24:57 +02:00
@pytest.fixture
def cleanup_init(self):
# prevent test_init from leaking state
yield
2018-09-06 20:04:11 +02:00
web_history = objreg.get('web-history', None)
if web_history is not None:
web_history.setParent(None)
2018-08-31 22:24:57 +02:00
objreg.delete('web-history')
try:
from PyQt5.QtWebKit import QWebHistoryInterface
QWebHistoryInterface.setDefaultInterface(None)
except ImportError:
pass
@pytest.mark.parametrize('backend', [usertypes.Backend.QtWebEngine,
usertypes.Backend.QtWebKit])
def test_init(self, backend, qapp, tmpdir, monkeypatch, cleanup_init):
if backend == usertypes.Backend.QtWebKit:
pytest.importorskip('PyQt5.QtWebKitWidgets')
else:
assert backend == usertypes.Backend.QtWebEngine
2018-08-31 22:24:57 +02:00
monkeypatch.setattr(history.objects, 'backend', backend)
history.init(qapp)
hist = objreg.get('web-history')
assert hist.parent() is qapp
2018-08-31 22:24:57 +02:00
try:
from PyQt5.QtWebKit import QWebHistoryInterface
except ImportError:
QWebHistoryInterface = None
2018-08-31 22:24:57 +02:00
if backend == usertypes.Backend.QtWebKit:
default_interface = QWebHistoryInterface.defaultInterface()
assert default_interface._history is hist
else:
assert backend == usertypes.Backend.QtWebEngine
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
2018-08-31 22:24:57 +02:00
class TestDump:
2018-09-06 20:04:11 +02:00
def test_debug_dump_history(self, web_history, tmpdir):
web_history.add_url(QUrl('http://example.com/1'),
title="Title1", atime=12345)
web_history.add_url(QUrl('http://example.com/2'),
title="Title2", atime=12346)
web_history.add_url(QUrl('http://example.com/3'),
title="Title3", atime=12347)
web_history.add_url(QUrl('http://example.com/4'),
title="Title4", atime=12348, redirect=True)
2018-08-31 22:24:57 +02:00
histfile = tmpdir / 'history'
2018-09-06 20:04:11 +02:00
web_history.debug_dump_history(str(histfile))
2018-08-31 22:24:57 +02:00
expected = ['12345 http://example.com/1 Title1',
'12346 http://example.com/2 Title2',
'12347 http://example.com/3 Title3',
'12348-r http://example.com/4 Title4']
assert histfile.read() == '\n'.join(expected)
2018-09-06 20:04:11 +02:00
def test_nonexistent(self, web_history, tmpdir):
2018-08-31 22:24:57 +02:00
histfile = tmpdir / 'nonexistent' / 'history'
2018-11-29 14:09:06 +01:00
with pytest.raises(cmdutils.CommandError):
2018-09-06 20:04:11 +02:00
web_history.debug_dump_history(str(histfile))
2018-08-31 22:24:57 +02:00
class TestRebuild:
2018-09-06 20:04:11 +02:00
def test_delete(self, web_history, stubs):
web_history.insert({'url': 'example.com/1', 'title': 'example1',
'redirect': False, 'atime': 1})
web_history.insert({'url': 'example.com/1', 'title': 'example1',
'redirect': False, 'atime': 2})
web_history.insert({'url': 'example.com/2%203', 'title': 'example2',
'redirect': False, 'atime': 3})
web_history.insert({'url': 'example.com/3', 'title': 'example3',
'redirect': True, 'atime': 4})
web_history.insert({'url': 'example.com/2 3', 'title': 'example2',
'redirect': False, 'atime': 5})
web_history.completion.delete_all()
hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress())
2018-08-31 22:24:57 +02:00
assert list(hist2.completion) == [
('example.com/1', 'example1', 2),
('example.com/2 3', 'example2', 5),
]
2018-09-06 20:04:11 +02:00
def test_no_rebuild(self, web_history, stubs):
2018-08-31 22:24:57 +02:00
"""Ensure that completion is not regenerated unless empty."""
2018-09-06 20:04:11 +02:00
web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1)
web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2)
web_history.completion.delete('url', 'example.com/2')
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress())
2018-08-31 22:24:57 +02:00
assert list(hist2.completion) == [('example.com/1', '', 1)]
2018-09-06 20:04:11 +02:00
def test_user_version(self, web_history, stubs, monkeypatch):
2018-08-31 22:24:57 +02:00
"""Ensure that completion is regenerated if user_version changes."""
2018-09-06 20:04:11 +02:00
web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1)
web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2)
web_history.completion.delete('url', 'example.com/2')
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress())
2018-08-31 22:24:57 +02:00
assert list(hist2.completion) == [('example.com/1', '', 1)]
monkeypatch.setattr(history, '_USER_VERSION',
history._USER_VERSION + 1)
2018-09-06 20:04:11 +02:00
hist3 = history.WebHistory(progress=stubs.FakeHistoryProgress())
2018-08-31 22:24:57 +02:00
assert list(hist3.completion) == [
('example.com/1', '', 1),
('example.com/2', '', 2),
]
2018-09-06 20:04:11 +02:00
def test_force_rebuild(self, web_history, stubs):
2018-08-31 22:24:57 +02:00
"""Ensure that completion is regenerated if we force a rebuild."""
2018-09-06 20:04:11 +02:00
web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1)
web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2)
web_history.completion.delete('url', 'example.com/2')
2018-08-31 22:24:57 +02:00
2018-09-06 20:04:11 +02:00
hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress())
2018-08-31 22:24:57 +02:00
assert list(hist2.completion) == [('example.com/1', '', 1)]
hist2.metainfo['force_rebuild'] = True
2018-09-06 20:04:11 +02:00
hist3 = history.WebHistory(progress=stubs.FakeHistoryProgress())
2018-08-31 22:24:57 +02:00
assert list(hist3.completion) == [
('example.com/1', '', 1),
('example.com/2', '', 2),
]
assert not hist3.metainfo['force_rebuild']
2018-09-06 20:04:11 +02:00
def test_exclude(self, config_stub, web_history, stubs):
"""Ensure that patterns in completion.web_history.exclude are ignored.
This setting should only be used for the completion.
"""
config_stub.val.completion.web_history.exclude = ['*.example.org']
2018-09-06 20:04:11 +02:00
assert web_history.metainfo['force_rebuild']
2018-09-06 20:04:11 +02:00
web_history.add_url(QUrl('http://example.com'),
redirect=False, atime=1)
web_history.add_url(QUrl('http://example.org'),
redirect=False, atime=2)
2018-09-06 20:04:11 +02:00
hist2 = history.WebHistory(progress=stubs.FakeHistoryProgress())
assert list(hist2.completion) == [('http://example.com', '', 1)]
2018-09-06 20:04:11 +02:00
def test_unrelated_config_change(self, config_stub, web_history):
config_stub.val.history_gap_interval = 1234
2018-09-06 20:04:11 +02:00
assert not web_history.metainfo['force_rebuild']
2018-09-06 17:13:46 +02:00
@pytest.mark.parametrize('patch_threshold', [True, False])
2018-09-06 20:04:11 +02:00
def test_progress(self, web_history, config_stub, monkeypatch, stubs,
patch_threshold):
web_history.add_url(QUrl('example.com/1'), redirect=False, atime=1)
web_history.add_url(QUrl('example.com/2'), redirect=False, atime=2)
web_history.metainfo['force_rebuild'] = True
2018-09-06 17:13:46 +02:00
if patch_threshold:
monkeypatch.setattr(history.WebHistory, '_PROGRESS_THRESHOLD', 1)
2018-09-06 20:04:11 +02:00
progress = stubs.FakeHistoryProgress()
2018-09-06 17:13:46 +02:00
history.WebHistory(progress=progress)
assert progress._value == 2
assert progress._finished
assert progress._started == patch_threshold
class TestCompletionMetaInfo:
@pytest.fixture
def metainfo(self):
return history.CompletionMetaInfo()
def test_contains_keyerror(self, metainfo):
with pytest.raises(KeyError):
'does_not_exist' in metainfo # pylint: disable=pointless-statement
def test_getitem_keyerror(self, metainfo):
with pytest.raises(KeyError):
metainfo['does_not_exist'] # pylint: disable=pointless-statement
def test_setitem_keyerror(self, metainfo):
with pytest.raises(KeyError):
metainfo['does_not_exist'] = 42
def test_contains(self, metainfo):
assert 'force_rebuild' in metainfo
def test_modify(self, metainfo):
assert not metainfo['force_rebuild']
metainfo['force_rebuild'] = True
assert metainfo['force_rebuild']
2018-09-06 17:23:39 +02:00
class TestHistoryProgress:
@pytest.fixture
def progress(self):
return history.HistoryProgress()
def test_no_start(self, progress):
"""Test calling tick/finish without start."""
progress.tick()
progress.finish()
assert progress._progress is None
assert progress._value == 1
def test_gui(self, qtbot, progress):
progress.start("Hello World", 42)
dialog = progress._progress
qtbot.add_widget(dialog)
progress.tick()
assert dialog.isVisible()
assert dialog.labelText() == "Hello World"
assert dialog.minimum() == 0
assert dialog.maximum() == 42
assert dialog.value() == 1
assert dialog.minimumDuration() == 500
2018-09-06 17:23:39 +02:00
progress.finish()
assert not dialog.isVisible()