1ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh"""Various utility functions."""
2ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehfrom collections import namedtuple, OrderedDict
3ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
4ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
5ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh__unittest = True
6ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
7ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh_MAX_LENGTH = 80
8ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef safe_repr(obj, short=False):
9ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    try:
10ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        result = repr(obj)
11ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    except Exception:
12ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        result = object.__repr__(obj)
13ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    if not short or len(result) < _MAX_LENGTH:
14ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return result
15ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    return result[:_MAX_LENGTH] + ' [truncated]...'
16ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
17ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
18ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef strclass(cls):
19ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    return "%s.%s" % (cls.__module__, cls.__name__)
20ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
21ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef sorted_list_difference(expected, actual):
22ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """Finds elements in only one or the other of two, sorted input lists.
23ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
24ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    Returns a two-element tuple of lists.    The first list contains those
25ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    elements in the "expected" list but not in the "actual" list, and the
26ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    second contains those elements in the "actual" list but not in the
27ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    "expected" list.    Duplicate elements in either input list are ignored.
28ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """
29ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    i = j = 0
30ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    missing = []
31ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    unexpected = []
32ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    while True:
33ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        try:
34ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            e = expected[i]
35ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            a = actual[j]
36ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            if e < a:
37ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                missing.append(e)
38ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                i += 1
39ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                while expected[i] == e:
40ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                    i += 1
41ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            elif e > a:
42ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                unexpected.append(a)
43ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                j += 1
44ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                while actual[j] == a:
45ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                    j += 1
46ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            else:
47ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                i += 1
48ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                try:
49ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                    while expected[i] == e:
50ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                        i += 1
51ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                finally:
52ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                    j += 1
53ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                    while actual[j] == a:
54ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                        j += 1
55ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        except IndexError:
56ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            missing.extend(expected[i:])
57ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            unexpected.extend(actual[j:])
58ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            break
59ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    return missing, unexpected
60ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
61ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
62ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef unorderable_list_difference(expected, actual, ignore_duplicate=False):
63ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """Same behavior as sorted_list_difference but
64ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for lists of unorderable items (like dicts).
65ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
66ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    As it does a linear search per item (remove) it
67ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    has O(n*n) performance.
68ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    """
69ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    missing = []
70ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    unexpected = []
71ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    while expected:
72ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        item = expected.pop()
73ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        try:
74ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            actual.remove(item)
75ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        except ValueError:
76ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            missing.append(item)
77ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if ignore_duplicate:
78ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            for lst in expected, actual:
79ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                try:
80ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                    while True:
81ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                        lst.remove(item)
82ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                except ValueError:
83ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                    pass
84ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    if ignore_duplicate:
85ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        while actual:
86ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            item = actual.pop()
87ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            unexpected.append(item)
88ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            try:
89ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                while True:
90ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                    actual.remove(item)
91ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            except ValueError:
92ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                pass
93ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        return missing, unexpected
94ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
95ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # anything left in actual is unexpected
96ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    return missing, actual
97ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
98ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh_Mismatch = namedtuple('Mismatch', 'actual expected value')
99ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
100ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef _count_diff_all_purpose(actual, expected):
101ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    'Returns list of (cnt_act, cnt_exp, elem) triples where the counts differ'
102ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # elements need not be hashable
103ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    s, t = list(actual), list(expected)
104ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    m, n = len(s), len(t)
105ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    NULL = object()
106ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    result = []
107ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for i, elem in enumerate(s):
108ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if elem is NULL:
109ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            continue
110ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        cnt_s = cnt_t = 0
111ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        for j in range(i, m):
112ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            if s[j] == elem:
113ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                cnt_s += 1
114ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                s[j] = NULL
115ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        for j, other_elem in enumerate(t):
116ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            if other_elem == elem:
117ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                cnt_t += 1
118ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                t[j] = NULL
119ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if cnt_s != cnt_t:
120ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            diff = _Mismatch(cnt_s, cnt_t, elem)
121ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            result.append(diff)
122ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
123ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for i, elem in enumerate(t):
124ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if elem is NULL:
125ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            continue
126ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        cnt_t = 0
127ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        for j in range(i, n):
128ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            if t[j] == elem:
129ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                cnt_t += 1
130ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh                t[j] = NULL
131ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        diff = _Mismatch(0, cnt_t, elem)
132ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        result.append(diff)
133ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    return result
134ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
135ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef _ordered_count(iterable):
136ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    'Return dict of element counts, in the order they were first seen'
137ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    c = OrderedDict()
138ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for elem in iterable:
139ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        c[elem] = c.get(elem, 0) + 1
140ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    return c
141ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh
142ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsiehdef _count_diff_hashable(actual, expected):
143ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    'Returns list of (cnt_act, cnt_exp, elem) triples where the counts differ'
144ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    # elements must be hashable
145ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    s, t = _ordered_count(actual), _ordered_count(expected)
146ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    result = []
147ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for elem, cnt_s in s.items():
148ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        cnt_t = t.get(elem, 0)
149ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if cnt_s != cnt_t:
150ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            diff = _Mismatch(cnt_s, cnt_t, elem)
151ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            result.append(diff)
152ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    for elem, cnt_t in t.items():
153ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh        if elem not in s:
154ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            diff = _Mismatch(0, cnt_t, elem)
155ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh            result.append(diff)
156ffab958fd8d42ed7227d83007350e61555a1fa36Andrew Hsieh    return result
157