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