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