2015-11-02 23:39:35 +01:00
|
|
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
|
|
|
|
2019-02-23 06:34:17 +01:00
|
|
|
# Copyright 2015-2019 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
2015-11-02 23:39:35 +01:00
|
|
|
#
|
|
|
|
# This file is part of qutebrowser.
|
|
|
|
#
|
|
|
|
# qutebrowser is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# qutebrowser is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with qutebrowser. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
"""Partial comparison of dicts/lists."""
|
|
|
|
|
|
|
|
|
2015-11-13 07:49:33 +01:00
|
|
|
import re
|
2015-11-12 19:04:37 +01:00
|
|
|
import pprint
|
2016-03-28 23:48:37 +02:00
|
|
|
import os.path
|
2017-06-30 22:03:34 +02:00
|
|
|
import contextlib
|
2015-11-10 06:50:31 +01:00
|
|
|
|
2016-08-22 07:39:11 +02:00
|
|
|
import pytest
|
|
|
|
|
2017-11-14 01:57:11 +01:00
|
|
|
from qutebrowser.utils import qtutils
|
|
|
|
|
2017-05-19 08:10:17 +02:00
|
|
|
|
|
|
|
qt58 = pytest.mark.skipif(
|
|
|
|
qtutils.version_check('5.9'), reason="Needs Qt 5.8 or earlier")
|
|
|
|
qt59 = pytest.mark.skipif(
|
|
|
|
not qtutils.version_check('5.9'), reason="Needs Qt 5.9 or newer")
|
2018-06-12 10:52:22 +02:00
|
|
|
qt510 = pytest.mark.skipif(
|
|
|
|
not qtutils.version_check('5.10'), reason="Needs Qt 5.10 or newer")
|
2018-06-11 08:52:31 +02:00
|
|
|
skip_qt511 = pytest.mark.skipif(
|
|
|
|
qtutils.version_check('5.11'), reason="Needs Qt 5.10 or earlier")
|
2017-05-19 08:10:17 +02:00
|
|
|
|
2015-11-10 06:50:31 +01:00
|
|
|
|
2016-04-21 00:14:13 +02:00
|
|
|
class PartialCompareOutcome:
|
|
|
|
|
|
|
|
"""Storage for a partial_compare error.
|
|
|
|
|
|
|
|
Evaluates to False if an error was found.
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
error: A string describing an error or None.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, error=None):
|
|
|
|
self.error = error
|
|
|
|
|
|
|
|
def __bool__(self):
|
|
|
|
return self.error is None
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return 'PartialCompareOutcome(error={!r})'.format(self.error)
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return 'true' if self.error is None else 'false'
|
|
|
|
|
|
|
|
|
2015-11-12 19:04:37 +01:00
|
|
|
def print_i(text, indent, error=False):
|
|
|
|
if error:
|
|
|
|
text = '| ****** {} ******'.format(text)
|
|
|
|
for line in text.splitlines():
|
|
|
|
print('| ' * indent + line)
|
|
|
|
|
|
|
|
|
2016-04-21 00:14:13 +02:00
|
|
|
def _partial_compare_dict(val1, val2, *, indent):
|
2015-11-02 23:39:35 +01:00
|
|
|
for key in val2:
|
|
|
|
if key not in val1:
|
2016-04-21 00:14:13 +02:00
|
|
|
outcome = PartialCompareOutcome(
|
|
|
|
"Key {!r} is in second dict but not in first!".format(key))
|
|
|
|
print_i(outcome.error, indent, error=True)
|
|
|
|
return outcome
|
2016-04-27 18:30:54 +02:00
|
|
|
outcome = partial_compare(val1[key], val2[key], indent=indent + 1)
|
2016-04-21 00:14:13 +02:00
|
|
|
if not outcome:
|
|
|
|
return outcome
|
|
|
|
return PartialCompareOutcome()
|
2015-11-02 23:39:35 +01:00
|
|
|
|
|
|
|
|
2016-04-21 00:14:13 +02:00
|
|
|
def _partial_compare_list(val1, val2, *, indent):
|
2015-11-02 23:39:35 +01:00
|
|
|
if len(val1) < len(val2):
|
2016-04-21 00:14:13 +02:00
|
|
|
outcome = PartialCompareOutcome(
|
|
|
|
"Second list is longer than first list")
|
|
|
|
print_i(outcome.error, indent, error=True)
|
|
|
|
return outcome
|
2015-11-02 23:39:35 +01:00
|
|
|
for item1, item2 in zip(val1, val2):
|
2016-04-27 18:30:54 +02:00
|
|
|
outcome = partial_compare(item1, item2, indent=indent + 1)
|
2016-04-21 00:14:13 +02:00
|
|
|
if not outcome:
|
|
|
|
return outcome
|
|
|
|
return PartialCompareOutcome()
|
|
|
|
|
|
|
|
|
|
|
|
def _partial_compare_float(val1, val2, *, indent):
|
2016-08-22 07:39:11 +02:00
|
|
|
if val1 == pytest.approx(val2):
|
2016-04-21 00:14:13 +02:00
|
|
|
return PartialCompareOutcome()
|
|
|
|
|
|
|
|
return PartialCompareOutcome("{!r} != {!r} (float comparison)".format(
|
|
|
|
val1, val2))
|
|
|
|
|
|
|
|
|
|
|
|
def _partial_compare_str(val1, val2, *, indent):
|
|
|
|
if pattern_match(pattern=val2, value=val1):
|
|
|
|
return PartialCompareOutcome()
|
|
|
|
|
|
|
|
return PartialCompareOutcome("{!r} != {!r} (pattern matching)".format(
|
|
|
|
val1, val2))
|
|
|
|
|
|
|
|
|
|
|
|
def _partial_compare_eq(val1, val2, *, indent):
|
|
|
|
if val1 == val2:
|
|
|
|
return PartialCompareOutcome()
|
|
|
|
return PartialCompareOutcome("{!r} != {!r}".format(val1, val2))
|
2015-11-02 23:39:35 +01:00
|
|
|
|
|
|
|
|
2015-11-12 19:04:37 +01:00
|
|
|
def partial_compare(val1, val2, *, indent=0):
|
2015-11-02 23:39:35 +01:00
|
|
|
"""Do a partial comparison between the given values.
|
|
|
|
|
|
|
|
For dicts, keys in val2 are checked, others are ignored.
|
|
|
|
For lists, entries at the positions in val2 are checked, others ignored.
|
|
|
|
For other values, == is used.
|
|
|
|
|
|
|
|
This happens recursively.
|
|
|
|
"""
|
2015-11-12 19:04:37 +01:00
|
|
|
print_i("Comparing", indent)
|
|
|
|
print_i(pprint.pformat(val1), indent + 1)
|
|
|
|
print_i("|---- to ----", indent)
|
|
|
|
print_i(pprint.pformat(val2), indent + 1)
|
|
|
|
|
2015-11-02 23:39:35 +01:00
|
|
|
if val2 is Ellipsis:
|
2015-11-12 19:04:37 +01:00
|
|
|
print_i("Ignoring ellipsis comparison", indent, error=True)
|
2016-04-21 00:14:13 +02:00
|
|
|
return PartialCompareOutcome()
|
2015-11-02 23:49:44 +01:00
|
|
|
elif type(val1) != type(val2): # pylint: disable=unidiomatic-typecheck
|
2016-04-21 00:14:13 +02:00
|
|
|
outcome = PartialCompareOutcome(
|
|
|
|
"Different types ({}, {}) -> False".format(type(val1).__name__,
|
|
|
|
type(val2).__name__))
|
|
|
|
print_i(outcome.error, indent, error=True)
|
|
|
|
return outcome
|
|
|
|
|
|
|
|
handlers = {
|
|
|
|
dict: _partial_compare_dict,
|
|
|
|
list: _partial_compare_list,
|
|
|
|
float: _partial_compare_float,
|
|
|
|
str: _partial_compare_str,
|
|
|
|
}
|
|
|
|
|
|
|
|
for typ, handler in handlers.items():
|
|
|
|
if isinstance(val2, typ):
|
|
|
|
print_i("|======= Comparing as {}".format(typ.__name__), indent)
|
|
|
|
outcome = handler(val1, val2, indent=indent)
|
|
|
|
break
|
2015-11-02 23:39:35 +01:00
|
|
|
else:
|
2015-11-12 19:04:37 +01:00
|
|
|
print_i("|======= Comparing via ==", indent)
|
2016-04-21 00:14:13 +02:00
|
|
|
outcome = _partial_compare_eq(val1, val2, indent=indent)
|
|
|
|
print_i("---> {}".format(outcome), indent)
|
|
|
|
return outcome
|
2015-11-13 07:49:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
def pattern_match(*, pattern, value):
|
|
|
|
"""Do fnmatch.fnmatchcase like matching, but only with * active.
|
|
|
|
|
|
|
|
Return:
|
|
|
|
True on a match, False otherwise.
|
|
|
|
"""
|
|
|
|
re_pattern = '.*'.join(re.escape(part) for part in pattern.split('*'))
|
2016-10-24 15:51:55 +02:00
|
|
|
return re.fullmatch(re_pattern, value, flags=re.DOTALL) is not None
|
2016-03-28 23:48:37 +02:00
|
|
|
|
2016-03-29 00:08:14 +02:00
|
|
|
|
2016-03-29 19:52:46 +02:00
|
|
|
def abs_datapath():
|
2016-05-29 18:20:00 +02:00
|
|
|
"""Get the absolute path to the end2end data directory."""
|
2016-03-29 19:52:46 +02:00
|
|
|
file_abs = os.path.abspath(os.path.dirname(__file__))
|
2016-05-29 18:20:00 +02:00
|
|
|
return os.path.join(file_abs, '..', 'end2end', 'data')
|
2017-06-30 22:03:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
@contextlib.contextmanager
|
|
|
|
def nop_contextmanager():
|
|
|
|
yield
|