summaryrefslogtreecommitdiff
path: root/tests/helpers/utils.py
blob: d3ef3cd70774e764b048dbb00f973c15d0cd1f5c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:

# Copyright 2015-2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
#
# This file is part of qutebrowser.
#
# qutebrowser is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# qutebrowser is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with qutebrowser.  If not, see <http://www.gnu.org/licenses/>.

"""Partial comparison of dicts/lists."""


import re
import pprint
import os.path


def print_i(text, indent, error=False):
    if error:
        text = '| ****** {} ******'.format(text)
    for line in text.splitlines():
        print('|   ' * indent + line)


def _partial_compare_dict(val1, val2, *, indent=0):
    for key in val2:
        if key not in val1:
            print_i("Key {!r} is in second dict but not in first!".format(key),
                    indent, error=True)
            return False
        if not partial_compare(val1[key], val2[key], indent=indent+1):
            return False
    return True


def _partial_compare_list(val1, val2, *, indent=0):
    if len(val1) < len(val2):
        print_i("Second list is longer than first list", indent, error=True)
        return False
    for item1, item2 in zip(val1, val2):
        if not partial_compare(item1, item2, indent=indent+1):
            return False
    return True


def partial_compare(val1, val2, *, indent=0):
    """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.
    """
    print_i("Comparing", indent)
    print_i(pprint.pformat(val1), indent + 1)
    print_i("|---- to ----", indent)
    print_i(pprint.pformat(val2), indent + 1)

    if val2 is Ellipsis:
        print_i("Ignoring ellipsis comparison", indent, error=True)
        return True
    elif type(val1) != type(val2):  # pylint: disable=unidiomatic-typecheck
        print_i("Different types ({}, {}) -> False".format(
                type(val1), type(val2)), indent, error=True)
        return False

    if isinstance(val2, dict):
        print_i("|======= Comparing as dicts", indent)
        equal = _partial_compare_dict(val1, val2, indent=indent)
    elif isinstance(val2, list):
        print_i("|======= Comparing as lists", indent)
        equal = _partial_compare_list(val1, val2, indent=indent)
    elif isinstance(val2, float):
        print_i("|======= Doing float comparison", indent)
        equal = abs(val1 - val2) < 0.00001
    elif isinstance(val2, str):
        print_i("|======= Doing string comparison", indent)
        equal = pattern_match(pattern=val2, value=val1)
    else:
        print_i("|======= Comparing via ==", indent)
        equal = val1 == val2
    print_i("---> {}".format(equal), indent)
    return equal


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('*'))
    return re.fullmatch(re_pattern, value) is not None


def abs_datapath():
    """Get the absolute path to the integration data directory.

    Return:
        The absolute path to the tests/integration/data directory.
    """
    file_abs = os.path.abspath(os.path.dirname(__file__))
    return os.path.join(file_abs, '..', 'integration', 'data')