Merge branch 'master' into feature/directory-browser
This commit is contained in:
commit
77190554cc
@ -12,9 +12,8 @@ install:
|
|||||||
- C:\Python27\python -u scripts\dev\ci_install.py
|
- C:\Python27\python -u scripts\dev\ci_install.py
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- C:\Python34\Scripts\tox -e smoke
|
- C:\Python34\Scripts\tox -e py34
|
||||||
- C:\Python34\Scripts\tox -e smoke-frozen
|
|
||||||
- C:\Python34\Scripts\tox -e unittests
|
|
||||||
- C:\Python34\Scripts\tox -e unittests-frozen
|
- C:\Python34\Scripts\tox -e unittests-frozen
|
||||||
|
- C:\Python34\Scripts\tox -e smoke-frozen
|
||||||
- C:\Python34\Scripts\tox -e pyflakes
|
- C:\Python34\Scripts\tox -e pyflakes
|
||||||
- C:\Python34\Scripts\tox -e pylint
|
- C:\Python34\Scripts\tox -e pylint
|
||||||
|
@ -12,3 +12,6 @@ exclude_lines =
|
|||||||
raise AssertionError
|
raise AssertionError
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
if __name__ == ["']__main__["']:
|
if __name__ == ["']__main__["']:
|
||||||
|
|
||||||
|
[xml]
|
||||||
|
output=.coverage.xml
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -21,7 +21,7 @@ __pycache__
|
|||||||
/.venv
|
/.venv
|
||||||
/.coverage
|
/.coverage
|
||||||
/htmlcov
|
/htmlcov
|
||||||
/coverage.xml
|
/.coverage.xml
|
||||||
/.tox
|
/.tox
|
||||||
/testresults.html
|
/testresults.html
|
||||||
/.cache
|
/.cache
|
||||||
|
@ -17,7 +17,7 @@ install:
|
|||||||
- python scripts/dev/ci_install.py
|
- python scripts/dev/ci_install.py
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- xvfb-run -s "-screen 0 640x480x16" tox -e unittests,smoke
|
- xvfb-run -s "-screen 0 640x480x16" tox -e py34
|
||||||
- tox -e misc
|
- tox -e misc
|
||||||
- tox -e pep257
|
- tox -e pep257
|
||||||
- tox -e pyflakes
|
- tox -e pyflakes
|
||||||
|
@ -48,6 +48,8 @@ Changed
|
|||||||
mode and is not hidden anymore.
|
mode and is not hidden anymore.
|
||||||
- `minimal_webkit_testbrowser.py` now has a `--webengine` switch to test
|
- `minimal_webkit_testbrowser.py` now has a `--webengine` switch to test
|
||||||
QtWebEngine if it's installed.
|
QtWebEngine if it's installed.
|
||||||
|
- The column width percentages for the completion view now depend on the
|
||||||
|
completion model.
|
||||||
|
|
||||||
Fixed
|
Fixed
|
||||||
~~~~~
|
~~~~~
|
||||||
@ -60,6 +62,8 @@ Fixed
|
|||||||
- Fixed entering of insert mode when certain disabled text fields were clicked.
|
- Fixed entering of insert mode when certain disabled text fields were clicked.
|
||||||
- Fixed a crash when using `:set` with `-p` and `!` (invert value)
|
- Fixed a crash when using `:set` with `-p` and `!` (invert value)
|
||||||
- Downloads with unknown size are now handled correctly.
|
- Downloads with unknown size are now handled correctly.
|
||||||
|
- `:navigate increment/decrement` (`<Ctrl-A>`/`<Ctrl-X>`) now handles some
|
||||||
|
corner-cases better.
|
||||||
|
|
||||||
Removed
|
Removed
|
||||||
~~~~~~~
|
~~~~~~~
|
||||||
|
@ -143,11 +143,12 @@ Contributors, sorted by the number of commits in descending order:
|
|||||||
* Lamar Pavel
|
* Lamar Pavel
|
||||||
* Austin Anderson
|
* Austin Anderson
|
||||||
* Artur Shaik
|
* Artur Shaik
|
||||||
|
* Alexander Cogneau
|
||||||
* ZDarian
|
* ZDarian
|
||||||
* Peter Vilim
|
* Peter Vilim
|
||||||
* John ShaggyTwoDope Jenkins
|
* John ShaggyTwoDope Jenkins
|
||||||
|
* Daniel
|
||||||
* Jimmy
|
* Jimmy
|
||||||
* Alexander Cogneau
|
|
||||||
* Zach-Button
|
* Zach-Button
|
||||||
* rikn00
|
* rikn00
|
||||||
* Patric Schmitz
|
* Patric Schmitz
|
||||||
@ -157,6 +158,7 @@ Contributors, sorted by the number of commits in descending order:
|
|||||||
* sbinix
|
* sbinix
|
||||||
* Tobias Patzl
|
* Tobias Patzl
|
||||||
* Johannes Altmanninger
|
* Johannes Altmanninger
|
||||||
|
* Thorsten Wißmann
|
||||||
* Samir Benmendil
|
* Samir Benmendil
|
||||||
* Regina Hug
|
* Regina Hug
|
||||||
* Mathias Fussenegger
|
* Mathias Fussenegger
|
||||||
@ -166,7 +168,6 @@ Contributors, sorted by the number of commits in descending order:
|
|||||||
* zwarag
|
* zwarag
|
||||||
* error800
|
* error800
|
||||||
* Tim Harder
|
* Tim Harder
|
||||||
* Thorsten Wißmann
|
|
||||||
* Thiago Barroso Perrotta
|
* Thiago Barroso Perrotta
|
||||||
* Matthias Lisin
|
* Matthias Lisin
|
||||||
* Helen Sherwood-Taylor
|
* Helen Sherwood-Taylor
|
||||||
|
@ -630,6 +630,8 @@ Syntax: +:tab-focus ['index']+
|
|||||||
|
|
||||||
Select the tab given as argument/[count].
|
Select the tab given as argument/[count].
|
||||||
|
|
||||||
|
If neither count nor index are given, it behaves like tab-next.
|
||||||
|
|
||||||
==== positional arguments
|
==== positional arguments
|
||||||
* +'index'+: The tab index to focus, starting with 1. The special value `last` focuses the last focused tab.
|
* +'index'+: The tab index to focus, starting with 1. The special value `last` focuses the last focused tab.
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ markers =
|
|||||||
osx: Tests which only can run on OS X.
|
osx: Tests which only can run on OS X.
|
||||||
not_frozen: Tests which can't be run if sys.frozen is True.
|
not_frozen: Tests which can't be run if sys.frozen is True.
|
||||||
frozen: Tests which can only be run if sys.frozen is True.
|
frozen: Tests which can only be run if sys.frozen is True.
|
||||||
|
integration: Tests which test a bigger portion of code, run without coverage.
|
||||||
flakes-ignore =
|
flakes-ignore =
|
||||||
UnusedImport
|
UnusedImport
|
||||||
UnusedVariable
|
UnusedVariable
|
||||||
|
@ -272,7 +272,7 @@ def process_pos_args(args, via_ipc=False, cwd=None):
|
|||||||
log.init.debug("Startup URL {}".format(cmd))
|
log.init.debug("Startup URL {}".format(cmd))
|
||||||
try:
|
try:
|
||||||
url = urlutils.fuzzy_url(cmd, cwd, relative=True)
|
url = urlutils.fuzzy_url(cmd, cwd, relative=True)
|
||||||
except urlutils.FuzzyUrlError as e:
|
except urlutils.InvalidUrlError as e:
|
||||||
message.error('current', "Error in startup argument '{}': "
|
message.error('current', "Error in startup argument '{}': "
|
||||||
"{}".format(cmd, e))
|
"{}".format(cmd, e))
|
||||||
else:
|
else:
|
||||||
@ -302,7 +302,7 @@ def _open_startpage(win_id=None):
|
|||||||
for urlstr in config.get('general', 'startpage'):
|
for urlstr in config.get('general', 'startpage'):
|
||||||
try:
|
try:
|
||||||
url = urlutils.fuzzy_url(urlstr, do_search=False)
|
url = urlutils.fuzzy_url(urlstr, do_search=False)
|
||||||
except urlutils.FuzzyUrlError as e:
|
except urlutils.InvalidUrlError as e:
|
||||||
message.error('current', "Error when opening startpage: "
|
message.error('current', "Error when opening startpage: "
|
||||||
"{}".format(e))
|
"{}".format(e))
|
||||||
tabbed_browser.tabopen(QUrl('about:blank'))
|
tabbed_browser.tabopen(QUrl('about:blank'))
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
|
|
||||||
"""Command dispatcher for TabbedBrowser."""
|
"""Command dispatcher for TabbedBrowser."""
|
||||||
|
|
||||||
import re
|
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
import posixpath
|
import posixpath
|
||||||
@ -304,7 +303,7 @@ class CommandDispatcher:
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
url = urlutils.fuzzy_url(url)
|
url = urlutils.fuzzy_url(url)
|
||||||
except urlutils.FuzzyUrlError as e:
|
except urlutils.InvalidUrlError as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
if tab or bg or window:
|
if tab or bg or window:
|
||||||
self._open(url, tab, bg, window)
|
self._open(url, tab, bg, window)
|
||||||
@ -472,29 +471,10 @@ class CommandDispatcher:
|
|||||||
background: Open the link in a new background tab.
|
background: Open the link in a new background tab.
|
||||||
window: Open the link in a new window.
|
window: Open the link in a new window.
|
||||||
"""
|
"""
|
||||||
encoded = bytes(url.toEncoded()).decode('ascii')
|
|
||||||
# Get the last number in a string
|
|
||||||
match = re.match(r'(.*\D|^)(\d+)(.*)', encoded)
|
|
||||||
if not match:
|
|
||||||
raise cmdexc.CommandError("No number found in URL!")
|
|
||||||
pre, number, post = match.groups()
|
|
||||||
if not number:
|
|
||||||
raise cmdexc.CommandError("No number found in URL!")
|
|
||||||
try:
|
try:
|
||||||
val = int(number)
|
new_url = urlutils.incdec_number(url, incdec)
|
||||||
except ValueError:
|
except urlutils.IncDecError as error:
|
||||||
raise cmdexc.CommandError("Could not parse number '{}'.".format(
|
raise cmdexc.CommandError(error.msg)
|
||||||
number))
|
|
||||||
if incdec == 'decrement':
|
|
||||||
if val <= 0:
|
|
||||||
raise cmdexc.CommandError("Can't decrement {}!".format(val))
|
|
||||||
val -= 1
|
|
||||||
elif incdec == 'increment':
|
|
||||||
val += 1
|
|
||||||
else:
|
|
||||||
raise ValueError("Invalid value {} for indec!".format(incdec))
|
|
||||||
urlstr = ''.join([pre, str(val), post]).encode('ascii')
|
|
||||||
new_url = QUrl.fromEncoded(urlstr)
|
|
||||||
self._open(new_url, tab, background, window)
|
self._open(new_url, tab, background, window)
|
||||||
|
|
||||||
def _navigate_up(self, url, tab, background, window):
|
def _navigate_up(self, url, tab, background, window):
|
||||||
@ -889,7 +869,7 @@ class CommandDispatcher:
|
|||||||
log.misc.debug("{} contained: '{}'".format(target, text))
|
log.misc.debug("{} contained: '{}'".format(target, text))
|
||||||
try:
|
try:
|
||||||
url = urlutils.fuzzy_url(text)
|
url = urlutils.fuzzy_url(text)
|
||||||
except urlutils.FuzzyUrlError as e:
|
except urlutils.InvalidUrlError as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
self._open(url, tab, bg, window)
|
self._open(url, tab, bg, window)
|
||||||
|
|
||||||
@ -898,6 +878,8 @@ class CommandDispatcher:
|
|||||||
def tab_focus(self, index: {'type': (int, 'last')}=None, count=None):
|
def tab_focus(self, index: {'type': (int, 'last')}=None, count=None):
|
||||||
"""Select the tab given as argument/[count].
|
"""Select the tab given as argument/[count].
|
||||||
|
|
||||||
|
If neither count nor index are given, it behaves like tab-next.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
index: The tab index to focus, starting with 1. The special value
|
index: The tab index to focus, starting with 1. The special value
|
||||||
`last` focuses the last focused tab.
|
`last` focuses the last focused tab.
|
||||||
@ -906,6 +888,9 @@ class CommandDispatcher:
|
|||||||
if index == 'last':
|
if index == 'last':
|
||||||
self._tab_focus_last()
|
self._tab_focus_last()
|
||||||
return
|
return
|
||||||
|
if index is None and count is None:
|
||||||
|
self.tab_next()
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
idx = cmdutils.arg_or_count(index, count, default=1,
|
idx = cmdutils.arg_or_count(index, count, default=1,
|
||||||
countzero=self._count())
|
countzero=self._count())
|
||||||
@ -1083,7 +1068,7 @@ class CommandDispatcher:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
url = urlutils.fuzzy_url(url)
|
url = urlutils.fuzzy_url(url)
|
||||||
except urlutils.FuzzyUrlError as e:
|
except urlutils.InvalidUrlError as e:
|
||||||
raise cmdexc.CommandError(e)
|
raise cmdexc.CommandError(e)
|
||||||
self._open(url, tab, bg, window)
|
self._open(url, tab, bg, window)
|
||||||
|
|
||||||
|
@ -350,16 +350,20 @@ class NetworkManager(QNetworkAccessManager):
|
|||||||
current_url = webview.url()
|
current_url = webview.url()
|
||||||
referer_header_conf = config.get('network', 'referer-header')
|
referer_header_conf = config.get('network', 'referer-header')
|
||||||
|
|
||||||
|
try:
|
||||||
if referer_header_conf == 'never':
|
if referer_header_conf == 'never':
|
||||||
# Note: using ''.encode('ascii') sends a header with no value,
|
# Note: using ''.encode('ascii') sends a header with no value,
|
||||||
# instead of no header at all
|
# instead of no header at all
|
||||||
req.setRawHeader('Referer'.encode('ascii'), QByteArray())
|
req.setRawHeader('Referer'.encode('ascii'), QByteArray())
|
||||||
elif (referer_header_conf == 'same-domain' and
|
elif (referer_header_conf == 'same-domain' and
|
||||||
current_url.isValid() and
|
|
||||||
not urlutils.same_domain(req.url(), current_url)):
|
not urlutils.same_domain(req.url(), current_url)):
|
||||||
req.setRawHeader('Referer'.encode('ascii'), QByteArray())
|
req.setRawHeader('Referer'.encode('ascii'), QByteArray())
|
||||||
# If refer_header_conf is set to 'always', we leave the header alone as
|
# If refer_header_conf is set to 'always', we leave the header
|
||||||
# QtWebKit did set it.
|
# alone as QtWebKit did set it.
|
||||||
|
except urlutils.InvalidUrlError:
|
||||||
|
# req.url() or current_url can be invalid - this happens on
|
||||||
|
# https://www.playstation.com/ for example.
|
||||||
|
pass
|
||||||
|
|
||||||
accept_language = config.get('network', 'accept-language')
|
accept_language = config.get('network', 'accept-language')
|
||||||
if accept_language is not None:
|
if accept_language is not None:
|
||||||
|
@ -225,7 +225,7 @@ class QuickmarkManager(UrlMarkManager):
|
|||||||
urlstr = self.marks[name]
|
urlstr = self.marks[name]
|
||||||
try:
|
try:
|
||||||
url = urlutils.fuzzy_url(urlstr, do_search=False)
|
url = urlutils.fuzzy_url(urlstr, do_search=False)
|
||||||
except urlutils.FuzzyUrlError as e:
|
except urlutils.InvalidUrlError as e:
|
||||||
raise InvalidUrlError(
|
raise InvalidUrlError(
|
||||||
"Invalid URL for quickmark {}: {}".format(name, str(e)))
|
"Invalid URL for quickmark {}: {}".format(name, str(e)))
|
||||||
return url
|
return url
|
||||||
|
@ -339,8 +339,6 @@ def get_child_frames(startframe):
|
|||||||
def focus_elem(frame):
|
def focus_elem(frame):
|
||||||
"""Get the focused element in a web frame.
|
"""Get the focused element in a web frame.
|
||||||
|
|
||||||
FIXME: Add tests.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
frame: The QWebFrame to search in.
|
frame: The QWebFrame to search in.
|
||||||
"""
|
"""
|
||||||
|
@ -28,6 +28,7 @@ from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QItemSelectionModel
|
|||||||
|
|
||||||
from qutebrowser.config import config, style
|
from qutebrowser.config import config, style
|
||||||
from qutebrowser.completion import completiondelegate, completer
|
from qutebrowser.completion import completiondelegate, completer
|
||||||
|
from qutebrowser.completion.models import base
|
||||||
from qutebrowser.utils import qtutils, objreg, utils
|
from qutebrowser.utils import qtutils, objreg, utils
|
||||||
|
|
||||||
|
|
||||||
@ -38,15 +39,13 @@ class CompletionView(QTreeView):
|
|||||||
Based on QTreeView but heavily customized so root elements show as category
|
Based on QTreeView but heavily customized so root elements show as category
|
||||||
headers, and children show as flat list.
|
headers, and children show as flat list.
|
||||||
|
|
||||||
Class attributes:
|
|
||||||
COLUMN_WIDTHS: A list of column widths, in percent.
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
enabled: Whether showing the CompletionView is enabled.
|
enabled: Whether showing the CompletionView is enabled.
|
||||||
_win_id: The ID of the window this CompletionView is associated with.
|
_win_id: The ID of the window this CompletionView is associated with.
|
||||||
_height: The height to use for the CompletionView.
|
_height: The height to use for the CompletionView.
|
||||||
_height_perc: Either None or a percentage if height should be relative.
|
_height_perc: Either None or a percentage if height should be relative.
|
||||||
_delegate: The item delegate used.
|
_delegate: The item delegate used.
|
||||||
|
_column_widths: A list of column widths, in percent.
|
||||||
|
|
||||||
Signals:
|
Signals:
|
||||||
resize_completion: Emitted when the completion should be resized.
|
resize_completion: Emitted when the completion should be resized.
|
||||||
@ -82,7 +81,6 @@ class CompletionView(QTreeView):
|
|||||||
border: 0px;
|
border: 0px;
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
COLUMN_WIDTHS = (20, 70, 10)
|
|
||||||
|
|
||||||
# FIXME style scrollbar
|
# FIXME style scrollbar
|
||||||
# https://github.com/The-Compiler/qutebrowser/issues/117
|
# https://github.com/The-Compiler/qutebrowser/issues/117
|
||||||
@ -103,6 +101,8 @@ class CompletionView(QTreeView):
|
|||||||
# FIXME handle new aliases.
|
# FIXME handle new aliases.
|
||||||
# objreg.get('config').changed.connect(self.init_command_completion)
|
# objreg.get('config').changed.connect(self.init_command_completion)
|
||||||
|
|
||||||
|
self._column_widths = base.BaseCompletionModel.COLUMN_WIDTHS
|
||||||
|
|
||||||
self._delegate = completiondelegate.CompletionItemDelegate(self)
|
self._delegate = completiondelegate.CompletionItemDelegate(self)
|
||||||
self.setItemDelegate(self._delegate)
|
self.setItemDelegate(self._delegate)
|
||||||
style.set_register_stylesheet(self)
|
style.set_register_stylesheet(self)
|
||||||
@ -128,9 +128,9 @@ class CompletionView(QTreeView):
|
|||||||
return utils.get_repr(self)
|
return utils.get_repr(self)
|
||||||
|
|
||||||
def _resize_columns(self):
|
def _resize_columns(self):
|
||||||
"""Resize the completion columns based on COLUMN_WIDTHS."""
|
"""Resize the completion columns based on column_widths."""
|
||||||
width = self.size().width()
|
width = self.size().width()
|
||||||
pixel_widths = [(width * perc // 100) for perc in self.COLUMN_WIDTHS]
|
pixel_widths = [(width * perc // 100) for perc in self._column_widths]
|
||||||
if self.verticalScrollBar().isVisible():
|
if self.verticalScrollBar().isVisible():
|
||||||
pixel_widths[-1] -= self.style().pixelMetric(
|
pixel_widths[-1] -= self.style().pixelMetric(
|
||||||
QStyle.PM_ScrollBarExtent) + 5
|
QStyle.PM_ScrollBarExtent) + 5
|
||||||
@ -203,6 +203,8 @@ class CompletionView(QTreeView):
|
|||||||
sel_model.deleteLater()
|
sel_model.deleteLater()
|
||||||
for i in range(model.rowCount()):
|
for i in range(model.rowCount()):
|
||||||
self.expand(model.index(i, 0))
|
self.expand(model.index(i, 0))
|
||||||
|
|
||||||
|
self._column_widths = model.srcmodel.COLUMN_WIDTHS
|
||||||
self._resize_columns()
|
self._resize_columns()
|
||||||
self.maybe_resize_completion()
|
self.maybe_resize_completion()
|
||||||
|
|
||||||
|
@ -39,8 +39,14 @@ class BaseCompletionModel(QStandardItemModel):
|
|||||||
|
|
||||||
Used for showing completions later in the CompletionView. Supports setting
|
Used for showing completions later in the CompletionView. Supports setting
|
||||||
marks and adding new categories/items easily.
|
marks and adding new categories/items easily.
|
||||||
|
|
||||||
|
Class Attributes:
|
||||||
|
COLUMN_WIDTHS: The width percentages of the columns used in the
|
||||||
|
completion view.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
COLUMN_WIDTHS = (30, 70, 0)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setColumnCount(3)
|
self.setColumnCount(3)
|
||||||
|
@ -32,6 +32,8 @@ class SettingSectionCompletionModel(base.BaseCompletionModel):
|
|||||||
|
|
||||||
# pylint: disable=abstract-method
|
# pylint: disable=abstract-method
|
||||||
|
|
||||||
|
COLUMN_WIDTHS = (20, 70, 10)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
cat = self.new_category("Sections")
|
cat = self.new_category("Sections")
|
||||||
@ -51,6 +53,8 @@ class SettingOptionCompletionModel(base.BaseCompletionModel):
|
|||||||
|
|
||||||
# pylint: disable=abstract-method
|
# pylint: disable=abstract-method
|
||||||
|
|
||||||
|
COLUMN_WIDTHS = (20, 70, 10)
|
||||||
|
|
||||||
def __init__(self, section, parent=None):
|
def __init__(self, section, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
cat = self.new_category(section)
|
cat = self.new_category(section)
|
||||||
@ -104,6 +108,8 @@ class SettingValueCompletionModel(base.BaseCompletionModel):
|
|||||||
|
|
||||||
# pylint: disable=abstract-method
|
# pylint: disable=abstract-method
|
||||||
|
|
||||||
|
COLUMN_WIDTHS = (20, 70, 10)
|
||||||
|
|
||||||
def __init__(self, section, option, parent=None):
|
def __init__(self, section, option, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._section = section
|
self._section = section
|
||||||
|
@ -40,6 +40,8 @@ class UrlCompletionModel(base.BaseCompletionModel):
|
|||||||
TEXT_COLUMN = 1
|
TEXT_COLUMN = 1
|
||||||
TIME_COLUMN = 2
|
TIME_COLUMN = 2
|
||||||
|
|
||||||
|
COLUMN_WIDTHS = (40, 50, 10)
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
|
@ -1236,7 +1236,7 @@ KEY_DATA = collections.OrderedDict([
|
|||||||
('tab-move', ['gm']),
|
('tab-move', ['gm']),
|
||||||
('tab-move -', ['gl']),
|
('tab-move -', ['gl']),
|
||||||
('tab-move +', ['gr']),
|
('tab-move +', ['gr']),
|
||||||
('tab-next', ['J', 'gt']),
|
('tab-focus', ['J', 'gt']),
|
||||||
('tab-prev', ['K', 'gT']),
|
('tab-prev', ['K', 'gT']),
|
||||||
('tab-clone', ['gC']),
|
('tab-clone', ['gC']),
|
||||||
('reload', ['r']),
|
('reload', ['r']),
|
||||||
|
@ -803,7 +803,7 @@ class File(BaseType):
|
|||||||
value = os.path.expandvars(value)
|
value = os.path.expandvars(value)
|
||||||
if not os.path.isabs(value):
|
if not os.path.isabs(value):
|
||||||
cfgdir = standarddir.config()
|
cfgdir = standarddir.config()
|
||||||
if cfgdir is not None:
|
assert cfgdir is not None
|
||||||
return os.path.join(cfgdir, value)
|
return os.path.join(cfgdir, value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@ -1113,7 +1113,7 @@ class FuzzyUrl(BaseType):
|
|||||||
from qutebrowser.utils import urlutils
|
from qutebrowser.utils import urlutils
|
||||||
try:
|
try:
|
||||||
self.transform(value)
|
self.transform(value)
|
||||||
except urlutils.FuzzyUrlError as e:
|
except urlutils.InvalidUrlError as e:
|
||||||
raise configexc.ValidationError(value, str(e))
|
raise configexc.ValidationError(value, str(e))
|
||||||
|
|
||||||
def transform(self, value):
|
def transform(self, value):
|
||||||
|
@ -26,7 +26,7 @@ class TextWrapper(textwrap.TextWrapper):
|
|||||||
|
|
||||||
"""Text wrapper customized to be used in configs."""
|
"""Text wrapper customized to be used in configs."""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
kw = {
|
kw = {
|
||||||
'width': 72,
|
'width': 72,
|
||||||
'replace_whitespace': False,
|
'replace_whitespace': False,
|
||||||
@ -36,4 +36,4 @@ class TextWrapper(textwrap.TextWrapper):
|
|||||||
'subsequent_indent': '# ',
|
'subsequent_indent': '# ',
|
||||||
}
|
}
|
||||||
kw.update(kwargs)
|
kw.update(kwargs)
|
||||||
super().__init__(*args, **kw)
|
super().__init__(**kw)
|
||||||
|
@ -55,9 +55,11 @@ class TextBase(QLabel):
|
|||||||
Args:
|
Args:
|
||||||
width: The maximal width the text should take.
|
width: The maximal width the text should take.
|
||||||
"""
|
"""
|
||||||
if self.text is not None:
|
if self.text():
|
||||||
self._elided_text = self.fontMetrics().elidedText(
|
self._elided_text = self.fontMetrics().elidedText(
|
||||||
self.text(), self._elidemode, width, Qt.TextShowMnemonic)
|
self.text(), self._elidemode, width, Qt.TextShowMnemonic)
|
||||||
|
else:
|
||||||
|
self._elided_text = ''
|
||||||
|
|
||||||
def setText(self, txt):
|
def setText(self, txt):
|
||||||
"""Extend QLabel::setText.
|
"""Extend QLabel::setText.
|
||||||
|
@ -25,7 +25,7 @@ import functools
|
|||||||
import datetime
|
import datetime
|
||||||
import contextlib
|
import contextlib
|
||||||
|
|
||||||
from PyQt5.QtCore import QEvent, QMetaMethod, QObject
|
from PyQt5.QtCore import Qt, QEvent, QMetaMethod, QObject
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from qutebrowser.utils import log, utils, qtutils, objreg
|
from qutebrowser.utils import log, utils, qtutils, objreg
|
||||||
@ -56,7 +56,7 @@ def log_signals(obj):
|
|||||||
dbg = dbg_signal(signal, args)
|
dbg = dbg_signal(signal, args)
|
||||||
try:
|
try:
|
||||||
r = repr(obj)
|
r = repr(obj)
|
||||||
except RuntimeError:
|
except RuntimeError: # pragma: no cover
|
||||||
r = '<deleted>'
|
r = '<deleted>'
|
||||||
log.signals.debug("Signal in {}: {}".format(r, dbg))
|
log.signals.debug("Signal in {}: {}".format(r, dbg))
|
||||||
|
|
||||||
@ -68,6 +68,7 @@ def log_signals(obj):
|
|||||||
qtutils.ensure_valid(meta_method)
|
qtutils.ensure_valid(meta_method)
|
||||||
if meta_method.methodType() == QMetaMethod.Signal:
|
if meta_method.methodType() == QMetaMethod.Signal:
|
||||||
name = bytes(meta_method.name()).decode('ascii')
|
name = bytes(meta_method.name()).decode('ascii')
|
||||||
|
if name != 'destroyed':
|
||||||
signal = getattr(obj, name)
|
signal = getattr(obj, name)
|
||||||
signal.connect(functools.partial(log_slot, obj, signal))
|
signal.connect(functools.partial(log_slot, obj, signal))
|
||||||
|
|
||||||
@ -105,19 +106,21 @@ def qenum_key(base, value, add_base=False, klass=None):
|
|||||||
klass = value.__class__
|
klass = value.__class__
|
||||||
if klass == int:
|
if klass == int:
|
||||||
raise TypeError("Can't guess enum class of an int!")
|
raise TypeError("Can't guess enum class of an int!")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
idx = klass.staticMetaObject.indexOfEnumerator(klass.__name__)
|
idx = base.staticMetaObject.indexOfEnumerator(klass.__name__)
|
||||||
|
ret = base.staticMetaObject.enumerator(idx).valueToKey(value)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
idx = -1
|
ret = None
|
||||||
if idx != -1:
|
|
||||||
ret = klass.staticMetaObject.enumerator(idx).valueToKey(value)
|
if ret is None:
|
||||||
else:
|
|
||||||
for name, obj in vars(base).items():
|
for name, obj in vars(base).items():
|
||||||
if isinstance(obj, klass) and obj == value:
|
if isinstance(obj, klass) and obj == value:
|
||||||
ret = name
|
ret = name
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
ret = '0x{:04x}'.format(int(value))
|
ret = '0x{:04x}'.format(int(value))
|
||||||
|
|
||||||
if add_base and hasattr(base, '__name__'):
|
if add_base and hasattr(base, '__name__'):
|
||||||
return '.'.join([base.__name__, ret])
|
return '.'.join([base.__name__, ret])
|
||||||
else:
|
else:
|
||||||
@ -177,7 +180,7 @@ def signal_name(sig):
|
|||||||
return m.group(1)
|
return m.group(1)
|
||||||
|
|
||||||
|
|
||||||
def _format_args(args=None, kwargs=None):
|
def format_args(args=None, kwargs=None):
|
||||||
"""Format a list of arguments/kwargs to a function-call like string."""
|
"""Format a list of arguments/kwargs to a function-call like string."""
|
||||||
if args is not None:
|
if args is not None:
|
||||||
arglist = [utils.compact_text(repr(arg), 200) for arg in args]
|
arglist = [utils.compact_text(repr(arg), 200) for arg in args]
|
||||||
@ -199,7 +202,7 @@ def dbg_signal(sig, args):
|
|||||||
Return:
|
Return:
|
||||||
A human-readable string representation of signal/args.
|
A human-readable string representation of signal/args.
|
||||||
"""
|
"""
|
||||||
return '{}({})'.format(signal_name(sig), _format_args(args))
|
return '{}({})'.format(signal_name(sig), format_args(args))
|
||||||
|
|
||||||
|
|
||||||
def format_call(func, args=None, kwargs=None, full=True):
|
def format_call(func, args=None, kwargs=None, full=True):
|
||||||
@ -218,7 +221,7 @@ def format_call(func, args=None, kwargs=None, full=True):
|
|||||||
name = utils.qualname(func)
|
name = utils.qualname(func)
|
||||||
else:
|
else:
|
||||||
name = func.__name__
|
name = func.__name__
|
||||||
return '{}({})'.format(name, _format_args(args, kwargs))
|
return '{}({})'.format(name, format_args(args, kwargs))
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
@ -247,25 +250,30 @@ def _get_widgets():
|
|||||||
|
|
||||||
def _get_pyqt_objects(lines, obj, depth=0):
|
def _get_pyqt_objects(lines, obj, depth=0):
|
||||||
"""Recursive method for get_all_objects to get Qt objects."""
|
"""Recursive method for get_all_objects to get Qt objects."""
|
||||||
for kid in obj.findChildren(QObject):
|
for kid in obj.findChildren(QObject, '', Qt.FindDirectChildrenOnly):
|
||||||
lines.append(' ' * depth + repr(kid))
|
lines.append(' ' * depth + repr(kid))
|
||||||
_get_pyqt_objects(lines, kid, depth + 1)
|
_get_pyqt_objects(lines, kid, depth + 1)
|
||||||
|
|
||||||
|
|
||||||
def get_all_objects():
|
def get_all_objects(start_obj=None):
|
||||||
"""Get all children of an object recursively as a string."""
|
"""Get all children of an object recursively as a string."""
|
||||||
output = ['']
|
output = ['']
|
||||||
widget_lines = _get_widgets()
|
widget_lines = _get_widgets()
|
||||||
widget_lines = [' ' + e for e in widget_lines]
|
widget_lines = [' ' + e for e in widget_lines]
|
||||||
widget_lines.insert(0, "Qt widgets - {} objects".format(
|
widget_lines.insert(0, "Qt widgets - {} objects:".format(
|
||||||
len(widget_lines)))
|
len(widget_lines)))
|
||||||
output += widget_lines
|
output += widget_lines
|
||||||
|
|
||||||
|
if start_obj is None:
|
||||||
|
start_obj = QApplication.instance()
|
||||||
|
|
||||||
pyqt_lines = []
|
pyqt_lines = []
|
||||||
_get_pyqt_objects(pyqt_lines, QApplication.instance())
|
_get_pyqt_objects(pyqt_lines, start_obj)
|
||||||
pyqt_lines = [' ' + e for e in pyqt_lines]
|
pyqt_lines = [' ' + e for e in pyqt_lines]
|
||||||
pyqt_lines.insert(0, 'Qt objects - {} objects:'.format(
|
pyqt_lines.insert(0, 'Qt objects - {} objects:'.format(
|
||||||
len(pyqt_lines)))
|
len(pyqt_lines)))
|
||||||
output += pyqt_lines
|
|
||||||
output += ['']
|
output += ['']
|
||||||
|
output += pyqt_lines
|
||||||
output += objreg.dump_objects()
|
output += objreg.dump_objects()
|
||||||
return '\n'.join(output)
|
return '\n'.join(output)
|
||||||
|
@ -37,6 +37,22 @@ from qutebrowser.commands import cmdexc
|
|||||||
# https://github.com/The-Compiler/qutebrowser/issues/108
|
# https://github.com/The-Compiler/qutebrowser/issues/108
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidUrlError(ValueError):
|
||||||
|
|
||||||
|
"""Error raised if a function got an invalid URL.
|
||||||
|
|
||||||
|
Inherits ValueError because that was the exception originally used for
|
||||||
|
that, so there still might be some code around which checks for that.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, url):
|
||||||
|
if url.isValid():
|
||||||
|
raise ValueError("Got valid URL {}!".format(url.toDisplayString()))
|
||||||
|
self.url = url
|
||||||
|
self.msg = get_errstring(url)
|
||||||
|
super().__init__(self.msg)
|
||||||
|
|
||||||
|
|
||||||
def _parse_search_term(s):
|
def _parse_search_term(s):
|
||||||
"""Get a search engine name and search term from a string.
|
"""Get a search engine name and search term from a string.
|
||||||
|
|
||||||
@ -185,7 +201,7 @@ def fuzzy_url(urlstr, cwd=None, relative=False, do_search=True):
|
|||||||
qtutils.ensure_valid(url)
|
qtutils.ensure_valid(url)
|
||||||
else:
|
else:
|
||||||
if not url.isValid():
|
if not url.isValid():
|
||||||
raise FuzzyUrlError("Invalid URL '{}'!".format(urlstr), url)
|
raise InvalidUrlError(url)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
@ -355,7 +371,7 @@ def host_tuple(url):
|
|||||||
This is suitable to identify a connection, e.g. for SSL errors.
|
This is suitable to identify a connection, e.g. for SSL errors.
|
||||||
"""
|
"""
|
||||||
if not url.isValid():
|
if not url.isValid():
|
||||||
raise ValueError(get_errstring(url))
|
raise InvalidUrlError(url)
|
||||||
scheme, host, port = url.scheme(), url.host(), url.port()
|
scheme, host, port = url.scheme(), url.host(), url.port()
|
||||||
assert scheme
|
assert scheme
|
||||||
if not host:
|
if not host:
|
||||||
@ -405,9 +421,9 @@ def same_domain(url1, url2):
|
|||||||
True if the domains are the same, False otherwise.
|
True if the domains are the same, False otherwise.
|
||||||
"""
|
"""
|
||||||
if not url1.isValid():
|
if not url1.isValid():
|
||||||
raise ValueError(get_errstring(url1))
|
raise InvalidUrlError(url1)
|
||||||
if not url2.isValid():
|
if not url2.isValid():
|
||||||
raise ValueError(get_errstring(url2))
|
raise InvalidUrlError(url2)
|
||||||
|
|
||||||
suffix1 = url1.topLevelDomain()
|
suffix1 = url1.topLevelDomain()
|
||||||
suffix2 = url2.topLevelDomain()
|
suffix2 = url2.topLevelDomain()
|
||||||
@ -422,24 +438,57 @@ def same_domain(url1, url2):
|
|||||||
return domain1 == domain2
|
return domain1 == domain2
|
||||||
|
|
||||||
|
|
||||||
class FuzzyUrlError(Exception):
|
class IncDecError(Exception):
|
||||||
|
|
||||||
"""Exception raised by fuzzy_url on problems.
|
"""Exception raised by incdec_number on problems.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
msg: The error message to use.
|
msg: The error message.
|
||||||
url: The QUrl which caused the error.
|
url: The QUrl which caused the error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg, url=None):
|
def __init__(self, msg, url):
|
||||||
super().__init__(msg)
|
super().__init__(msg)
|
||||||
if url is not None and url.isValid():
|
|
||||||
raise ValueError("Got valid URL {}!".format(url.toDisplayString()))
|
|
||||||
self.url = url
|
self.url = url
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.url is None or not self.url.errorString():
|
return '{}: {}'.format(self.msg, self.url.toString())
|
||||||
return self.msg
|
|
||||||
|
|
||||||
|
def incdec_number(url, incdec):
|
||||||
|
"""Find a number in the url and increment or decrement it.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url: The current url
|
||||||
|
incdec: Either 'increment' or 'decrement'
|
||||||
|
|
||||||
|
Return:
|
||||||
|
The new url with the number incremented/decremented.
|
||||||
|
|
||||||
|
Raises IncDecError if the url contains no number.
|
||||||
|
"""
|
||||||
|
if not url.isValid():
|
||||||
|
raise InvalidUrlError(url)
|
||||||
|
|
||||||
|
path = url.path()
|
||||||
|
# Get the last number in a string
|
||||||
|
match = re.match(r'(.*\D|^)(\d+)(.*)', path)
|
||||||
|
if not match:
|
||||||
|
raise IncDecError("No number found in URL!", url)
|
||||||
|
pre, number, post = match.groups()
|
||||||
|
# This should always succeed because we match \d+
|
||||||
|
val = int(number)
|
||||||
|
if incdec == 'decrement':
|
||||||
|
if val <= 0:
|
||||||
|
raise IncDecError("Can't decrement {}!".format(val), url)
|
||||||
|
val -= 1
|
||||||
|
elif incdec == 'increment':
|
||||||
|
val += 1
|
||||||
else:
|
else:
|
||||||
return '{}: {}'.format(self.msg, self.url.errorString())
|
raise ValueError("Invalid value {} for indec!".format(incdec))
|
||||||
|
new_path = ''.join([pre, str(val), post])
|
||||||
|
# Make a copy of the QUrl so we don't modify the original
|
||||||
|
new_url = QUrl(url)
|
||||||
|
new_url.setPath(new_path)
|
||||||
|
return new_url
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
"""Enforce perfect coverage on some files."""
|
"""Enforce perfect coverage on some files."""
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
@ -37,6 +38,8 @@ PERFECT_FILES = [
|
|||||||
'qutebrowser/browser/tabhistory.py',
|
'qutebrowser/browser/tabhistory.py',
|
||||||
'qutebrowser/browser/http.py',
|
'qutebrowser/browser/http.py',
|
||||||
'qutebrowser/browser/rfc6266.py',
|
'qutebrowser/browser/rfc6266.py',
|
||||||
|
'qutebrowser/browser/webelem.py',
|
||||||
|
'qutebrowser/browser/network/schemehandler.py',
|
||||||
|
|
||||||
'qutebrowser/misc/readline.py',
|
'qutebrowser/misc/readline.py',
|
||||||
'qutebrowser/misc/split.py',
|
'qutebrowser/misc/split.py',
|
||||||
@ -45,10 +48,12 @@ PERFECT_FILES = [
|
|||||||
'qutebrowser/mainwindow/statusbar/percentage.py',
|
'qutebrowser/mainwindow/statusbar/percentage.py',
|
||||||
'qutebrowser/mainwindow/statusbar/progress.py',
|
'qutebrowser/mainwindow/statusbar/progress.py',
|
||||||
'qutebrowser/mainwindow/statusbar/tabindex.py',
|
'qutebrowser/mainwindow/statusbar/tabindex.py',
|
||||||
|
'qutebrowser/mainwindow/statusbar/textbase.py',
|
||||||
|
|
||||||
'qutebrowser/config/configtypes.py',
|
'qutebrowser/config/configtypes.py',
|
||||||
'qutebrowser/config/configdata.py',
|
'qutebrowser/config/configdata.py',
|
||||||
'qutebrowser/config/configexc.py',
|
'qutebrowser/config/configexc.py',
|
||||||
|
'qutebrowser/config/textwrapper.py',
|
||||||
|
|
||||||
'qutebrowser/utils/qtutils.py',
|
'qutebrowser/utils/qtutils.py',
|
||||||
'qutebrowser/utils/standarddir.py',
|
'qutebrowser/utils/standarddir.py',
|
||||||
@ -56,6 +61,8 @@ PERFECT_FILES = [
|
|||||||
'qutebrowser/utils/usertypes.py',
|
'qutebrowser/utils/usertypes.py',
|
||||||
'qutebrowser/utils/utils.py',
|
'qutebrowser/utils/utils.py',
|
||||||
'qutebrowser/utils/version.py',
|
'qutebrowser/utils/version.py',
|
||||||
|
'qutebrowser/utils/debug.py',
|
||||||
|
'qutebrowser/utils/jinja.py',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@ -67,10 +74,23 @@ def main():
|
|||||||
"""
|
"""
|
||||||
utils.change_cwd()
|
utils.change_cwd()
|
||||||
|
|
||||||
|
if sys.platform != 'linux':
|
||||||
|
print("Skipping coverage checks on non-Linux system.")
|
||||||
|
sys.exit()
|
||||||
|
elif '-k' in sys.argv[1:]:
|
||||||
|
print("Skipping coverage checks because -k is given.")
|
||||||
|
sys.exit()
|
||||||
|
elif '-m' in sys.argv[1:]:
|
||||||
|
print("Skipping coverage checks because -m is given.")
|
||||||
|
sys.exit()
|
||||||
|
elif any(arg.startswith('tests' + os.sep) for arg in sys.argv[1:]):
|
||||||
|
print("Skipping coverage checks because a filename is given.")
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
for path in PERFECT_FILES:
|
for path in PERFECT_FILES:
|
||||||
assert os.path.exists(os.path.join(*path.split('/'))), path
|
assert os.path.exists(os.path.join(*path.split('/'))), path
|
||||||
|
|
||||||
with open('coverage.xml', encoding='utf-8') as f:
|
with open('.coverage.xml', encoding='utf-8') as f:
|
||||||
tree = ElementTree.parse(f)
|
tree = ElementTree.parse(f)
|
||||||
classes = tree.getroot().findall('./packages/package/classes/class')
|
classes = tree.getroot().findall('./packages/package/classes/class')
|
||||||
|
|
||||||
@ -101,6 +121,8 @@ def main():
|
|||||||
print("{} has 100% coverage but is not in PERFECT_FILES!".format(
|
print("{} has 100% coverage but is not in PERFECT_FILES!".format(
|
||||||
filename))
|
filename))
|
||||||
|
|
||||||
|
os.remove('.coverage.xml')
|
||||||
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ def main():
|
|||||||
'redefined-outer-name',
|
'redefined-outer-name',
|
||||||
'unused-argument',
|
'unused-argument',
|
||||||
'missing-docstring',
|
'missing-docstring',
|
||||||
|
'protected-access',
|
||||||
# https://bitbucket.org/logilab/pylint/issue/511/
|
# https://bitbucket.org/logilab/pylint/issue/511/
|
||||||
'undefined-variable',
|
'undefined-variable',
|
||||||
]
|
]
|
||||||
|
@ -24,7 +24,6 @@ import logging
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from qutebrowser.browser import http
|
from qutebrowser.browser import http
|
||||||
from qutebrowser.utils import log
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_NAME = 'qutebrowser-download'
|
DEFAULT_NAME = 'qutebrowser-download'
|
||||||
@ -56,7 +55,7 @@ class HeaderChecker:
|
|||||||
"""Check if the passed header is ignored."""
|
"""Check if the passed header is ignored."""
|
||||||
reply = self.stubs.FakeNetworkReply(
|
reply = self.stubs.FakeNetworkReply(
|
||||||
headers={'Content-Disposition': header})
|
headers={'Content-Disposition': header})
|
||||||
with self.caplog.atLevel(logging.ERROR, logger=log.rfc6266.name):
|
with self.caplog.atLevel(logging.ERROR, 'rfc6266'):
|
||||||
# with self.assertLogs(log.rfc6266, logging.ERROR):
|
# with self.assertLogs(log.rfc6266, logging.ERROR):
|
||||||
cd_inline, cd_filename = http.parse_content_disposition(reply)
|
cd_inline, cd_filename = http.parse_content_disposition(reply)
|
||||||
assert cd_filename == DEFAULT_NAME
|
assert cd_filename == DEFAULT_NAME
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
"""Hypothesis tests for qutebrowser.browser.http."""
|
"""Hypothesis tests for qutebrowser.browser.http."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import hypothesis
|
import hypothesis
|
||||||
from hypothesis import strategies
|
from hypothesis import strategies
|
||||||
@ -35,10 +37,11 @@ from qutebrowser.browser import http, rfc6266
|
|||||||
'attachment; filename*={}',
|
'attachment; filename*={}',
|
||||||
])
|
])
|
||||||
@hypothesis.given(strategies.text(alphabet=[chr(x) for x in range(255)]))
|
@hypothesis.given(strategies.text(alphabet=[chr(x) for x in range(255)]))
|
||||||
def test_parse_content_disposition(template, stubs, s):
|
def test_parse_content_disposition(caplog, template, stubs, s):
|
||||||
"""Test parsing headers based on templates which hypothesis completes."""
|
"""Test parsing headers based on templates which hypothesis completes."""
|
||||||
header = template.format(s)
|
header = template.format(s)
|
||||||
reply = stubs.FakeNetworkReply(headers={'Content-Disposition': header})
|
reply = stubs.FakeNetworkReply(headers={'Content-Disposition': header})
|
||||||
|
with caplog.atLevel(logging.ERROR, 'rfc6266'):
|
||||||
http.parse_content_disposition(reply)
|
http.parse_content_disposition(reply)
|
||||||
|
|
||||||
|
|
||||||
|
35
tests/browser/network/test_schemehandler.py
Normal file
35
tests/browser/network/test_schemehandler.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# 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 browser.network.schemehandler."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from qutebrowser.browser.network import schemehandler
|
||||||
|
|
||||||
|
|
||||||
|
def test_init():
|
||||||
|
handler = schemehandler.SchemeHandler(0)
|
||||||
|
assert handler._win_id == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_request():
|
||||||
|
handler = schemehandler.SchemeHandler(0)
|
||||||
|
with pytest.raises(NotImplementedError):
|
||||||
|
handler.createRequest(None, None, None)
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for the webelement utils."""
|
"""Tests for the webelement utils."""
|
||||||
|
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
@ -334,6 +332,14 @@ class TestIsVisible:
|
|||||||
def frame(self, stubs):
|
def frame(self, stubs):
|
||||||
return stubs.FakeWebFrame(QRect(0, 0, 100, 100))
|
return stubs.FakeWebFrame(QRect(0, 0, 100, 100))
|
||||||
|
|
||||||
|
def test_invalid_frame_geometry(self, stubs):
|
||||||
|
"""Test with an invalid frame geometry."""
|
||||||
|
rect = QRect(0, 0, 0, 0)
|
||||||
|
assert not rect.isValid()
|
||||||
|
frame = stubs.FakeWebFrame(rect)
|
||||||
|
elem = get_webelem(QRect(0, 0, 10, 10), frame)
|
||||||
|
assert not elem.is_visible(frame)
|
||||||
|
|
||||||
def test_invalid_invisible(self, frame):
|
def test_invalid_invisible(self, frame):
|
||||||
"""Test elements with an invalid geometry which are invisible."""
|
"""Test elements with an invalid geometry which are invisible."""
|
||||||
elem = get_webelem(QRect(0, 0, 0, 0), frame)
|
elem = get_webelem(QRect(0, 0, 0, 0), frame)
|
||||||
@ -460,6 +466,67 @@ class TestIsVisibleIframe:
|
|||||||
assert not objects.elems[2].is_visible(objects.frame)
|
assert not objects.elems[2].is_visible(objects.frame)
|
||||||
assert objects.elems[3].is_visible(objects.frame)
|
assert objects.elems[3].is_visible(objects.frame)
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def invalid_objects(self, stubs):
|
||||||
|
"""Set up the following base situation:
|
||||||
|
|
||||||
|
0, 0 300, 0
|
||||||
|
##############################
|
||||||
|
# #
|
||||||
|
0,10 # iframe 100,10 #
|
||||||
|
#********** #
|
||||||
|
#* e * elems[0]: 10, 10 in iframe (visible)
|
||||||
|
#* * #
|
||||||
|
#* * #
|
||||||
|
#********** #
|
||||||
|
0,110 #. .100,110 #
|
||||||
|
#. . #
|
||||||
|
#. e . elems[2]: 20,150 in iframe (not visible)
|
||||||
|
#.......... #
|
||||||
|
##############################
|
||||||
|
300, 0 300, 300
|
||||||
|
|
||||||
|
Returns an Objects namedtuple with frame/iframe/elems attributes.
|
||||||
|
"""
|
||||||
|
frame = stubs.FakeWebFrame(QRect(0, 0, 300, 300))
|
||||||
|
iframe = stubs.FakeWebFrame(QRect(0, 10, 100, 100), parent=frame)
|
||||||
|
assert frame.geometry().contains(iframe.geometry())
|
||||||
|
|
||||||
|
elems = [
|
||||||
|
get_webelem(QRect(10, 10, 0, 0), iframe),
|
||||||
|
get_webelem(QRect(20, 150, 0, 0), iframe),
|
||||||
|
]
|
||||||
|
for e in elems:
|
||||||
|
assert not e.geometry().isValid()
|
||||||
|
|
||||||
|
return self.Objects(frame=frame, iframe=iframe, elems=elems)
|
||||||
|
|
||||||
|
def test_invalid_visible(self, invalid_objects):
|
||||||
|
"""Test elements with an invalid geometry which are visible.
|
||||||
|
|
||||||
|
This seems to happen sometimes in the real world, with real elements
|
||||||
|
which *are* visible, but don't have a valid geometry.
|
||||||
|
"""
|
||||||
|
elem = invalid_objects.elems[0]
|
||||||
|
assert elem.is_visible(invalid_objects.frame)
|
||||||
|
|
||||||
|
def test_invalid_invisible(self, invalid_objects):
|
||||||
|
"""Test elements with an invalid geometry which are invisible."""
|
||||||
|
assert not invalid_objects.elems[1].is_visible(invalid_objects.frame)
|
||||||
|
|
||||||
|
|
||||||
|
def test_focus_element(stubs):
|
||||||
|
"""Test getting focus element with a fake frame/element.
|
||||||
|
|
||||||
|
Testing this with a real webpage is almost impossible because the window
|
||||||
|
and the element would have focus, which is hard to achieve consistently in
|
||||||
|
a test.
|
||||||
|
"""
|
||||||
|
frame = stubs.FakeWebFrame(QRect(0, 0, 100, 100))
|
||||||
|
elem = get_webelem()
|
||||||
|
frame.focus_elem = elem._elem
|
||||||
|
assert webelem.focus_elem(frame)._elem is elem._elem
|
||||||
|
|
||||||
|
|
||||||
class TestRectOnView:
|
class TestRectOnView:
|
||||||
|
|
||||||
|
53
tests/completion/test_column_widths.py
Normal file
53
tests/completion/test_column_widths.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2015 Alexander Cogneau <alexander.cogneau@gmail.com>
|
||||||
|
#
|
||||||
|
# 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.completion.models column widths"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from qutebrowser.completion.models.base import BaseCompletionModel
|
||||||
|
from qutebrowser.completion.models.configmodel import (
|
||||||
|
SettingOptionCompletionModel, SettingSectionCompletionModel,
|
||||||
|
SettingValueCompletionModel)
|
||||||
|
from qutebrowser.completion.models.miscmodels import (
|
||||||
|
CommandCompletionModel, HelpCompletionModel, QuickmarkCompletionModel,
|
||||||
|
BookmarkCompletionModel, SessionCompletionModel)
|
||||||
|
from qutebrowser.completion.models.urlmodel import UrlCompletionModel
|
||||||
|
|
||||||
|
|
||||||
|
class TestColumnWidths:
|
||||||
|
|
||||||
|
"""Tests for the column widths of the completion models"""
|
||||||
|
|
||||||
|
CLASSES = [BaseCompletionModel, SettingOptionCompletionModel,
|
||||||
|
SettingOptionCompletionModel, SettingSectionCompletionModel,
|
||||||
|
SettingValueCompletionModel, CommandCompletionModel,
|
||||||
|
HelpCompletionModel, QuickmarkCompletionModel,
|
||||||
|
BookmarkCompletionModel, SessionCompletionModel,
|
||||||
|
UrlCompletionModel]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("model", CLASSES)
|
||||||
|
def test_list_size(self, model):
|
||||||
|
"""Test if there are 3 items in the COLUMN_WIDTHS property"""
|
||||||
|
assert len(model.COLUMN_WIDTHS) == 3
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("model", CLASSES)
|
||||||
|
def test_column_width_sum(self, model):
|
||||||
|
"""Test if the sum of the widths asserts to 100"""
|
||||||
|
assert sum(model.COLUMN_WIDTHS) == 100
|
@ -16,8 +16,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for qutebrowser.config.config."""
|
"""Tests for qutebrowser.config.config."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -234,10 +232,12 @@ class TestKeyConfigParser:
|
|||||||
assert new == new_expected
|
assert new == new_expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
class TestDefaultConfig:
|
class TestDefaultConfig:
|
||||||
|
|
||||||
"""Test validating of the default config."""
|
"""Test validating of the default config."""
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('qapp')
|
||||||
def test_default_config(self):
|
def test_default_config(self):
|
||||||
"""Test validating of the default config."""
|
"""Test validating of the default config."""
|
||||||
conf = config.ConfigManager(None, None)
|
conf = config.ConfigManager(None, None)
|
||||||
@ -254,6 +254,7 @@ class TestDefaultConfig:
|
|||||||
runner.parse(cmd, aliases=False)
|
runner.parse(cmd, aliases=False)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
class TestConfigInit:
|
class TestConfigInit:
|
||||||
|
|
||||||
"""Test initializing of the config."""
|
"""Test initializing of the config."""
|
||||||
@ -272,8 +273,10 @@ class TestConfigInit:
|
|||||||
objreg.register('save-manager', mock.MagicMock())
|
objreg.register('save-manager', mock.MagicMock())
|
||||||
args = argparse.Namespace(relaxed_config=False)
|
args = argparse.Namespace(relaxed_config=False)
|
||||||
objreg.register('args', args)
|
objreg.register('args', args)
|
||||||
|
old_standarddir_args = standarddir._args
|
||||||
yield
|
yield
|
||||||
objreg.global_registry.clear()
|
objreg.global_registry.clear()
|
||||||
|
standarddir._args = old_standarddir_args
|
||||||
|
|
||||||
def test_config_none(self, monkeypatch):
|
def test_config_none(self, monkeypatch):
|
||||||
"""Test initializing with config path set to None."""
|
"""Test initializing with config path set to None."""
|
||||||
|
@ -16,13 +16,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for qutebrowser.config.configtypes."""
|
"""Tests for qutebrowser.config.configtypes."""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import collections
|
import collections
|
||||||
import itertools
|
import itertools
|
||||||
|
import os.path
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@ -1076,6 +1075,7 @@ class TestRegexList:
|
|||||||
assert klass().transform(val) == expected
|
assert klass().transform(val) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('qapp')
|
||||||
class TestFileAndUserStyleSheet:
|
class TestFileAndUserStyleSheet:
|
||||||
|
|
||||||
"""Test File/UserStyleSheet."""
|
"""Test File/UserStyleSheet."""
|
||||||
@ -1146,6 +1146,26 @@ class TestFileAndUserStyleSheet:
|
|||||||
with pytest.raises(configexc.ValidationError):
|
with pytest.raises(configexc.ValidationError):
|
||||||
configtypes.File().validate('foobar')
|
configtypes.File().validate('foobar')
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('configtype, value, raises', [
|
||||||
|
(configtypes.File(), 'foobar', True),
|
||||||
|
(configtypes.UserStyleSheet(), 'foobar', False),
|
||||||
|
(configtypes.UserStyleSheet(), '\ud800', True),
|
||||||
|
])
|
||||||
|
def test_validate_rel_inexistent(self, os_mock, monkeypatch, configtype,
|
||||||
|
value, raises):
|
||||||
|
"""Test with a relative path and standarddir.config returning None."""
|
||||||
|
monkeypatch.setattr(
|
||||||
|
'qutebrowser.config.configtypes.standarddir.config',
|
||||||
|
lambda: 'this/does/not/exist')
|
||||||
|
os_mock.path.isabs.return_value = False
|
||||||
|
os_mock.path.isfile.side_effect = os.path.isfile
|
||||||
|
|
||||||
|
if raises:
|
||||||
|
with pytest.raises(configexc.ValidationError):
|
||||||
|
configtype.validate(value)
|
||||||
|
else:
|
||||||
|
configtype.validate(value)
|
||||||
|
|
||||||
def test_validate_expanduser(self, klass, os_mock):
|
def test_validate_expanduser(self, klass, os_mock):
|
||||||
"""Test if validate expands the user correctly."""
|
"""Test if validate expands the user correctly."""
|
||||||
os_mock.path.isfile.side_effect = (lambda path:
|
os_mock.path.isfile.side_effect = (lambda path:
|
||||||
|
38
tests/config/test_textwrapper.py
Normal file
38
tests/config/test_textwrapper.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# 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 config.textwrapper."""
|
||||||
|
|
||||||
|
from qutebrowser.config import textwrapper
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_args():
|
||||||
|
wrapper = textwrapper.TextWrapper()
|
||||||
|
assert wrapper.width == 72
|
||||||
|
assert not wrapper.replace_whitespace
|
||||||
|
assert not wrapper.break_long_words
|
||||||
|
assert not wrapper.break_on_hyphens
|
||||||
|
assert wrapper.initial_indent == '# '
|
||||||
|
assert wrapper.subsequent_indent == '# '
|
||||||
|
|
||||||
|
|
||||||
|
def test_custom_args():
|
||||||
|
wrapper = textwrapper.TextWrapper(drop_whitespace=False)
|
||||||
|
assert wrapper.width == 72
|
||||||
|
assert not wrapper.drop_whitespace
|
@ -23,22 +23,24 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import collections
|
import collections
|
||||||
import itertools
|
import itertools
|
||||||
|
import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import stubs as stubsmod
|
import stubs as stubsmod
|
||||||
|
import logfail
|
||||||
from qutebrowser.config import configexc
|
from qutebrowser.config import configexc
|
||||||
from qutebrowser.utils import objreg, usertypes
|
from qutebrowser.utils import objreg, usertypes
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session', autouse=True)
|
|
||||||
def app_and_logging(qapp):
|
|
||||||
"""Initialize a QApplication and logging.
|
|
||||||
|
|
||||||
This ensures that a QApplication is created and used by all tests.
|
@pytest.yield_fixture(scope='session', autouse=True)
|
||||||
"""
|
def fail_on_logging():
|
||||||
from log import init
|
handler = logfail.LogFailHandler()
|
||||||
init()
|
logging.getLogger().addHandler(handler)
|
||||||
|
yield
|
||||||
|
logging.getLogger().removeHandler(handler)
|
||||||
|
handler.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
@ -58,7 +60,7 @@ def unicode_encode_err():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='session')
|
@pytest.fixture(scope='session')
|
||||||
def qnam():
|
def qnam(qapp):
|
||||||
"""Session-wide QNetworkAccessManager."""
|
"""Session-wide QNetworkAccessManager."""
|
||||||
from PyQt5.QtNetwork import QNetworkAccessManager
|
from PyQt5.QtNetwork import QNetworkAccessManager
|
||||||
nam = QNetworkAccessManager()
|
nam = QNetworkAccessManager()
|
||||||
@ -118,8 +120,14 @@ def pytest_collection_modifyitems(items):
|
|||||||
http://pytest.org/latest/plugins.html
|
http://pytest.org/latest/plugins.html
|
||||||
"""
|
"""
|
||||||
for item in items:
|
for item in items:
|
||||||
if 'qtbot' in getattr(item, 'fixturenames', ()):
|
if 'qapp' in getattr(item, 'fixturenames', ()):
|
||||||
item.add_marker('gui')
|
item.add_marker('gui')
|
||||||
|
if sys.platform == 'linux' and not os.environ.get('DISPLAY', ''):
|
||||||
|
if 'CI' in os.environ:
|
||||||
|
raise Exception("No display available on CI!")
|
||||||
|
skip_marker = pytest.mark.skipif(
|
||||||
|
True, reason="No DISPLAY available")
|
||||||
|
item.add_marker(skip_marker)
|
||||||
|
|
||||||
|
|
||||||
def _generate_cmdline_tests():
|
def _generate_cmdline_tests():
|
||||||
|
@ -67,12 +67,14 @@ def caret_tester(js_tester):
|
|||||||
return CaretTester(js_tester)
|
return CaretTester(js_tester)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
def test_simple(caret_tester):
|
def test_simple(caret_tester):
|
||||||
"""Test with a simple (one-line) HTML text."""
|
"""Test with a simple (one-line) HTML text."""
|
||||||
caret_tester.js.load('position_caret/simple.html')
|
caret_tester.js.load('position_caret/simple.html')
|
||||||
caret_tester.check()
|
caret_tester.check()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
def test_scrolled_down(caret_tester):
|
def test_scrolled_down(caret_tester):
|
||||||
"""Test with multiple text blocks with the viewport scrolled down."""
|
"""Test with multiple text blocks with the viewport scrolled down."""
|
||||||
caret_tester.js.load('position_caret/scrolled_down.html')
|
caret_tester.js.load('position_caret/scrolled_down.html')
|
||||||
@ -81,6 +83,7 @@ def test_scrolled_down(caret_tester):
|
|||||||
caret_tester.check()
|
caret_tester.check()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
@pytest.mark.parametrize('style', ['visibility: hidden', 'display: none'])
|
@pytest.mark.parametrize('style', ['visibility: hidden', 'display: none'])
|
||||||
def test_invisible(caret_tester, style):
|
def test_invisible(caret_tester, style):
|
||||||
"""Test with hidden text elements."""
|
"""Test with hidden text elements."""
|
||||||
@ -88,6 +91,7 @@ def test_invisible(caret_tester, style):
|
|||||||
caret_tester.check()
|
caret_tester.check()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
def test_scrolled_down_img(caret_tester):
|
def test_scrolled_down_img(caret_tester):
|
||||||
"""Test with an image at the top with the viewport scrolled down."""
|
"""Test with an image at the top with the viewport scrolled down."""
|
||||||
caret_tester.js.load('position_caret/scrolled_down_img.html')
|
caret_tester.js.load('position_caret/scrolled_down_img.html')
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for BaseKeyParser."""
|
"""Tests for BaseKeyParser."""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
@ -29,7 +27,6 @@ from PyQt5.QtCore import Qt
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from qutebrowser.keyinput import basekeyparser
|
from qutebrowser.keyinput import basekeyparser
|
||||||
from qutebrowser.utils import log
|
|
||||||
|
|
||||||
|
|
||||||
CONFIG = {'input': {'timeout': 100}}
|
CONFIG = {'input': {'timeout': 100}}
|
||||||
@ -107,7 +104,7 @@ class TestSpecialKeys:
|
|||||||
def setup(self, caplog, fake_keyconfig):
|
def setup(self, caplog, fake_keyconfig):
|
||||||
self.kp = basekeyparser.BaseKeyParser(0)
|
self.kp = basekeyparser.BaseKeyParser(0)
|
||||||
self.kp.execute = mock.Mock()
|
self.kp.execute = mock.Mock()
|
||||||
with caplog.atLevel(logging.WARNING, log.keyboard.name):
|
with caplog.atLevel(logging.WARNING, 'keyboard'):
|
||||||
# Ignoring keychain 'ccc' in mode 'test' because keychains are not
|
# Ignoring keychain 'ccc' in mode 'test' because keychains are not
|
||||||
# supported there.
|
# supported there.
|
||||||
self.kp.read_config('test')
|
self.kp.read_config('test')
|
||||||
@ -186,7 +183,7 @@ class TestKeyChain:
|
|||||||
self.kp.execute.assert_called_once_with('0', self.kp.Type.chain, None)
|
self.kp.execute.assert_called_once_with('0', self.kp.Type.chain, None)
|
||||||
assert self.kp._keystring == ''
|
assert self.kp._keystring == ''
|
||||||
|
|
||||||
def test_ambiguous_keychain(self, fake_keyevent_factory, config_stub,
|
def test_ambiguous_keychain(self, qapp, fake_keyevent_factory, config_stub,
|
||||||
monkeypatch):
|
monkeypatch):
|
||||||
"""Test ambiguous keychain."""
|
"""Test ambiguous keychain."""
|
||||||
config_stub.data = CONFIG
|
config_stub.data = CONFIG
|
||||||
|
@ -39,8 +39,6 @@ class TestsNormalKeyParser:
|
|||||||
kp: The NormalKeyParser to be tested.
|
kp: The NormalKeyParser to be tested.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
@pytest.yield_fixture(autouse=True)
|
@pytest.yield_fixture(autouse=True)
|
||||||
def setup(self, monkeypatch, stubs, config_stub, fake_keyconfig):
|
def setup(self, monkeypatch, stubs, config_stub, fake_keyconfig):
|
||||||
"""Set up mocks and read the test config."""
|
"""Set up mocks and read the test config."""
|
||||||
|
68
tests/log.py
68
tests/log.py
@ -1,68 +0,0 @@
|
|||||||
# 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/>.
|
|
||||||
|
|
||||||
"""Logging setup for the tests."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from PyQt5.QtCore import (QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg,
|
|
||||||
qInstallMessageHandler)
|
|
||||||
|
|
||||||
|
|
||||||
def init():
|
|
||||||
"""Initialize logging for the tests."""
|
|
||||||
logging.basicConfig(format='\nLOG %(levelname)s %(name)s '
|
|
||||||
'%(module)s:%(funcName)s:%(lineno)d %(message)s',
|
|
||||||
level=logging.WARNING)
|
|
||||||
logging.captureWarnings(True)
|
|
||||||
qInstallMessageHandler(qt_message_handler)
|
|
||||||
|
|
||||||
|
|
||||||
def qt_message_handler(msg_type, context, msg):
|
|
||||||
"""Qt message handler to redirect qWarning etc. to the logging system.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
QtMsgType msg_type: The level of the message.
|
|
||||||
QMessageLogContext context: The source code location of the message.
|
|
||||||
msg: The message text.
|
|
||||||
"""
|
|
||||||
# Mapping from Qt logging levels to the matching logging module levels.
|
|
||||||
# Note we map critical to ERROR as it's actually "just" an error, and fatal
|
|
||||||
# to critical.
|
|
||||||
qt_to_logging = {
|
|
||||||
QtDebugMsg: logging.DEBUG,
|
|
||||||
QtWarningMsg: logging.WARNING,
|
|
||||||
QtCriticalMsg: logging.ERROR,
|
|
||||||
QtFatalMsg: logging.CRITICAL,
|
|
||||||
}
|
|
||||||
level = qt_to_logging[msg_type]
|
|
||||||
# There's very similar code in utils.log, but we want it duplicated here
|
|
||||||
# for the tests.
|
|
||||||
if context.function is None:
|
|
||||||
func = 'none'
|
|
||||||
else:
|
|
||||||
func = context.function
|
|
||||||
if context.category is None or context.category == 'default':
|
|
||||||
name = 'qt'
|
|
||||||
else:
|
|
||||||
name = 'qt-' + context.category
|
|
||||||
logger = logging.getLogger('qt-tests')
|
|
||||||
record = logger.makeRecord(name, level, context.file, context.line, msg,
|
|
||||||
None, None, func)
|
|
||||||
logger.handle(record)
|
|
67
tests/logfail.py
Normal file
67
tests/logfail.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
"""Logging handling for the tests."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pytest_capturelog
|
||||||
|
except ImportError:
|
||||||
|
# When using pytest for pyflakes/pep8/..., the plugin won't be available
|
||||||
|
# but conftest.py will still be loaded.
|
||||||
|
#
|
||||||
|
# However, LogFailHandler.emit will never be used in that case, so we just
|
||||||
|
# ignore the ImportError.
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LogFailHandler(logging.Handler):
|
||||||
|
|
||||||
|
"""A logging handler which makes tests fail on unexpected messages."""
|
||||||
|
|
||||||
|
def __init__(self, level=logging.NOTSET, min_level=logging.WARNING):
|
||||||
|
self._min_level = min_level
|
||||||
|
super().__init__(level)
|
||||||
|
|
||||||
|
def emit(self, record):
|
||||||
|
logger = logging.getLogger(record.name)
|
||||||
|
root_logger = logging.getLogger()
|
||||||
|
|
||||||
|
for h in root_logger.handlers:
|
||||||
|
if isinstance(h, pytest_capturelog.CaptureLogHandler):
|
||||||
|
caplog_handler = h
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# The CaptureLogHandler is not available anymore during fixture
|
||||||
|
# teardown, so we ignore logging messages emitted there..
|
||||||
|
return
|
||||||
|
|
||||||
|
if (logger.level == record.levelno or
|
||||||
|
caplog_handler.level == record.levelno):
|
||||||
|
# caplog.atLevel(...) was used with the level of this message, i.e.
|
||||||
|
# it was expected.
|
||||||
|
return
|
||||||
|
if record.levelno < self._min_level:
|
||||||
|
return
|
||||||
|
pytest.fail("Got logging message on logger {} with level {}: "
|
||||||
|
"{}!".format(record.name, record.levelname,
|
||||||
|
record.getMessage()))
|
@ -50,4 +50,54 @@ def test_elided_text(qtbot, elidemode, check):
|
|||||||
label.setText(long_string)
|
label.setText(long_string)
|
||||||
label.resize(100, 50)
|
label.resize(100, 50)
|
||||||
label.show()
|
label.show()
|
||||||
assert check(label._elided_text) # pylint: disable=protected-access
|
qtbot.waitForWindowShown(label)
|
||||||
|
assert check(label._elided_text)
|
||||||
|
|
||||||
|
|
||||||
|
def test_settext_empty(mocker, qtbot):
|
||||||
|
"""Make sure using setText('') works and runs repaint."""
|
||||||
|
label = TextBase()
|
||||||
|
qtbot.add_widget(label)
|
||||||
|
mocker.patch('qutebrowser.mainwindow.statusbar.textbase.TextBase.repaint',
|
||||||
|
autospec=True)
|
||||||
|
|
||||||
|
label.setText('')
|
||||||
|
label.repaint.assert_called_with()
|
||||||
|
|
||||||
|
|
||||||
|
def test_resize(qtbot):
|
||||||
|
"""Make sure the elided text is updated when resizing."""
|
||||||
|
label = TextBase()
|
||||||
|
qtbot.add_widget(label)
|
||||||
|
long_string = 'Hello world! ' * 20
|
||||||
|
label.setText(long_string)
|
||||||
|
|
||||||
|
label.show()
|
||||||
|
qtbot.waitForWindowShown(label)
|
||||||
|
|
||||||
|
text_1 = label._elided_text
|
||||||
|
label.resize(20, 50)
|
||||||
|
text_2 = label._elided_text
|
||||||
|
|
||||||
|
assert text_1 != text_2
|
||||||
|
|
||||||
|
|
||||||
|
def test_text_elide_none(mocker, qtbot):
|
||||||
|
"""Make sure the text doesn't get elided if it's empty."""
|
||||||
|
label = TextBase()
|
||||||
|
qtbot.add_widget(label)
|
||||||
|
label.setText('')
|
||||||
|
mocker.patch('qutebrowser.mainwindow.statusbar.textbase.TextBase.'
|
||||||
|
'fontMetrics')
|
||||||
|
label._update_elided_text(20)
|
||||||
|
|
||||||
|
assert not label.fontMetrics.called
|
||||||
|
|
||||||
|
|
||||||
|
def test_unset_text(qtbot):
|
||||||
|
"""Make sure the text is cleared properly."""
|
||||||
|
label = TextBase()
|
||||||
|
qtbot.add_widget(label)
|
||||||
|
label.setText('foo')
|
||||||
|
label.setText('')
|
||||||
|
assert not label._elided_text
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
"""Tests for qutebrowser.misc.editor."""
|
"""Tests for qutebrowser.misc.editor."""
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import logging
|
import logging
|
||||||
@ -46,7 +44,7 @@ class TestArg:
|
|||||||
stubs.fake_qprocess())
|
stubs.fake_qprocess())
|
||||||
self.editor = editor.ExternalEditor(0)
|
self.editor = editor.ExternalEditor(0)
|
||||||
yield
|
yield
|
||||||
self.editor._cleanup() # pylint: disable=protected-access
|
self.editor._cleanup()
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def stubbed_config(self, config_stub, monkeypatch):
|
def stubbed_config(self, config_stub, monkeypatch):
|
||||||
@ -229,18 +227,18 @@ class TestErrorMessage:
|
|||||||
monkeypatch.setattr('qutebrowser.misc.editor.config', config_stub)
|
monkeypatch.setattr('qutebrowser.misc.editor.config', config_stub)
|
||||||
self.editor = editor.ExternalEditor(0)
|
self.editor = editor.ExternalEditor(0)
|
||||||
yield
|
yield
|
||||||
self.editor._cleanup() # pylint: disable=protected-access
|
self.editor._cleanup()
|
||||||
|
|
||||||
def test_proc_error(self, caplog):
|
def test_proc_error(self, caplog):
|
||||||
"""Test on_proc_error."""
|
"""Test on_proc_error."""
|
||||||
self.editor.edit("")
|
self.editor.edit("")
|
||||||
with caplog.atLevel(logging.ERROR, 'message'):
|
with caplog.atLevel(logging.ERROR):
|
||||||
self.editor.on_proc_error(QProcess.Crashed)
|
self.editor.on_proc_error(QProcess.Crashed)
|
||||||
assert len(caplog.records()) == 2
|
assert len(caplog.records()) == 2
|
||||||
|
|
||||||
def test_proc_return(self, caplog):
|
def test_proc_return(self, caplog):
|
||||||
"""Test on_proc_finished with a bad exit status."""
|
"""Test on_proc_finished with a bad exit status."""
|
||||||
self.editor.edit("")
|
self.editor.edit("")
|
||||||
with caplog.atLevel(logging.ERROR, 'message'):
|
with caplog.atLevel(logging.ERROR):
|
||||||
self.editor.on_proc_closed(1, QProcess.NormalExit)
|
self.editor.on_proc_closed(1, QProcess.NormalExit)
|
||||||
assert len(caplog.records()) == 3
|
assert len(caplog.records()) == 2
|
||||||
|
@ -17,12 +17,12 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for qutebrowser.misc.guiprocess."""
|
"""Tests for qutebrowser.misc.guiprocess."""
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from PyQt5.QtCore import QProcess
|
from PyQt5.QtCore import QProcess
|
||||||
@ -97,6 +97,7 @@ def test_double_start(qtbot, proc):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.not_frozen
|
@pytest.mark.not_frozen
|
||||||
|
@pytest.mark.skipif(os.name == 'nt', reason="Test is flaky on Windows...")
|
||||||
def test_double_start_finished(qtbot, proc):
|
def test_double_start_finished(qtbot, proc):
|
||||||
"""Test starting a GUIProcess twice (with the first call finished)."""
|
"""Test starting a GUIProcess twice (with the first call finished)."""
|
||||||
with qtbot.waitSignals([proc.started, proc.finished], raising=True,
|
with qtbot.waitSignals([proc.started, proc.finished], raising=True,
|
||||||
@ -117,8 +118,11 @@ def test_cmd_args(fake_proc):
|
|||||||
assert (fake_proc.cmd, fake_proc.args) == (cmd, args)
|
assert (fake_proc.cmd, fake_proc.args) == (cmd, args)
|
||||||
|
|
||||||
|
|
||||||
def test_error(qtbot, proc):
|
# WORKAROUND for https://github.com/pytest-dev/pytest-qt/issues/67
|
||||||
|
@pytest.mark.skipif(os.name == 'nt', reason="Test is flaky on Windows...")
|
||||||
|
def test_error(qtbot, proc, caplog):
|
||||||
"""Test the process emitting an error."""
|
"""Test the process emitting an error."""
|
||||||
|
with caplog.atLevel(logging.ERROR, 'message'):
|
||||||
with qtbot.waitSignal(proc.error, raising=True):
|
with qtbot.waitSignal(proc.error, raising=True):
|
||||||
proc.start('this_does_not_exist_either', [])
|
proc.start('this_does_not_exist_either', [])
|
||||||
|
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for qutebrowser.misc.lineparser."""
|
"""Tests for qutebrowser.misc.lineparser."""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
|
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
"""Tests for qutebrowser.misc.readline."""
|
"""Tests for qutebrowser.misc.readline."""
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import inspect
|
import inspect
|
||||||
|
|
||||||
|
@ -42,7 +42,11 @@ class FakeKeyEvent:
|
|||||||
|
|
||||||
class FakeWebFrame:
|
class FakeWebFrame:
|
||||||
|
|
||||||
"""A stub for QWebFrame."""
|
"""A stub for QWebFrame.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
focus_elem: The 'focused' element.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, geometry, scroll=None, parent=None):
|
def __init__(self, geometry, scroll=None, parent=None):
|
||||||
"""Constructor.
|
"""Constructor.
|
||||||
@ -57,6 +61,16 @@ class FakeWebFrame:
|
|||||||
self.geometry = mock.Mock(return_value=geometry)
|
self.geometry = mock.Mock(return_value=geometry)
|
||||||
self.scrollPosition = mock.Mock(return_value=scroll)
|
self.scrollPosition = mock.Mock(return_value=scroll)
|
||||||
self.parentFrame = mock.Mock(return_value=parent)
|
self.parentFrame = mock.Mock(return_value=parent)
|
||||||
|
self.focus_elem = None
|
||||||
|
|
||||||
|
def findFirstElement(self, selector):
|
||||||
|
if selector == '*:focus':
|
||||||
|
if self.focus_elem is not None:
|
||||||
|
return self.focus_elem
|
||||||
|
else:
|
||||||
|
raise Exception("Trying to get focus element but it's unset!")
|
||||||
|
else:
|
||||||
|
raise Exception("Unknown selector {!r}!".format(selector))
|
||||||
|
|
||||||
|
|
||||||
class FakeChildrenFrame:
|
class FakeChildrenFrame:
|
||||||
@ -73,11 +87,14 @@ class FakeQApplication:
|
|||||||
|
|
||||||
"""Stub to insert as QApplication module."""
|
"""Stub to insert as QApplication module."""
|
||||||
|
|
||||||
def __init__(self, style=None):
|
def __init__(self, style=None, all_widgets=None):
|
||||||
self.instance = mock.Mock(return_value=self)
|
self.instance = mock.Mock(return_value=self)
|
||||||
|
|
||||||
self.style = mock.Mock(spec=QCommonStyle)
|
self.style = mock.Mock(spec=QCommonStyle)
|
||||||
self.style().metaObject().className.return_value = style
|
self.style().metaObject().className.return_value = style
|
||||||
|
|
||||||
|
self.allWidgets = lambda: all_widgets
|
||||||
|
|
||||||
|
|
||||||
class FakeUrl:
|
class FakeUrl:
|
||||||
|
|
||||||
|
65
tests/test_logfail.py
Normal file
65
tests/test_logfail.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
# 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 the LogFailHandler test helper."""
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_debug():
|
||||||
|
logging.debug('foo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_warning():
|
||||||
|
with pytest.raises(pytest.fail.Exception):
|
||||||
|
logging.warning('foo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_expected(caplog):
|
||||||
|
with caplog.atLevel(logging.ERROR):
|
||||||
|
logging.error('foo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_expected_logger(caplog):
|
||||||
|
logger = 'logfail_test_logger'
|
||||||
|
with caplog.atLevel(logging.ERROR, logger):
|
||||||
|
logging.getLogger(logger).error('foo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_expected_wrong_level(caplog):
|
||||||
|
with pytest.raises(pytest.fail.Exception):
|
||||||
|
with caplog.atLevel(logging.ERROR):
|
||||||
|
logging.critical('foo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_expected_logger_wrong_level(caplog):
|
||||||
|
logger = 'logfail_test_logger'
|
||||||
|
with pytest.raises(pytest.fail.Exception):
|
||||||
|
with caplog.atLevel(logging.ERROR, logger):
|
||||||
|
logging.getLogger(logger).critical('foo')
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_expected_wrong_logger(caplog):
|
||||||
|
logger = 'logfail_test_logger'
|
||||||
|
with pytest.raises(pytest.fail.Exception):
|
||||||
|
with caplog.atLevel(logging.ERROR, logger):
|
||||||
|
logging.error('foo')
|
@ -1,45 +0,0 @@
|
|||||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
||||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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.utils.debug.log_time."""
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
|
|
||||||
from qutebrowser.utils import debug
|
|
||||||
|
|
||||||
|
|
||||||
def test_log_time(caplog):
|
|
||||||
"""Test if log_time logs properly."""
|
|
||||||
logger_name = 'qt-tests'
|
|
||||||
|
|
||||||
with caplog.atLevel(logging.DEBUG, logger=logger_name):
|
|
||||||
with debug.log_time(logging.getLogger(logger_name), action='foobar'):
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
records = caplog.records()
|
|
||||||
assert len(records) == 1
|
|
||||||
|
|
||||||
pattern = re.compile(r'^Foobar took ([\d.]*) seconds\.$')
|
|
||||||
match = pattern.match(records[0].msg)
|
|
||||||
assert match
|
|
||||||
|
|
||||||
duration = float(match.group(1))
|
|
||||||
assert 0 < duration < 1
|
|
@ -1,64 +0,0 @@
|
|||||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
||||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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.utils.debug.qenum_key."""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from PyQt5.QtWidgets import QStyle, QFrame
|
|
||||||
|
|
||||||
from qutebrowser.utils import debug
|
|
||||||
|
|
||||||
|
|
||||||
def test_no_metaobj():
|
|
||||||
"""Test with an enum with no meta-object."""
|
|
||||||
assert not hasattr(QStyle.PrimitiveElement, 'staticMetaObject')
|
|
||||||
key = debug.qenum_key(QStyle, QStyle.PE_PanelButtonCommand)
|
|
||||||
assert key == 'PE_PanelButtonCommand'
|
|
||||||
|
|
||||||
|
|
||||||
def test_metaobj():
|
|
||||||
"""Test with an enum with meta-object."""
|
|
||||||
assert hasattr(QFrame, 'staticMetaObject')
|
|
||||||
key = debug.qenum_key(QFrame, QFrame.Sunken)
|
|
||||||
assert key == 'Sunken'
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_base():
|
|
||||||
"""Test with add_base=True."""
|
|
||||||
key = debug.qenum_key(QFrame, QFrame.Sunken, add_base=True)
|
|
||||||
assert key == 'QFrame.Sunken'
|
|
||||||
|
|
||||||
|
|
||||||
def test_int_noklass():
|
|
||||||
"""Test passing an int without explicit klass given."""
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
debug.qenum_key(QFrame, 42)
|
|
||||||
|
|
||||||
|
|
||||||
def test_int():
|
|
||||||
"""Test passing an int with explicit klass given."""
|
|
||||||
key = debug.qenum_key(QFrame, 0x0030, klass=QFrame.Shadow)
|
|
||||||
assert key == 'Sunken'
|
|
||||||
|
|
||||||
|
|
||||||
def test_unknown():
|
|
||||||
"""Test passing an unknown value."""
|
|
||||||
key = debug.qenum_key(QFrame, 0x1337, klass=QFrame.Shadow)
|
|
||||||
assert key == '0x1337'
|
|
@ -1,77 +0,0 @@
|
|||||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
||||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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.utils.debug.qflags_key.
|
|
||||||
|
|
||||||
https://github.com/The-Compiler/qutebrowser/issues/42
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
|
||||||
from qutebrowser.utils import debug
|
|
||||||
|
|
||||||
|
|
||||||
fixme = pytest.mark.xfail(reason="See issue #42", raises=AssertionError)
|
|
||||||
|
|
||||||
|
|
||||||
@fixme
|
|
||||||
def test_single():
|
|
||||||
"""Test with single value."""
|
|
||||||
flags = debug.qflags_key(Qt, Qt.AlignTop)
|
|
||||||
assert flags == 'AlignTop'
|
|
||||||
|
|
||||||
|
|
||||||
@fixme
|
|
||||||
def test_multiple():
|
|
||||||
"""Test with multiple values."""
|
|
||||||
flags = debug.qflags_key(Qt, Qt.AlignLeft | Qt.AlignTop)
|
|
||||||
assert flags == 'AlignLeft|AlignTop'
|
|
||||||
|
|
||||||
|
|
||||||
def test_combined():
|
|
||||||
"""Test with a combined value."""
|
|
||||||
flags = debug.qflags_key(Qt, Qt.AlignCenter)
|
|
||||||
assert flags == 'AlignHCenter|AlignVCenter'
|
|
||||||
|
|
||||||
|
|
||||||
@fixme
|
|
||||||
def test_add_base():
|
|
||||||
"""Test with add_base=True."""
|
|
||||||
flags = debug.qflags_key(Qt, Qt.AlignTop, add_base=True)
|
|
||||||
assert flags == 'Qt.AlignTop'
|
|
||||||
|
|
||||||
|
|
||||||
def test_int_noklass():
|
|
||||||
"""Test passing an int without explicit klass given."""
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
debug.qflags_key(Qt, 42)
|
|
||||||
|
|
||||||
|
|
||||||
@fixme
|
|
||||||
def test_int():
|
|
||||||
"""Test passing an int with explicit klass given."""
|
|
||||||
flags = debug.qflags_key(Qt, 0x0021, klass=Qt.Alignment)
|
|
||||||
assert flags == 'AlignLeft|AlignTop'
|
|
||||||
|
|
||||||
|
|
||||||
def test_unknown():
|
|
||||||
"""Test passing an unknown value."""
|
|
||||||
flags = debug.qflags_key(Qt, 0x1100, klass=Qt.Alignment)
|
|
||||||
assert flags == '0x0100|0x1000'
|
|
@ -1,52 +0,0 @@
|
|||||||
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
|
||||||
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
||||||
|
|
||||||
#
|
|
||||||
# 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/>.
|
|
||||||
|
|
||||||
"""Test signal debug output functions."""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from qutebrowser.utils import debug
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def signal(stubs):
|
|
||||||
"""Fixture to provide a faked pyqtSignal."""
|
|
||||||
return stubs.FakeSignal()
|
|
||||||
|
|
||||||
|
|
||||||
def test_signal_name(signal):
|
|
||||||
"""Test signal_name()."""
|
|
||||||
assert debug.signal_name(signal) == 'fake'
|
|
||||||
|
|
||||||
|
|
||||||
def test_dbg_signal(signal):
|
|
||||||
"""Test dbg_signal()."""
|
|
||||||
assert debug.dbg_signal(signal, [23, 42]) == 'fake(23, 42)'
|
|
||||||
|
|
||||||
|
|
||||||
def test_dbg_signal_eliding(signal):
|
|
||||||
"""Test eliding in dbg_signal()."""
|
|
||||||
dbg_signal = debug.dbg_signal(signal, ['x' * 201])
|
|
||||||
assert dbg_signal == "fake('{}\u2026)".format('x' * 198)
|
|
||||||
|
|
||||||
|
|
||||||
def test_dbg_signal_newline(signal):
|
|
||||||
"""Test dbg_signal() with a newline."""
|
|
||||||
dbg_signal = debug.dbg_signal(signal, ['foo\nbar'])
|
|
||||||
assert dbg_signal == r"fake('foo\nbar')"
|
|
252
tests/utils/test_debug.py
Normal file
252
tests/utils/test_debug.py
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
# Copyright 2014-2015 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
#
|
||||||
|
# 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.utils.debug."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
import time
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from PyQt5.QtCore import pyqtSignal, Qt, QEvent, QObject
|
||||||
|
from PyQt5.QtWidgets import QStyle, QFrame
|
||||||
|
|
||||||
|
from qutebrowser.utils import debug
|
||||||
|
|
||||||
|
|
||||||
|
@debug.log_events
|
||||||
|
class EventObject(QObject):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_events(qapp, caplog):
|
||||||
|
obj = EventObject()
|
||||||
|
qapp.postEvent(obj, QEvent(QEvent.User))
|
||||||
|
qapp.processEvents()
|
||||||
|
records = caplog.records()
|
||||||
|
assert len(records) == 1
|
||||||
|
assert records[0].msg == 'Event in test_debug.EventObject: User'
|
||||||
|
|
||||||
|
|
||||||
|
class SignalObject(QObject):
|
||||||
|
|
||||||
|
signal1 = pyqtSignal()
|
||||||
|
signal2 = pyqtSignal(str, str)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""This is not a nice thing to do, but it makes our tests easier."""
|
||||||
|
return '<repr>'
|
||||||
|
|
||||||
|
|
||||||
|
@debug.log_signals
|
||||||
|
class DecoratedSignalObject(SignalObject):
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(params=[(SignalObject, True), (DecoratedSignalObject, False)])
|
||||||
|
def signal_obj(request):
|
||||||
|
klass, wrap = request.param
|
||||||
|
obj = klass()
|
||||||
|
if wrap:
|
||||||
|
debug.log_signals(obj)
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_signals(caplog, signal_obj):
|
||||||
|
signal_obj.signal1.emit()
|
||||||
|
signal_obj.signal2.emit('foo', 'bar')
|
||||||
|
|
||||||
|
records = caplog.records()
|
||||||
|
assert len(records) == 2
|
||||||
|
assert records[0].msg == 'Signal in <repr>: signal1()'
|
||||||
|
assert records[1].msg == "Signal in <repr>: signal2('foo', 'bar')"
|
||||||
|
|
||||||
|
|
||||||
|
def test_log_time(caplog):
|
||||||
|
logger_name = 'qt-tests'
|
||||||
|
|
||||||
|
with caplog.atLevel(logging.DEBUG, logger_name):
|
||||||
|
with debug.log_time(logging.getLogger(logger_name), action='foobar'):
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
records = caplog.records()
|
||||||
|
assert len(records) == 1
|
||||||
|
|
||||||
|
pattern = re.compile(r'^Foobar took ([\d.]*) seconds\.$')
|
||||||
|
match = pattern.match(records[0].msg)
|
||||||
|
assert match
|
||||||
|
|
||||||
|
duration = float(match.group(1))
|
||||||
|
assert 0 < duration < 1
|
||||||
|
|
||||||
|
|
||||||
|
class TestQEnumKey:
|
||||||
|
|
||||||
|
def test_metaobj(self):
|
||||||
|
"""Make sure the classes we use in the tests have a metaobj or not.
|
||||||
|
|
||||||
|
If Qt/PyQt even changes and our tests wouldn't test the full
|
||||||
|
functionality of qenum_key because of that, this test will tell us.
|
||||||
|
"""
|
||||||
|
assert not hasattr(QStyle.PrimitiveElement, 'staticMetaObject')
|
||||||
|
assert hasattr(QFrame, 'staticMetaObject')
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('base, value, klass, expected', [
|
||||||
|
(QStyle, QStyle.PE_PanelButtonCommand, None, 'PE_PanelButtonCommand'),
|
||||||
|
(QFrame, QFrame.Sunken, None, 'Sunken'),
|
||||||
|
(QFrame, 0x0030, QFrame.Shadow, 'Sunken'),
|
||||||
|
(QFrame, 0x1337, QFrame.Shadow, '0x1337'),
|
||||||
|
])
|
||||||
|
def test_qenum_key(self, base, value, klass, expected):
|
||||||
|
key = debug.qenum_key(base, value, klass=klass)
|
||||||
|
assert key == expected
|
||||||
|
|
||||||
|
def test_add_base(self):
|
||||||
|
key = debug.qenum_key(QFrame, QFrame.Sunken, add_base=True)
|
||||||
|
assert key == 'QFrame.Sunken'
|
||||||
|
|
||||||
|
def test_int_noklass(self):
|
||||||
|
"""Test passing an int without explicit klass given."""
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
debug.qenum_key(QFrame, 42)
|
||||||
|
|
||||||
|
|
||||||
|
class TestQFlagsKey:
|
||||||
|
|
||||||
|
"""Tests for qutebrowser.utils.debug.qflags_key.
|
||||||
|
|
||||||
|
https://github.com/The-Compiler/qutebrowser/issues/42
|
||||||
|
"""
|
||||||
|
|
||||||
|
fixme = pytest.mark.xfail(reason="See issue #42", raises=AssertionError)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('base, value, klass, expected', [
|
||||||
|
fixme((Qt, Qt.AlignTop, None, 'AlignTop')),
|
||||||
|
fixme((Qt, Qt.AlignLeft | Qt.AlignTop, None, 'AlignLeft|AlignTop')),
|
||||||
|
(Qt, Qt.AlignCenter, None, 'AlignHCenter|AlignVCenter'),
|
||||||
|
fixme((Qt, 0x0021, Qt.Alignment, 'AlignLeft|AlignTop')),
|
||||||
|
(Qt, 0x1100, Qt.Alignment, '0x0100|0x1000'),
|
||||||
|
])
|
||||||
|
def test_qflags_key(self, base, value, klass, expected):
|
||||||
|
flags = debug.qflags_key(base, value, klass=klass)
|
||||||
|
assert flags == expected
|
||||||
|
|
||||||
|
@fixme
|
||||||
|
def test_add_base(self):
|
||||||
|
"""Test with add_base=True."""
|
||||||
|
flags = debug.qflags_key(Qt, Qt.AlignTop, add_base=True)
|
||||||
|
assert flags == 'Qt.AlignTop'
|
||||||
|
|
||||||
|
def test_int_noklass(self):
|
||||||
|
"""Test passing an int without explicit klass given."""
|
||||||
|
with pytest.raises(TypeError):
|
||||||
|
debug.qflags_key(Qt, 42)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('signal, expected', [
|
||||||
|
(SignalObject().signal1, 'signal1'),
|
||||||
|
(SignalObject().signal2, 'signal2'),
|
||||||
|
])
|
||||||
|
def test_signal_name(signal, expected):
|
||||||
|
assert debug.signal_name(signal) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('args, kwargs, expected', [
|
||||||
|
([], {}, ''),
|
||||||
|
(None, None, ''),
|
||||||
|
(['foo'], None, "'foo'"),
|
||||||
|
(['foo', 'bar'], None, "'foo', 'bar'"),
|
||||||
|
(None, {'foo': 'bar'}, "foo='bar'"),
|
||||||
|
(['foo', 'bar'], {'baz': 'fish'}, "'foo', 'bar', baz='fish'"),
|
||||||
|
(['x' * 300], None, "'{}".format('x' * 198 + '…')),
|
||||||
|
])
|
||||||
|
def test_format_args(args, kwargs, expected):
|
||||||
|
assert debug.format_args(args, kwargs) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def func():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('func, args, kwargs, full, expected', [
|
||||||
|
(func, None, None, False, 'func()'),
|
||||||
|
(func, [1, 2], None, False, 'func(1, 2)'),
|
||||||
|
(func, [1, 2], None, True, 'test_debug.func(1, 2)'),
|
||||||
|
(func, [1, 2], {'foo': 3}, False, 'func(1, 2, foo=3)'),
|
||||||
|
])
|
||||||
|
def test_format_call(func, args, kwargs, full, expected):
|
||||||
|
assert debug.format_call(func, args, kwargs, full) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('args, expected', [
|
||||||
|
([23, 42], 'fake(23, 42)'),
|
||||||
|
(['x' * 201], "fake('{}\u2026)".format('x' * 198)),
|
||||||
|
(['foo\nbar'], r"fake('foo\nbar')"),
|
||||||
|
])
|
||||||
|
def test_dbg_signal(stubs, args, expected):
|
||||||
|
assert debug.dbg_signal(stubs.FakeSignal(), args) == expected
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetAllObjects:
|
||||||
|
|
||||||
|
class Object(QObject):
|
||||||
|
|
||||||
|
def __init__(self, name, parent=None):
|
||||||
|
self._name = name
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<{}>'.format(self._name)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_all_objects(self, stubs, monkeypatch):
|
||||||
|
# pylint: disable=unused-variable
|
||||||
|
widgets = [self.Object('Widget 1'), self.Object('Widget 2')]
|
||||||
|
app = stubs.FakeQApplication(all_widgets=widgets)
|
||||||
|
monkeypatch.setattr(debug, 'QApplication', app)
|
||||||
|
|
||||||
|
root = QObject()
|
||||||
|
o1 = self.Object('Object 1', root)
|
||||||
|
o2 = self.Object('Object 2', o1)
|
||||||
|
o3 = self.Object('Object 3', root)
|
||||||
|
|
||||||
|
expected = textwrap.dedent("""
|
||||||
|
Qt widgets - 2 objects:
|
||||||
|
<Widget 1>
|
||||||
|
<Widget 2>
|
||||||
|
|
||||||
|
Qt objects - 3 objects:
|
||||||
|
<Object 1>
|
||||||
|
<Object 2>
|
||||||
|
<Object 3>
|
||||||
|
|
||||||
|
global object registry - 0 objects:
|
||||||
|
""").rstrip('\n')
|
||||||
|
|
||||||
|
assert debug.get_all_objects(start_obj=root) == expected
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('qapp')
|
||||||
|
def test_get_all_objects_qapp(self):
|
||||||
|
objects = debug.get_all_objects()
|
||||||
|
event_dispatcher = '<PyQt5.QtCore.QAbstractEventDispatcher object at'
|
||||||
|
session_manager = '<PyQt5.QtGui.QSessionManager object at'
|
||||||
|
assert event_dispatcher in objects or session_manager in objects
|
@ -22,6 +22,7 @@
|
|||||||
import os.path
|
import os.path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import jinja2
|
||||||
|
|
||||||
from qutebrowser.utils import jinja
|
from qutebrowser.utils import jinja
|
||||||
|
|
||||||
@ -34,7 +35,7 @@ def patch_read_file(monkeypatch):
|
|||||||
if path == os.path.join('html', 'test.html'):
|
if path == os.path.join('html', 'test.html'):
|
||||||
return """Hello {{var}}"""
|
return """Hello {{var}}"""
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid path {}!".format(path))
|
raise IOError("Invalid path {}!".format(path))
|
||||||
|
|
||||||
monkeypatch.setattr('qutebrowser.utils.jinja.utils.read_file', _read_file)
|
monkeypatch.setattr('qutebrowser.utils.jinja.utils.read_file', _read_file)
|
||||||
|
|
||||||
@ -47,6 +48,13 @@ def test_simple_template():
|
|||||||
assert data == "Hello World"
|
assert data == "Hello World"
|
||||||
|
|
||||||
|
|
||||||
|
def test_not_found():
|
||||||
|
"""Test with a template which does not exist."""
|
||||||
|
with pytest.raises(jinja2.TemplateNotFound) as excinfo:
|
||||||
|
jinja.env.get_template('does_not_exist.html')
|
||||||
|
assert str(excinfo.value) == 'does_not_exist.html'
|
||||||
|
|
||||||
|
|
||||||
def test_utf8():
|
def test_utf8():
|
||||||
"""Test rendering with an UTF8 template.
|
"""Test rendering with an UTF8 template.
|
||||||
|
|
||||||
@ -59,3 +67,16 @@ def test_utf8():
|
|||||||
# https://bitbucket.org/logilab/pylint/issue/490/
|
# https://bitbucket.org/logilab/pylint/issue/490/
|
||||||
data = template.render(var='\u2603') # pylint: disable=no-member
|
data = template.render(var='\u2603') # pylint: disable=no-member
|
||||||
assert data == "Hello \u2603"
|
assert data == "Hello \u2603"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('name, expected', [
|
||||||
|
(None, False),
|
||||||
|
('foo', False),
|
||||||
|
('foo.html', True),
|
||||||
|
('foo.htm', True),
|
||||||
|
('foo.xml', True),
|
||||||
|
('blah/bar/foo.html', True),
|
||||||
|
('foo.bar.html', True),
|
||||||
|
])
|
||||||
|
def test_autoescape(name, expected):
|
||||||
|
assert jinja._guess_autoescape(name) == expected
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for qutebrowser.utils.log."""
|
"""Tests for qutebrowser.utils.log."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -204,6 +202,7 @@ class TestRAMHandler:
|
|||||||
assert handler.dump_log() == "Two\nThree"
|
assert handler.dump_log() == "Two\nThree"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.integration
|
||||||
class TestInitLog:
|
class TestInitLog:
|
||||||
|
|
||||||
"""Tests for init_log."""
|
"""Tests for init_log."""
|
||||||
@ -238,8 +237,8 @@ class TestHideQtWarning:
|
|||||||
|
|
||||||
def test_unfiltered(self, logger, caplog):
|
def test_unfiltered(self, logger, caplog):
|
||||||
"""Test a message which is not filtered."""
|
"""Test a message which is not filtered."""
|
||||||
with log.hide_qt_warning("World", logger='qt-tests'):
|
with log.hide_qt_warning("World", 'qt-tests'):
|
||||||
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
with caplog.atLevel(logging.WARNING, 'qt-tests'):
|
||||||
logger.warning("Hello World")
|
logger.warning("Hello World")
|
||||||
assert len(caplog.records()) == 1
|
assert len(caplog.records()) == 1
|
||||||
record = caplog.records()[0]
|
record = caplog.records()[0]
|
||||||
@ -248,21 +247,21 @@ class TestHideQtWarning:
|
|||||||
|
|
||||||
def test_filtered_exact(self, logger, caplog):
|
def test_filtered_exact(self, logger, caplog):
|
||||||
"""Test a message which is filtered (exact match)."""
|
"""Test a message which is filtered (exact match)."""
|
||||||
with log.hide_qt_warning("Hello", logger='qt-tests'):
|
with log.hide_qt_warning("Hello", 'qt-tests'):
|
||||||
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
with caplog.atLevel(logging.WARNING, 'qt-tests'):
|
||||||
logger.warning("Hello")
|
logger.warning("Hello")
|
||||||
assert not caplog.records()
|
assert not caplog.records()
|
||||||
|
|
||||||
def test_filtered_start(self, logger, caplog):
|
def test_filtered_start(self, logger, caplog):
|
||||||
"""Test a message which is filtered (match at line start)."""
|
"""Test a message which is filtered (match at line start)."""
|
||||||
with log.hide_qt_warning("Hello", logger='qt-tests'):
|
with log.hide_qt_warning("Hello", 'qt-tests'):
|
||||||
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
with caplog.atLevel(logging.WARNING, 'qt-tests'):
|
||||||
logger.warning("Hello World")
|
logger.warning("Hello World")
|
||||||
assert not caplog.records()
|
assert not caplog.records()
|
||||||
|
|
||||||
def test_filtered_whitespace(self, logger, caplog):
|
def test_filtered_whitespace(self, logger, caplog):
|
||||||
"""Test a message which is filtered (match with whitespace)."""
|
"""Test a message which is filtered (match with whitespace)."""
|
||||||
with log.hide_qt_warning("Hello", logger='qt-tests'):
|
with log.hide_qt_warning("Hello", 'qt-tests'):
|
||||||
with caplog.atLevel(logging.WARNING, logger='qt-tests'):
|
with caplog.atLevel(logging.WARNING, 'qt-tests'):
|
||||||
logger.warning(" Hello World ")
|
logger.warning(" Hello World ")
|
||||||
assert not caplog.records()
|
assert not caplog.records()
|
||||||
|
@ -35,7 +35,6 @@ import unittest
|
|||||||
import unittest.mock
|
import unittest.mock
|
||||||
from PyQt5.QtCore import (QDataStream, QPoint, QUrl, QByteArray, QIODevice,
|
from PyQt5.QtCore import (QDataStream, QPoint, QUrl, QByteArray, QIODevice,
|
||||||
QTimer, QBuffer, QFile, QProcess, QFileDevice)
|
QTimer, QBuffer, QFile, QProcess, QFileDevice)
|
||||||
from PyQt5.QtWidgets import QApplication
|
|
||||||
|
|
||||||
from qutebrowser import qutebrowser
|
from qutebrowser import qutebrowser
|
||||||
from qutebrowser.utils import qtutils
|
from qutebrowser.utils import qtutils
|
||||||
@ -505,19 +504,18 @@ class TestSavefileOpen:
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('orgname, expected', [(None, ''), ('test', 'test')])
|
@pytest.mark.parametrize('orgname, expected', [(None, ''), ('test', 'test')])
|
||||||
def test_unset_organization(orgname, expected):
|
def test_unset_organization(qapp, orgname, expected):
|
||||||
"""Test unset_organization.
|
"""Test unset_organization.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
orgname: The organizationName to set initially.
|
orgname: The organizationName to set initially.
|
||||||
expected: The organizationName which is expected when reading back.
|
expected: The organizationName which is expected when reading back.
|
||||||
"""
|
"""
|
||||||
app = QApplication.instance()
|
qapp.setOrganizationName(orgname)
|
||||||
app.setOrganizationName(orgname)
|
assert qapp.organizationName() == expected # sanity check
|
||||||
assert app.organizationName() == expected # sanity check
|
|
||||||
with qtutils.unset_organization():
|
with qtutils.unset_organization():
|
||||||
assert app.organizationName() == ''
|
assert qapp.organizationName() == ''
|
||||||
assert app.organizationName() == expected
|
assert qapp.organizationName() == expected
|
||||||
|
|
||||||
|
|
||||||
if test_file is not None:
|
if test_file is not None:
|
||||||
@ -921,6 +919,7 @@ class TestPyQIODevice:
|
|||||||
assert str(excinfo.value) == 'Reading failed'
|
assert str(excinfo.value) == 'Reading failed'
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures('qapp')
|
||||||
class TestEventLoop:
|
class TestEventLoop:
|
||||||
|
|
||||||
"""Tests for EventLoop.
|
"""Tests for EventLoop.
|
||||||
@ -929,8 +928,6 @@ class TestEventLoop:
|
|||||||
loop: The EventLoop we're testing.
|
loop: The EventLoop we're testing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
def _assert_executing(self):
|
def _assert_executing(self):
|
||||||
"""Slot which gets called from timers to be sure the loop runs."""
|
"""Slot which gets called from timers to be sure the loop runs."""
|
||||||
assert self.loop._executing
|
assert self.loop._executing
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for qutebrowser.utils.standarddir."""
|
"""Tests for qutebrowser.utils.standarddir."""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
@ -29,23 +27,22 @@ import logging
|
|||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from PyQt5.QtCore import QStandardPaths
|
from PyQt5.QtCore import QStandardPaths
|
||||||
from PyQt5.QtWidgets import QApplication
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from qutebrowser.utils import standarddir
|
from qutebrowser.utils import standarddir
|
||||||
|
|
||||||
|
|
||||||
@pytest.yield_fixture(autouse=True)
|
@pytest.yield_fixture(autouse=True)
|
||||||
def change_qapp_name():
|
def change_qapp_name(qapp):
|
||||||
"""Change the name of the QApplication instance.
|
"""Change the name of the QApplication instance.
|
||||||
|
|
||||||
This changes the applicationName for all tests in this module to
|
This changes the applicationName for all tests in this module to
|
||||||
"qutebrowser_test".
|
"qutebrowser_test".
|
||||||
"""
|
"""
|
||||||
old_name = QApplication.instance().applicationName()
|
old_name = qapp.applicationName()
|
||||||
QApplication.instance().setApplicationName('qutebrowser_test')
|
qapp.setApplicationName('qutebrowser_test')
|
||||||
yield
|
yield
|
||||||
QApplication.instance().setApplicationName(old_name)
|
qapp.setApplicationName(old_name)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -271,7 +268,7 @@ class TestInitCacheDirTag:
|
|||||||
monkeypatch.setattr('qutebrowser.utils.standarddir.cache',
|
monkeypatch.setattr('qutebrowser.utils.standarddir.cache',
|
||||||
lambda: str(tmpdir))
|
lambda: str(tmpdir))
|
||||||
mocker.patch('builtins.open', side_effect=OSError)
|
mocker.patch('builtins.open', side_effect=OSError)
|
||||||
with caplog.atLevel(logging.ERROR, 'misc'):
|
with caplog.atLevel(logging.ERROR, 'init'):
|
||||||
standarddir._init_cachedir_tag()
|
standarddir._init_cachedir_tag()
|
||||||
assert len(caplog.records()) == 1
|
assert len(caplog.records()) == 1
|
||||||
assert caplog.records()[0].message == 'Failed to create CACHEDIR.TAG'
|
assert caplog.records()[0].message == 'Failed to create CACHEDIR.TAG'
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for qutebrowser.utils.urlutils."""
|
"""Tests for qutebrowser.utils.urlutils."""
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
@ -219,7 +217,7 @@ class TestFuzzyUrl:
|
|||||||
|
|
||||||
@pytest.mark.parametrize('do_search, exception', [
|
@pytest.mark.parametrize('do_search, exception', [
|
||||||
(True, qtutils.QtValueError),
|
(True, qtutils.QtValueError),
|
||||||
(False, urlutils.FuzzyUrlError),
|
(False, urlutils.InvalidUrlError),
|
||||||
])
|
])
|
||||||
def test_invalid_url(self, do_search, exception, is_url_mock, monkeypatch):
|
def test_invalid_url(self, do_search, exception, is_url_mock, monkeypatch):
|
||||||
"""Test with an invalid URL."""
|
"""Test with an invalid URL."""
|
||||||
@ -469,35 +467,41 @@ def test_host_tuple(qurl, tpl):
|
|||||||
assert urlutils.host_tuple(qurl) == tpl
|
assert urlutils.host_tuple(qurl) == tpl
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('url, raising, has_err_string', [
|
class TestInvalidUrlError:
|
||||||
(None, False, False),
|
|
||||||
|
@pytest.mark.parametrize('url, raising, has_err_string', [
|
||||||
(QUrl(), False, False),
|
(QUrl(), False, False),
|
||||||
(QUrl('http://www.example.com/'), True, False),
|
(QUrl('http://www.example.com/'), True, False),
|
||||||
(QUrl('://'), False, True),
|
(QUrl('://'), False, True),
|
||||||
])
|
])
|
||||||
def test_fuzzy_url_error(url, raising, has_err_string):
|
def test_invalid_url_error(self, url, raising, has_err_string):
|
||||||
"""Test FuzzyUrlError.
|
"""Test InvalidUrlError.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
url: The URL to pass to FuzzyUrlError.
|
url: The URL to pass to InvalidUrlError.
|
||||||
raising; True if the FuzzyUrlError should raise itself.
|
raising; True if the InvalidUrlError should raise itself.
|
||||||
has_err_string: Whether the QUrl is expected to have errorString set.
|
has_err_string: Whether the QUrl is expected to have errorString
|
||||||
|
set.
|
||||||
"""
|
"""
|
||||||
if raising:
|
if raising:
|
||||||
expected_exc = ValueError
|
expected_exc = ValueError
|
||||||
else:
|
else:
|
||||||
expected_exc = urlutils.FuzzyUrlError
|
expected_exc = urlutils.InvalidUrlError
|
||||||
|
|
||||||
with pytest.raises(expected_exc) as excinfo:
|
with pytest.raises(expected_exc) as excinfo:
|
||||||
raise urlutils.FuzzyUrlError("Error message", url)
|
raise urlutils.InvalidUrlError(url)
|
||||||
|
|
||||||
if not raising:
|
if not raising:
|
||||||
|
expected_text = "Invalid URL"
|
||||||
if has_err_string:
|
if has_err_string:
|
||||||
expected_text = "Error message: " + url.errorString()
|
expected_text += " - " + url.errorString()
|
||||||
else:
|
|
||||||
expected_text = "Error message"
|
|
||||||
assert str(excinfo.value) == expected_text
|
assert str(excinfo.value) == expected_text
|
||||||
|
|
||||||
|
def test_value_error_subclass(self):
|
||||||
|
"""Make sure InvalidUrlError is a ValueError subclass."""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
raise urlutils.InvalidUrlError(QUrl())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('are_same, url1, url2', [
|
@pytest.mark.parametrize('are_same, url1, url2', [
|
||||||
(True, 'http://example.com', 'http://www.example.com'),
|
(True, 'http://example.com', 'http://www.example.com'),
|
||||||
@ -522,5 +526,72 @@ def test_same_domain(are_same, url1, url2):
|
|||||||
])
|
])
|
||||||
def test_same_domain_invalid_url(url1, url2):
|
def test_same_domain_invalid_url(url1, url2):
|
||||||
"""Test same_domain with invalid URLs."""
|
"""Test same_domain with invalid URLs."""
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(urlutils.InvalidUrlError):
|
||||||
urlutils.same_domain(QUrl(url1), QUrl(url2))
|
urlutils.same_domain(QUrl(url1), QUrl(url2))
|
||||||
|
|
||||||
|
class TestIncDecNumber:
|
||||||
|
|
||||||
|
"""Tests for urlutils.incdec_number()."""
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('url, incdec, output', [
|
||||||
|
("http://example.com/index1.html", "increment", "http://example.com/index2.html"),
|
||||||
|
("http://foo.bar/folder_1/image_2", "increment", "http://foo.bar/folder_1/image_3"),
|
||||||
|
("http://bbc.c0.uk:80/story_1", "increment", "http://bbc.c0.uk:80/story_2"),
|
||||||
|
("http://mydomain.tld/1_%C3%A4", "increment", "http://mydomain.tld/2_%C3%A4"),
|
||||||
|
("http://example.com/site/5#5", "increment", "http://example.com/site/6#5"),
|
||||||
|
|
||||||
|
("http://example.com/index10.html", "decrement", "http://example.com/index9.html"),
|
||||||
|
("http://foo.bar/folder_1/image_3", "decrement", "http://foo.bar/folder_1/image_2"),
|
||||||
|
("http://bbc.c0.uk:80/story_1", "decrement", "http://bbc.c0.uk:80/story_0"),
|
||||||
|
("http://mydomain.tld/2_%C3%A4", "decrement", "http://mydomain.tld/1_%C3%A4"),
|
||||||
|
("http://example.com/site/5#5", "decrement", "http://example.com/site/4#5"),
|
||||||
|
])
|
||||||
|
def test_incdec_number(self, url, incdec, output):
|
||||||
|
"""Test incdec_number with valid URLs."""
|
||||||
|
new_url = urlutils.incdec_number(QUrl(url), incdec)
|
||||||
|
assert new_url == QUrl(output)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('url', [
|
||||||
|
"http://example.com/long/path/but/no/number",
|
||||||
|
"http://ex4mple.com/number/in/hostname",
|
||||||
|
"http://example.com:42/number/in/port",
|
||||||
|
"http://www2.example.com/number/in/subdomain",
|
||||||
|
"http://example.com/%C3%B6/urlencoded/data",
|
||||||
|
"http://example.com/number/in/anchor#5",
|
||||||
|
"http://www2.ex4mple.com:42/all/of/the/%C3%A4bove#5",
|
||||||
|
])
|
||||||
|
def test_no_number(self, url):
|
||||||
|
"""Test incdec_number with URLs that don't contain a number."""
|
||||||
|
with pytest.raises(urlutils.IncDecError):
|
||||||
|
urlutils.incdec_number(QUrl(url), "increment")
|
||||||
|
|
||||||
|
def test_number_below_0(self):
|
||||||
|
"""Test incdec_number with a number that would be below zero
|
||||||
|
after decrementing."""
|
||||||
|
with pytest.raises(urlutils.IncDecError):
|
||||||
|
urlutils.incdec_number(QUrl('http://example.com/page_0.html'),
|
||||||
|
'decrement')
|
||||||
|
|
||||||
|
def test_invalid_url(self):
|
||||||
|
"""Test if incdec_number rejects an invalid URL."""
|
||||||
|
with pytest.raises(urlutils.InvalidUrlError):
|
||||||
|
urlutils.incdec_number(QUrl(""), "increment")
|
||||||
|
|
||||||
|
def test_wrong_mode(self):
|
||||||
|
"""Test if incdec_number rejects a wrong parameter for the incdec
|
||||||
|
argument."""
|
||||||
|
valid_url = QUrl("http://example.com/0")
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
urlutils.incdec_number(valid_url, "foobar")
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("url, msg, expected_str", [
|
||||||
|
("http://example.com", "Invalid", "Invalid: http://example.com"),
|
||||||
|
])
|
||||||
|
def test_incdec_error(self, url, msg, expected_str):
|
||||||
|
"""Test IncDecError."""
|
||||||
|
url = QUrl(url)
|
||||||
|
with pytest.raises(urlutils.IncDecError) as excinfo:
|
||||||
|
raise urlutils.IncDecError(msg, url)
|
||||||
|
|
||||||
|
assert excinfo.value.url == url
|
||||||
|
assert str(excinfo.value) == expected_str
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for qutebrowser.utils.version."""
|
"""Tests for qutebrowser.utils.version."""
|
||||||
|
|
||||||
import io
|
import io
|
||||||
@ -105,11 +103,12 @@ class TestGitStr:
|
|||||||
commit_file_mock.return_value = 'deadbeef'
|
commit_file_mock.return_value = 'deadbeef'
|
||||||
assert version._git_str() == 'deadbeef'
|
assert version._git_str() == 'deadbeef'
|
||||||
|
|
||||||
def test_frozen_oserror(self, commit_file_mock, monkeypatch):
|
def test_frozen_oserror(self, caplog, commit_file_mock, monkeypatch):
|
||||||
"""Test with sys.frozen=True and OSError when reading git-commit-id."""
|
"""Test with sys.frozen=True and OSError when reading git-commit-id."""
|
||||||
monkeypatch.setattr(qutebrowser.utils.version.sys, 'frozen', True,
|
monkeypatch.setattr(qutebrowser.utils.version.sys, 'frozen', True,
|
||||||
raising=False)
|
raising=False)
|
||||||
commit_file_mock.side_effect = OSError
|
commit_file_mock.side_effect = OSError
|
||||||
|
with caplog.atLevel(logging.ERROR, 'misc'):
|
||||||
assert version._git_str() is None
|
assert version._git_str() is None
|
||||||
|
|
||||||
@pytest.mark.not_frozen
|
@pytest.mark.not_frozen
|
||||||
@ -130,12 +129,14 @@ class TestGitStr:
|
|||||||
commit_file_mock.return_value = '1b4d1dea'
|
commit_file_mock.return_value = '1b4d1dea'
|
||||||
assert version._git_str() == '1b4d1dea'
|
assert version._git_str() == '1b4d1dea'
|
||||||
|
|
||||||
def test_normal_path_oserror(self, mocker, git_str_subprocess_fake):
|
def test_normal_path_oserror(self, mocker, git_str_subprocess_fake,
|
||||||
|
caplog):
|
||||||
"""Test with things raising OSError."""
|
"""Test with things raising OSError."""
|
||||||
m = mocker.patch('qutebrowser.utils.version.os')
|
m = mocker.patch('qutebrowser.utils.version.os')
|
||||||
m.path.join.side_effect = OSError
|
m.path.join.side_effect = OSError
|
||||||
mocker.patch('qutebrowser.utils.version.utils.read_file',
|
mocker.patch('qutebrowser.utils.version.utils.read_file',
|
||||||
side_effect=OSError)
|
side_effect=OSError)
|
||||||
|
with caplog.atLevel(logging.ERROR, 'misc'):
|
||||||
assert version._git_str() is None
|
assert version._git_str() is None
|
||||||
|
|
||||||
@pytest.mark.not_frozen
|
@pytest.mark.not_frozen
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for the NeighborList class."""
|
"""Tests for the NeighborList class."""
|
||||||
|
|
||||||
from qutebrowser.utils import usertypes
|
from qutebrowser.utils import usertypes
|
||||||
|
@ -86,6 +86,6 @@ def test_abort_typeerror(question, qtbot, mocker, caplog):
|
|||||||
"""Test Question.abort() with .emit() raising a TypeError."""
|
"""Test Question.abort() with .emit() raising a TypeError."""
|
||||||
signal_mock = mocker.patch('qutebrowser.utils.usertypes.Question.aborted')
|
signal_mock = mocker.patch('qutebrowser.utils.usertypes.Question.aborted')
|
||||||
signal_mock.emit.side_effect = TypeError
|
signal_mock.emit.side_effect = TypeError
|
||||||
with caplog.atLevel(logging.ERROR):
|
with caplog.atLevel(logging.ERROR, 'misc'):
|
||||||
question.abort()
|
question.abort()
|
||||||
assert caplog.records()[0].message == 'Error while aborting question'
|
assert caplog.records()[0].message == 'Error while aborting question'
|
||||||
|
@ -17,8 +17,6 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
|
||||||
|
|
||||||
"""Tests for Timer."""
|
"""Tests for Timer."""
|
||||||
|
|
||||||
from qutebrowser.utils import usertypes
|
from qutebrowser.utils import usertypes
|
||||||
@ -74,13 +72,13 @@ def test_start_overflow():
|
|||||||
def test_timeout_start(qtbot):
|
def test_timeout_start(qtbot):
|
||||||
"""Make sure the timer works with start()."""
|
"""Make sure the timer works with start()."""
|
||||||
t = usertypes.Timer()
|
t = usertypes.Timer()
|
||||||
with qtbot.waitSignal(t.timeout, raising=True):
|
with qtbot.waitSignal(t.timeout, timeout=3000, raising=True):
|
||||||
t.start(200)
|
t.start(200)
|
||||||
|
|
||||||
|
|
||||||
def test_timeout_set_interval(qtbot):
|
def test_timeout_set_interval(qtbot):
|
||||||
"""Make sure the timer works with setInterval()."""
|
"""Make sure the timer works with setInterval()."""
|
||||||
t = usertypes.Timer()
|
t = usertypes.Timer()
|
||||||
with qtbot.waitSignal(t.timeout, raising=True):
|
with qtbot.waitSignal(t.timeout, timeout=3000, raising=True):
|
||||||
t.setInterval(200)
|
t.setInterval(200)
|
||||||
t.start()
|
t.start()
|
||||||
|
91
tox.ini
91
tox.ini
@ -4,23 +4,14 @@
|
|||||||
# and then run "tox" from this directory.
|
# and then run "tox" from this directory.
|
||||||
|
|
||||||
[tox]
|
[tox]
|
||||||
envlist = smoke,unittests,misc,pep257,pyflakes,pep8,mccabe,pylint,pyroma,check-manifest
|
envlist = py34,misc,pep257,pyflakes,pep8,mccabe,pylint,pyroma,check-manifest
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
passenv = PYTHON
|
|
||||||
basepython = python3
|
|
||||||
|
|
||||||
[testenv:mkvenv]
|
|
||||||
commands = {envpython} scripts/link_pyqt.py --tox {envdir}
|
|
||||||
envdir = {toxinidir}/.venv
|
|
||||||
usedevelop = true
|
|
||||||
|
|
||||||
[testenv:unittests]
|
|
||||||
# https://bitbucket.org/hpk42/tox/issue/246/ - only needed for Windows though
|
# https://bitbucket.org/hpk42/tox/issue/246/ - only needed for Windows though
|
||||||
setenv =
|
setenv =
|
||||||
QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/platforms
|
QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/platforms
|
||||||
PYTEST_QT_API=pyqt5
|
PYTEST_QT_API=pyqt5
|
||||||
passenv = PYTHON DISPLAY XAUTHORITY HOME
|
passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER CI
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
py==1.4.30
|
py==1.4.30
|
||||||
@ -28,18 +19,29 @@ deps =
|
|||||||
pytest-capturelog==0.7
|
pytest-capturelog==0.7
|
||||||
pytest-qt==1.5.1
|
pytest-qt==1.5.1
|
||||||
pytest-mock==0.7.0
|
pytest-mock==0.7.0
|
||||||
pytest-html==1.3.2
|
pytest-html==1.4
|
||||||
hypothesis==1.10.1
|
hypothesis==1.10.1
|
||||||
hypothesis-pytest==0.15.1
|
hypothesis-pytest==0.17.0
|
||||||
|
coverage==3.7.1
|
||||||
|
pytest-cov==2.0.0
|
||||||
|
cov-core==1.15.0
|
||||||
commands =
|
commands =
|
||||||
{envpython} scripts/link_pyqt.py --tox {envdir}
|
{envpython} scripts/link_pyqt.py --tox {envdir}
|
||||||
{envpython} -m py.test --strict -rfEsw {posargs:tests}
|
{envpython} -m py.test --strict -rfEsw --cov qutebrowser --cov-report xml --cov-report= {posargs:tests}
|
||||||
|
{envpython} scripts/dev/check_coverage.py {posargs}
|
||||||
|
{envpython} -m qutebrowser --no-err-windows --nowindow --temp-basedir about:blank ":later 500 quit"
|
||||||
|
|
||||||
|
[testenv:mkvenv]
|
||||||
|
basepython = python3
|
||||||
|
commands = {envpython} scripts/link_pyqt.py --tox {envdir}
|
||||||
|
envdir = {toxinidir}/.venv
|
||||||
|
usedevelop = true
|
||||||
|
|
||||||
[testenv:unittests-watch]
|
[testenv:unittests-watch]
|
||||||
setenv = {[testenv:unittests]setenv}
|
basepython = python3
|
||||||
passenv = {[testenv:unittests]passenv}
|
passenv = {[testenv]passenv}
|
||||||
deps =
|
deps =
|
||||||
{[testenv:unittests]deps}
|
{[testenv]deps}
|
||||||
pytest-testmon==0.6
|
pytest-testmon==0.6
|
||||||
pytest-watch==3.2.0
|
pytest-watch==3.2.0
|
||||||
commands =
|
commands =
|
||||||
@ -47,38 +49,32 @@ commands =
|
|||||||
{envdir}/bin/ptw -- --testmon --strict -rfEsw {posargs:tests}
|
{envdir}/bin/ptw -- --testmon --strict -rfEsw {posargs:tests}
|
||||||
|
|
||||||
[testenv:unittests-frozen]
|
[testenv:unittests-frozen]
|
||||||
setenv = {[testenv:unittests]setenv}
|
basepython = python3
|
||||||
passenv = {[testenv:unittests]passenv}
|
passenv = {[testenv]passenv}
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps =
|
deps =
|
||||||
{[testenv:unittests]deps}
|
{[testenv]deps}
|
||||||
cx_Freeze==4.3.4
|
cx_Freeze==4.3.4
|
||||||
commands =
|
commands =
|
||||||
{envpython} scripts/link_pyqt.py --tox {envdir}
|
{envpython} scripts/link_pyqt.py --tox {envdir}
|
||||||
{envpython} scripts/dev/freeze_tests.py build_exe -b {envdir}/build
|
{envpython} scripts/dev/freeze_tests.py build_exe -b {envdir}/build
|
||||||
{envdir}/build/run-frozen-tests --strict -rfEsw {posargs}
|
{envdir}/build/run-frozen-tests --strict -rfEsw {posargs}
|
||||||
|
|
||||||
[testenv:coverage]
|
|
||||||
passenv = PYTHON DISPLAY XAUTHORITY HOME
|
|
||||||
deps =
|
|
||||||
{[testenv:unittests]deps}
|
|
||||||
coverage==3.7.1
|
|
||||||
pytest-cov==2.0.0
|
|
||||||
cov-core==1.15.0
|
|
||||||
commands =
|
|
||||||
{envpython} scripts/link_pyqt.py --tox {envdir}
|
|
||||||
{envpython} -m py.test --strict -rfEswx -v --cov qutebrowser --cov-report term --cov-report html --cov-report xml {posargs:tests}
|
|
||||||
{envpython} scripts/dev/check_coverage.py
|
|
||||||
|
|
||||||
[testenv:misc]
|
[testenv:misc]
|
||||||
|
basepython = python3
|
||||||
|
# For global .gitignore files
|
||||||
|
passenv = HOME
|
||||||
|
deps =
|
||||||
commands =
|
commands =
|
||||||
{envpython} scripts/dev/misc_checks.py git
|
{envpython} scripts/dev/misc_checks.py git
|
||||||
{envpython} scripts/dev/misc_checks.py vcs
|
{envpython} scripts/dev/misc_checks.py vcs
|
||||||
{envpython} scripts/dev/misc_checks.py spelling
|
{envpython} scripts/dev/misc_checks.py spelling
|
||||||
|
|
||||||
[testenv:pylint]
|
[testenv:pylint]
|
||||||
|
basepython = python3
|
||||||
skip_install = true
|
skip_install = true
|
||||||
setenv = PYTHONPATH={toxinidir}/scripts/dev
|
setenv = PYTHONPATH={toxinidir}/scripts/dev
|
||||||
|
passenv =
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
astroid==1.3.8
|
astroid==1.3.8
|
||||||
@ -93,9 +89,10 @@ commands =
|
|||||||
{envpython} scripts/dev/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no --expected-line-ending-format=LF
|
{envpython} scripts/dev/run_pylint_on_tests.py --rcfile=.pylintrc --output-format=colorized --reports=no --expected-line-ending-format=LF
|
||||||
|
|
||||||
[testenv:pep257]
|
[testenv:pep257]
|
||||||
|
basepython = python3
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps = pep257==0.6.0
|
|
||||||
passenv = PYTHON LANG
|
passenv = PYTHON LANG
|
||||||
|
deps = pep257==0.6.0
|
||||||
# Disabled checks:
|
# Disabled checks:
|
||||||
# D102: Missing docstring in public method (will be handled by others)
|
# D102: Missing docstring in public method (will be handled by others)
|
||||||
# D103: Missing docstring in public function (will be handled by others)
|
# D103: Missing docstring in public function (will be handled by others)
|
||||||
@ -104,8 +101,10 @@ passenv = PYTHON LANG
|
|||||||
commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D209,D402 '--match=(?!resources|test_*).*\.py'
|
commands = {envpython} -m pep257 scripts tests qutebrowser --ignore=D102,D103,D209,D402 '--match=(?!resources|test_*).*\.py'
|
||||||
|
|
||||||
[testenv:pyflakes]
|
[testenv:pyflakes]
|
||||||
|
basepython = python3
|
||||||
# https://github.com/fschulze/pytest-flakes/issues/6
|
# https://github.com/fschulze/pytest-flakes/issues/6
|
||||||
setenv = LANG=en_US.UTF-8
|
setenv = LANG=en_US.UTF-8
|
||||||
|
passenv =
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
py==1.4.30
|
py==1.4.30
|
||||||
@ -117,6 +116,8 @@ commands =
|
|||||||
{envpython} -m py.test -q --flakes --ignore=tests
|
{envpython} -m py.test -q --flakes --ignore=tests
|
||||||
|
|
||||||
[testenv:pep8]
|
[testenv:pep8]
|
||||||
|
basepython = python3
|
||||||
|
passenv =
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
py==1.4.30
|
py==1.4.30
|
||||||
@ -128,6 +129,8 @@ commands =
|
|||||||
{envpython} -m py.test -q --pep8 --ignore=tests
|
{envpython} -m py.test -q --pep8 --ignore=tests
|
||||||
|
|
||||||
[testenv:mccabe]
|
[testenv:mccabe]
|
||||||
|
basepython = python3
|
||||||
|
passenv =
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
py==1.4.30
|
py==1.4.30
|
||||||
@ -139,7 +142,9 @@ commands =
|
|||||||
{envpython} -m py.test -q --mccabe --ignore=tests
|
{envpython} -m py.test -q --mccabe --ignore=tests
|
||||||
|
|
||||||
[testenv:pyroma]
|
[testenv:pyroma]
|
||||||
|
basepython = python3
|
||||||
skip_install = true
|
skip_install = true
|
||||||
|
passenv =
|
||||||
deps =
|
deps =
|
||||||
pyroma==1.8.2
|
pyroma==1.8.2
|
||||||
docutils==0.12
|
docutils==0.12
|
||||||
@ -148,7 +153,9 @@ commands =
|
|||||||
{envdir}/bin/pyroma .
|
{envdir}/bin/pyroma .
|
||||||
|
|
||||||
[testenv:check-manifest]
|
[testenv:check-manifest]
|
||||||
|
basepython = python3
|
||||||
skip_install = true
|
skip_install = true
|
||||||
|
passenv =
|
||||||
deps =
|
deps =
|
||||||
check-manifest==0.25
|
check-manifest==0.25
|
||||||
commands =
|
commands =
|
||||||
@ -156,8 +163,10 @@ commands =
|
|||||||
{envdir}/bin/check-manifest --ignore 'qutebrowser/git-commit-id,qutebrowser/html/doc,qutebrowser/html/doc/*,*/__pycache__'
|
{envdir}/bin/check-manifest --ignore 'qutebrowser/git-commit-id,qutebrowser/html/doc,qutebrowser/html/doc/*,*/__pycache__'
|
||||||
|
|
||||||
[testenv:docs]
|
[testenv:docs]
|
||||||
|
basepython = python3
|
||||||
skip_install = true
|
skip_install = true
|
||||||
whitelist_externals = git
|
whitelist_externals = git
|
||||||
|
passenv =
|
||||||
deps =
|
deps =
|
||||||
-r{toxinidir}/requirements.txt
|
-r{toxinidir}/requirements.txt
|
||||||
commands =
|
commands =
|
||||||
@ -166,22 +175,12 @@ commands =
|
|||||||
git --no-pager diff --exit-code --stat
|
git --no-pager diff --exit-code --stat
|
||||||
{envpython} scripts/asciidoc2html.py {posargs}
|
{envpython} scripts/asciidoc2html.py {posargs}
|
||||||
|
|
||||||
[testenv:smoke]
|
|
||||||
# https://bitbucket.org/hpk42/tox/issue/246/ - only needed for Windows though
|
|
||||||
setenv = QT_QPA_PLATFORM_PLUGIN_PATH={envdir}/Lib/site-packages/PyQt5/plugins/platforms
|
|
||||||
passenv = PYTHON DISPLAY XAUTHORITY HOME USERNAME USER
|
|
||||||
deps =
|
|
||||||
-r{toxinidir}/requirements.txt
|
|
||||||
commands =
|
|
||||||
{envpython} scripts/link_pyqt.py --tox {envdir}
|
|
||||||
{envpython} -m qutebrowser --no-err-windows --nowindow --temp-basedir about:blank ":later 500 quit"
|
|
||||||
|
|
||||||
[testenv:smoke-frozen]
|
[testenv:smoke-frozen]
|
||||||
setenv = {[testenv:smoke]setenv}
|
basepython = python3
|
||||||
passenv = {[testenv:smoke]passenv}
|
passenv = {[testenv]passenv}
|
||||||
skip_install = true
|
skip_install = true
|
||||||
deps =
|
deps =
|
||||||
{[testenv:smoke]deps}
|
{[testenv]deps}
|
||||||
cx_Freeze==4.3.4
|
cx_Freeze==4.3.4
|
||||||
commands =
|
commands =
|
||||||
{envpython} scripts/link_pyqt.py --tox {envdir}
|
{envpython} scripts/link_pyqt.py --tox {envdir}
|
||||||
|
Loading…
Reference in New Issue
Block a user