| """Various utility functions.""" |
| |
| from collections import namedtuple, OrderedDict |
| 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.__name__) |
| |
| 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 _ordered_count(iterable): |
| 'Return dict of element counts, in the order they were first seen' |
| c = OrderedDict() |
| for elem in iterable: |
| c[elem] = c.get(elem, 0) + 1 |
| return c |
| |
| 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 = _ordered_count(actual), _ordered_count(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 |