More sql code review fixes.

- remove outdated comment
- fix sql init error message
- clean up history text import code
- fix test_history file path in coverage check
- use real web history, not stub, for completion model tests
- use qtmodeltester in sql/list_category tests
- test url encoding in history tests
- fix test_clear by using a callable mock
- remove test_debug_dump_history_oserror as the check is now the same as
  for the file not existing
- rename nonempty to data in test_completionmodel
- add more delete_cur_item tests
- test empty option/value completion
This commit is contained in:
Ryan Roden-Corrent 2017-07-07 21:16:50 -04:00
parent 515e82262d
commit f9f8900fe9
11 changed files with 115 additions and 123 deletions

View File

@ -83,8 +83,6 @@ def run(args):
standarddir.init(args)
if args.version:
# we need to init sql to print the sql version
# we can use an in-memory database as we just want to query the version
print(version.version())
sys.exit(usertypes.Exit.ok)
@ -431,8 +429,8 @@ def _init_modules(args, crash_handler):
try:
sql.init(os.path.join(standarddir.data(), 'history.sqlite'))
except sql.SqlException as e:
error.handle_fatal_exc(e, args, 'Is sqlite installed?',
pre_text='Failed to initialize SQL')
error.handle_fatal_exc(e, args, 'Error initializing SQL',
pre_text='Error initializing SQL')
sys.exit(usertypes.Exit.err_init)
log.init.debug("Initializing web history...")

View File

@ -171,6 +171,8 @@ class WebHistory(sql.SqlTable):
def _parse_entry(self, line):
"""Parse a history line like '12345 http://example.com title'."""
if not line or line.startswith('#'):
return None
data = line.split(maxsplit=2)
if len(data) == 2:
atime, url = data
@ -240,11 +242,8 @@ class WebHistory(sql.SqlTable):
data = {'url': [], 'title': [], 'atime': [], 'redirect': []}
completion_data = {'url': [], 'title': [], 'last_atime': []}
for (i, line) in enumerate(f):
line = line.strip()
if not line or line.startswith('#'):
continue
try:
parsed = self._parse_entry(line)
parsed = self._parse_entry(line.strip())
if parsed is None:
continue
url, title, atime, redirect = parsed
@ -271,10 +270,6 @@ class WebHistory(sql.SqlTable):
"""
dest = os.path.expanduser(dest)
dirname = os.path.dirname(dest)
if not os.path.exists(dirname):
raise cmdexc.CommandError('Path does not exist', dirname)
lines = ('{}{} {} {}'
.format(int(x.atime), '-r' * x.redirect, x.url, x.title)
for x in self.select(sort_by='atime', sort_order='asc'))

View File

@ -51,12 +51,10 @@ PERFECT_FILES = [
'browser/webkit/cache.py'),
('tests/unit/browser/webkit/test_cookies.py',
'browser/webkit/cookies.py'),
('tests/unit/browser/webkit/test_history.py',
('tests/unit/browser/test_history.py',
'browser/history.py'),
('tests/unit/browser/webkit/test_history.py',
('tests/unit/browser/test_history.py',
'browser/webkit/webkithistory.py'),
('tests/unit/browser/webkit/test_history.py',
'browser/history.py'),
('tests/unit/browser/webkit/http/test_http.py',
'browser/webkit/http.py'),
('tests/unit/browser/webkit/http/test_content_disposition.py',

View File

@ -257,15 +257,6 @@ def bookmark_manager_stub(stubs):
objreg.delete('bookmark-manager')
@pytest.fixture
def web_history_stub(init_sql, stubs):
"""Fixture which provides a fake web-history object."""
stub = stubs.WebHistoryStub()
objreg.register('web-history', stub)
yield stub
objreg.delete('web-history')
@pytest.fixture
def session_manager_stub(stubs):
"""Fixture which provides a fake session-manager object."""
@ -491,3 +482,22 @@ def init_sql(data_tmpdir):
sql.init(path)
yield
sql.close()
@pytest.fixture
def validate_model(qtmodeltester):
"""Provides a function to validate a completion category."""
def validate(cat, expected):
"""Check that a category contains the items in the given order.
Args:
cat: The category to inspect.
expected: A list of tuples containing the expected items.
"""
qtmodeltester.data_display_may_return_none = True
qtmodeltester.check(cat)
assert cat.rowCount() == len(expected)
for row, items in enumerate(expected):
for col, item in enumerate(items):
assert cat.data(cat.index(row, col)) == item
return validate

View File

@ -33,7 +33,6 @@ from qutebrowser.browser import browsertab
from qutebrowser.config import configexc
from qutebrowser.utils import usertypes, utils
from qutebrowser.mainwindow import mainwindow
from qutebrowser.misc import sql
class FakeNetworkCache(QAbstractNetworkCache):
@ -523,37 +522,6 @@ class QuickmarkManagerStub(UrlMarkManagerStub):
self.delete(key)
class WebHistoryStub(sql.SqlTable):
"""Stub for the web-history object."""
def __init__(self):
super().__init__("History", ['url', 'title', 'atime', 'redirect'])
self.completion = sql.SqlTable("CompletionHistory",
['url', 'title', 'last_atime'])
def __contains__(self, url):
q = self.contains_query('url')
return q.run(val=url).value()
def add_url(self, url, title="", *, redirect=False, atime=None):
self.insert({'url': url, 'title': title, 'atime': atime,
'redirect': redirect})
if not redirect:
self.completion.insert({'url': url,
'title': title,
'last_atime': atime})
def delete_url(self, url):
"""Remove all history entries with the given url.
Args:
url: URL string to delete.
"""
self.delete('url', url)
self.completion.delete('url', url)
class HostBlockerStub:
"""Stub for the host-blocker object."""

View File

@ -170,16 +170,3 @@ def abs_datapath():
"""Get the absolute path to the end2end data directory."""
file_abs = os.path.abspath(os.path.dirname(__file__))
return os.path.join(file_abs, '..', 'end2end', 'data')
def validate_model(cat, expected):
"""Check that a category contains the expected items in the given order.
Args:
cat: The category to inspect.
expected: A list of tuples containing the expected items.
"""
assert cat.rowCount() == len(expected)
for row, items in enumerate(expected):
for col, item in enumerate(items):
assert cat.data(cat.index(row, col)) == item

View File

@ -21,6 +21,7 @@
import logging
import os
import unittest.mock
import pytest
from PyQt5.QtCore import QUrl
@ -114,7 +115,7 @@ def test_clear(qtbot, tmpdir, hist, mocker):
hist.add_url(QUrl('http://www.qutebrowser.org/'))
m = mocker.patch('qutebrowser.browser.history.message.confirm_async',
spec=[])
new=unittest.mock.Mock, spec=[])
hist.clear()
assert m.called
@ -134,16 +135,22 @@ def test_delete_url(hist):
before = set(hist)
hist.delete_url(QUrl('http://example.com/1'))
diff = before.difference(set(hist))
assert diff == set([('http://example.com/1', '', 0, False)])
assert diff == {('http://example.com/1', '', 0, False)}
@pytest.mark.parametrize('url, atime, title, redirect', [
('http://www.example.com', 12346, 'the title', False),
('http://www.example.com', 12346, 'the title', True)
@pytest.mark.parametrize('url, atime, title, redirect, expected_url', [
('http://www.example.com', 12346, 'the title', False,
'http://www.example.com'),
('http://www.example.com', 12346, 'the title', True,
'http://www.example.com'),
('http://www.example.com/spa ce', 12346, 'the title', False,
'http://www.example.com/spa%20ce'),
('https://user:pass@example.com', 12346, 'the title', False,
'https://user@example.com'),
])
def test_add_item(qtbot, hist, url, atime, title, redirect):
def test_add_item(qtbot, hist, url, atime, title, redirect, expected_url):
hist.add_url(QUrl(url), atime=atime, title=title, redirect=redirect)
assert list(hist) == [(url, title, atime, redirect)]
assert list(hist) == [(expected_url, title, atime, redirect)]
def test_add_item_invalid(qtbot, hist, caplog):
@ -164,7 +171,7 @@ def test_add_item_invalid(qtbot, hist, caplog):
def test_add_from_tab(hist, level, url, req_url, expected, mock_time, caplog):
with caplog.at_level(level):
hist.add_from_tab(QUrl(url), QUrl(req_url), 'title')
assert set(list(hist)) == set(expected)
assert set(hist) == set(expected)
@pytest.fixture
@ -330,11 +337,3 @@ def test_debug_dump_history_nonexistent(hist, tmpdir):
histfile = tmpdir / 'nonexistent' / 'history'
with pytest.raises(cmdexc.CommandError):
hist.debug_dump_history(str(histfile))
def test_debug_dump_history_oserror(hist, tmpdir):
histfile = tmpdir / 'history'
histfile.write('')
os.chmod(str(histfile), 0)
with pytest.raises(cmdexc.CommandError):
hist.debug_dump_history(str(histfile))

View File

@ -39,18 +39,18 @@ def test_first_last_item(counts):
cat = mock.Mock(spec=['layoutChanged'])
cat.rowCount = mock.Mock(return_value=c, spec=[])
model.add_category(cat)
nonempty = [i for i, rowCount in enumerate(counts) if rowCount > 0]
if not nonempty:
data = [i for i, rowCount in enumerate(counts) if rowCount > 0]
if not data:
# with no items, first and last should be an invalid index
assert not model.first_item().isValid()
assert not model.last_item().isValid()
else:
first = nonempty[0]
last = nonempty[-1]
# first item of the first nonempty category
first = data[0]
last = data[-1]
# first item of the first data category
assert model.first_item().row() == 0
assert model.first_item().parent().row() == first
# last item of the last nonempty category
# last item of the last data category
assert model.last_item().row() == counts[last] - 1
assert model.last_item().parent().row() == last

View File

@ -19,10 +19,12 @@
"""Tests for CompletionFilterModel."""
from unittest import mock
import pytest
from helpers import utils
from qutebrowser.completion.models import listcategory
from qutebrowser.commands import cmdexc
@pytest.mark.parametrize('pattern, before, after', [
@ -43,8 +45,26 @@ from qutebrowser.completion.models import listcategory
[('foo', 'bar'), ('bar', 'foo'), ('bar', 'bar')],
[('foo', 'bar'), ('bar', 'foo')]),
])
def test_set_pattern(pattern, before, after):
def test_set_pattern(pattern, before, after, validate_model):
"""Validate the filtering and sorting results of set_pattern."""
cat = listcategory.ListCategory('Foo', before)
cat.set_pattern(pattern)
utils.validate_model(cat, after)
validate_model(cat, after)
def test_delete_cur_item(validate_model):
func = mock.Mock(spec=[])
cat = listcategory.ListCategory('Foo', [('a', 'b'), ('c', 'd')],
delete_func=func)
idx = cat.index(0, 0)
cat.delete_cur_item(idx)
func.assert_called_once_with(['a', 'b'])
validate_model(cat, [('c', 'd')])
def test_delete_cur_item_no_func(validate_model):
cat = listcategory.ListCategory('Foo', [('a', 'b'), ('c', 'd')])
idx = cat.index(0, 0)
with pytest.raises(cmdexc.CommandError, match="Cannot delete this item."):
cat.delete_cur_item(idx)
validate_model(cat, [('a', 'b'), ('c', 'd')])

View File

@ -27,7 +27,8 @@ from PyQt5.QtCore import QUrl
from qutebrowser.completion.models import miscmodels, urlmodel, configmodel
from qutebrowser.config import sections, value
from qutebrowser.misc import sql
from qutebrowser.utils import objreg
from qutebrowser.browser import history
def _check_completions(model, expected):
@ -136,29 +137,33 @@ def bookmarks(bookmark_manager_stub):
@pytest.fixture
def history_completion_table(init_sql):
return sql.SqlTable("CompletionHistory", ['url', 'title', 'last_atime'])
def web_history(init_sql, stubs):
"""Fixture which provides a web-history object."""
stub = history.WebHistory()
objreg.register('web-history', stub)
yield stub
objreg.delete('web-history')
@pytest.fixture
def web_history(web_history_stub):
def web_history_populated(web_history):
"""Pre-populate the web-history database."""
web_history_stub.add_url(
url='http://qutebrowser.org',
web_history.add_url(
url=QUrl('http://qutebrowser.org'),
title='qutebrowser',
atime=datetime(2015, 9, 5).timestamp()
)
web_history_stub.add_url(
url='https://python.org',
web_history.add_url(
url=QUrl('https://python.org'),
title='Welcome to Python.org',
atime=datetime(2016, 3, 8).timestamp()
)
web_history_stub.add_url(
url='https://github.com',
web_history.add_url(
url=QUrl('https://github.com'),
title='https://github.com',
atime=datetime(2016, 5, 1).timestamp()
)
return web_history_stub
return web_history
def test_command_completion(qtmodeltester, monkeypatch, stubs, config_stub,
@ -261,8 +266,8 @@ def test_bookmark_completion(qtmodeltester, bookmarks):
})
def test_url_completion(qtmodeltester, config_stub, web_history, quickmarks,
bookmarks):
def test_url_completion(qtmodeltester, config_stub, web_history_populated,
quickmarks, bookmarks):
"""Test the results of url completion.
Verify that:
@ -313,12 +318,12 @@ def test_url_completion(qtmodeltester, config_stub, web_history, quickmarks,
('foo%bar', '', '%', 1),
('foobar', '', '%', 0),
])
def test_url_completion_pattern(config_stub, web_history_stub,
def test_url_completion_pattern(config_stub, web_history,
quickmark_manager_stub, bookmark_manager_stub,
url, title, pattern, rowcount):
"""Test that url completion filters by url and title."""
config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d'}
web_history_stub.add_url(url, title)
web_history.add_url(QUrl(url), title)
model = urlmodel.url()
model.set_pattern(pattern)
# 2, 0 is History
@ -349,7 +354,7 @@ def test_url_completion_delete_bookmark(qtmodeltester, config_stub, bookmarks,
def test_url_completion_delete_quickmark(qtmodeltester, config_stub,
web_history, quickmarks, bookmarks,
quickmarks, web_history, bookmarks,
qtbot):
"""Test deleting a bookmark from the url completion model."""
config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d'}
@ -373,7 +378,7 @@ def test_url_completion_delete_quickmark(qtmodeltester, config_stub,
def test_url_completion_delete_history(qtmodeltester, config_stub,
web_history_stub, web_history,
web_history_populated,
quickmarks, bookmarks):
"""Test deleting a history entry."""
config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d'}
@ -389,9 +394,9 @@ def test_url_completion_delete_history(qtmodeltester, config_stub,
assert model.data(parent) == "History"
assert model.data(idx) == 'https://python.org'
assert 'https://python.org' in web_history_stub
assert 'https://python.org' in web_history_populated
model.delete_cur_item(idx)
assert 'https://python.org' not in web_history_stub
assert 'https://python.org' not in web_history_populated
def test_session_completion(qtmodeltester, session_manager_stub):
@ -504,6 +509,12 @@ def test_setting_option_completion(qtmodeltester, monkeypatch, stubs,
})
def test_setting_option_completion_empty(monkeypatch, stubs, config_stub):
module = 'qutebrowser.completion.models.configmodel'
_patch_configdata(monkeypatch, stubs, module + '.configdata.DATA')
assert configmodel.option('typo') is None
def test_setting_option_completion_valuelist(qtmodeltester, monkeypatch, stubs,
config_stub):
module = 'qutebrowser.completion.models.configmodel'
@ -545,6 +556,13 @@ def test_setting_value_completion(qtmodeltester, monkeypatch, stubs,
})
def test_setting_value_completion_empty(monkeypatch, stubs, config_stub):
module = 'qutebrowser.completion.models.configmodel'
_patch_configdata(monkeypatch, stubs, module + '.configdata.DATA')
config_stub.data = {'general': {}}
assert configmodel.value('general', 'typo') is None
def test_bind_completion(qtmodeltester, monkeypatch, stubs, config_stub,
key_config_stub):
"""Test the results of keybinding command completion.
@ -583,7 +601,7 @@ def test_bind_completion(qtmodeltester, monkeypatch, stubs, config_stub,
def test_url_completion_benchmark(benchmark, config_stub,
quickmark_manager_stub,
bookmark_manager_stub,
web_history_stub):
web_history):
"""Benchmark url completion."""
config_stub.data['completion'] = {'timestamp-format': '%Y-%m-%d',
'web-history-max-items': 1000}
@ -595,7 +613,7 @@ def test_url_completion_benchmark(benchmark, config_stub,
'title': ['title{}'.format(i) for i in r]
}
web_history_stub.completion.insert_batch(entries)
web_history.completion.insert_batch(entries)
quickmark_manager_stub.marks = collections.OrderedDict([
('title{}'.format(i), 'example.com/{}'.format(i))

View File

@ -23,7 +23,6 @@ import unittest.mock
import pytest
from helpers import utils
from qutebrowser.misc import sql
from qutebrowser.completion.models import sqlcategory
from qutebrowser.commands import cmdexc
@ -61,14 +60,14 @@ pytestmark = pytest.mark.usefixtures('init_sql')
[('B', 'C', 2), ('A', 'F', 0), ('C', 'A', 1)],
[('B', 'C', 2), ('C', 'A', 1), ('A', 'F', 0)]),
])
def test_sorting(sort_by, sort_order, data, expected):
def test_sorting(sort_by, sort_order, data, expected, validate_model):
table = sql.SqlTable('Foo', ['a', 'b', 'c'])
for row in data:
table.insert({'a': row[0], 'b': row[1], 'c': row[2]})
cat = sqlcategory.SqlCategory('Foo', filter_fields=['a'], sort_by=sort_by,
sort_order=sort_order)
cat.set_pattern('')
utils.validate_model(cat, expected)
validate_model(cat, expected)
@pytest.mark.parametrize('pattern, filter_cols, before, after', [
@ -116,7 +115,7 @@ def test_sorting(sort_by, sort_order, data, expected):
[("can't touch this", '', ''), ('a', '', '')],
[("can't touch this", '', '')]),
])
def test_set_pattern(pattern, filter_cols, before, after):
def test_set_pattern(pattern, filter_cols, before, after, validate_model):
"""Validate the filtering and sorting results of set_pattern."""
table = sql.SqlTable('Foo', ['a', 'b', 'c'])
for row in before:
@ -124,15 +123,15 @@ def test_set_pattern(pattern, filter_cols, before, after):
filter_fields = [['a', 'b', 'c'][i] for i in filter_cols]
cat = sqlcategory.SqlCategory('Foo', filter_fields=filter_fields)
cat.set_pattern(pattern)
utils.validate_model(cat, after)
validate_model(cat, after)
def test_select():
def test_select(validate_model):
table = sql.SqlTable('Foo', ['a', 'b', 'c'])
table.insert({'a': 'foo', 'b': 'bar', 'c': 'baz'})
cat = sqlcategory.SqlCategory('Foo', filter_fields=['a'], select='b, c, a')
cat.set_pattern('')
utils.validate_model(cat, [('bar', 'baz', 'foo')])
validate_model(cat, [('bar', 'baz', 'foo')])
def test_delete_cur_item():