Spaces:
Running
Running
| """Various utility functions.""" | |
| from collections import namedtuple, Counter | |
| from os.path import commonprefix | |
| __unittest = True | |
| _MAX_LENGTH = 80 | |
| _PLACEHOLDER_LEN = 12 | |
| _MIN_BEGIN_LEN = 5 | |
| _MIN_END_LEN = 5 | |
| _MIN_COMMON_LEN = 5 | |
| _MIN_DIFF_LEN = _MAX_LENGTH - \ | |
| (_MIN_BEGIN_LEN + _PLACEHOLDER_LEN + _MIN_COMMON_LEN + | |
| _PLACEHOLDER_LEN + _MIN_END_LEN) | |
| assert _MIN_DIFF_LEN >= 0 | |
| def _shorten(s, prefixlen, suffixlen): | |
| skip = len(s) - prefixlen - suffixlen | |
| if skip > _PLACEHOLDER_LEN: | |
| s = '%s[%d chars]%s' % (s[:prefixlen], skip, s[len(s) - suffixlen:]) | |
| return s | |
| def _common_shorten_repr(*args): | |
| args = tuple(map(safe_repr, args)) | |
| maxlen = max(map(len, args)) | |
| if maxlen <= _MAX_LENGTH: | |
| return args | |
| prefix = commonprefix(args) | |
| prefixlen = len(prefix) | |
| common_len = _MAX_LENGTH - \ | |
| (maxlen - prefixlen + _MIN_BEGIN_LEN + _PLACEHOLDER_LEN) | |
| if common_len > _MIN_COMMON_LEN: | |
| assert _MIN_BEGIN_LEN + _PLACEHOLDER_LEN + _MIN_COMMON_LEN + \ | |
| (maxlen - prefixlen) < _MAX_LENGTH | |
| prefix = _shorten(prefix, _MIN_BEGIN_LEN, common_len) | |
| return tuple(prefix + s[prefixlen:] for s in args) | |
| prefix = _shorten(prefix, _MIN_BEGIN_LEN, _MIN_COMMON_LEN) | |
| return tuple(prefix + _shorten(s[prefixlen:], _MIN_DIFF_LEN, _MIN_END_LEN) | |
| for s in args) | |
| def safe_repr(obj, short=False): | |
| try: | |
| result = repr(obj) | |
| except Exception: | |
| result = object.__repr__(obj) | |
| if not short or len(result) < _MAX_LENGTH: | |
| return result | |
| return result[:_MAX_LENGTH] + ' [truncated]...' | |
| def strclass(cls): | |
| return "%s.%s" % (cls.__module__, cls.__qualname__) | |
| def sorted_list_difference(expected, actual): | |
| """Finds elements in only one or the other of two, sorted input lists. | |
| Returns a two-element tuple of lists. The first list contains those | |
| elements in the "expected" list but not in the "actual" list, and the | |
| second contains those elements in the "actual" list but not in the | |
| "expected" list. Duplicate elements in either input list are ignored. | |
| """ | |
| i = j = 0 | |
| missing = [] | |
| unexpected = [] | |
| while True: | |
| try: | |
| e = expected[i] | |
| a = actual[j] | |
| if e < a: | |
| missing.append(e) | |
| i += 1 | |
| while expected[i] == e: | |
| i += 1 | |
| elif e > a: | |
| unexpected.append(a) | |
| j += 1 | |
| while actual[j] == a: | |
| j += 1 | |
| else: | |
| i += 1 | |
| try: | |
| while expected[i] == e: | |
| i += 1 | |
| finally: | |
| j += 1 | |
| while actual[j] == a: | |
| j += 1 | |
| except IndexError: | |
| missing.extend(expected[i:]) | |
| unexpected.extend(actual[j:]) | |
| break | |
| return missing, unexpected | |
| def unorderable_list_difference(expected, actual): | |
| """Same behavior as sorted_list_difference but | |
| for lists of unorderable items (like dicts). | |
| As it does a linear search per item (remove) it | |
| has O(n*n) performance.""" | |
| missing = [] | |
| while expected: | |
| item = expected.pop() | |
| try: | |
| actual.remove(item) | |
| except ValueError: | |
| missing.append(item) | |
| # anything left in actual is unexpected | |
| return missing, actual | |
| def three_way_cmp(x, y): | |
| """Return -1 if x < y, 0 if x == y and 1 if x > y""" | |
| return (x > y) - (x < y) | |
| _Mismatch = namedtuple('Mismatch', 'actual expected value') | |
| def _count_diff_all_purpose(actual, expected): | |
| 'Returns list of (cnt_act, cnt_exp, elem) triples where the counts differ' | |
| # elements need not be hashable | |
| s, t = list(actual), list(expected) | |
| m, n = len(s), len(t) | |
| NULL = object() | |
| result = [] | |
| for i, elem in enumerate(s): | |
| if elem is NULL: | |
| continue | |
| cnt_s = cnt_t = 0 | |
| for j in range(i, m): | |
| if s[j] == elem: | |
| cnt_s += 1 | |
| s[j] = NULL | |
| for j, other_elem in enumerate(t): | |
| if other_elem == elem: | |
| cnt_t += 1 | |
| t[j] = NULL | |
| if cnt_s != cnt_t: | |
| diff = _Mismatch(cnt_s, cnt_t, elem) | |
| result.append(diff) | |
| for i, elem in enumerate(t): | |
| if elem is NULL: | |
| continue | |
| cnt_t = 0 | |
| for j in range(i, n): | |
| if t[j] == elem: | |
| cnt_t += 1 | |
| t[j] = NULL | |
| diff = _Mismatch(0, cnt_t, elem) | |
| result.append(diff) | |
| return result | |
| def _count_diff_hashable(actual, expected): | |
| 'Returns list of (cnt_act, cnt_exp, elem) triples where the counts differ' | |
| # elements must be hashable | |
| s, t = Counter(actual), Counter(expected) | |
| result = [] | |
| for elem, cnt_s in s.items(): | |
| cnt_t = t.get(elem, 0) | |
| if cnt_s != cnt_t: | |
| diff = _Mismatch(cnt_s, cnt_t, elem) | |
| result.append(diff) | |
| for elem, cnt_t in t.items(): | |
| if elem not in s: | |
| diff = _Mismatch(0, cnt_t, elem) | |
| result.append(diff) | |
| return result | |