aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Sawicki <contact@grub4k.xyz>2025-02-21 21:29:07 +0100
committerGitHub <noreply@github.com>2025-02-21 21:29:07 +0100
commita3e0c7d3b267abdf3933b709704a28d43bb46503 (patch)
treeb0385ebdeb40221dd9eb1c0c9f5b967af5fad201
parentf7a1f2d8132967a62b0f6d5665c6d2dde2d42c09 (diff)
[test] Show all differences for `expect_value` and `expect_dict` (#12334)
Authored by: Grub4K
-rw-r--r--test/helper.py174
1 files changed, 98 insertions, 76 deletions
diff --git a/test/helper.py b/test/helper.py
index 193019019..4169af799 100644
--- a/test/helper.py
+++ b/test/helper.py
@@ -101,87 +101,109 @@ def getwebpagetestcases():
md5 = lambda s: hashlib.md5(s.encode()).hexdigest()
-def expect_value(self, got, expected, field):
- if isinstance(expected, str) and expected.startswith('re:'):
- match_str = expected[len('re:'):]
- match_rex = re.compile(match_str)
-
- self.assertTrue(
- isinstance(got, str),
- f'Expected a {str.__name__} object, but got {type(got).__name__} for field {field}')
- self.assertTrue(
- match_rex.match(got),
- f'field {field} (value: {got!r}) should match {match_str!r}')
- elif isinstance(expected, str) and expected.startswith('startswith:'):
- start_str = expected[len('startswith:'):]
- self.assertTrue(
- isinstance(got, str),
- f'Expected a {str.__name__} object, but got {type(got).__name__} for field {field}')
- self.assertTrue(
- got.startswith(start_str),
- f'field {field} (value: {got!r}) should start with {start_str!r}')
- elif isinstance(expected, str) and expected.startswith('contains:'):
- contains_str = expected[len('contains:'):]
- self.assertTrue(
- isinstance(got, str),
- f'Expected a {str.__name__} object, but got {type(got).__name__} for field {field}')
- self.assertTrue(
- contains_str in got,
- f'field {field} (value: {got!r}) should contain {contains_str!r}')
- elif isinstance(expected, type):
- self.assertTrue(
- isinstance(got, expected),
- f'Expected type {expected!r} for field {field}, but got value {got!r} of type {type(got)!r}')
- elif isinstance(expected, dict) and isinstance(got, dict):
- expect_dict(self, got, expected)
- elif isinstance(expected, list) and isinstance(got, list):
- self.assertEqual(
- len(expected), len(got),
- f'Expect a list of length {len(expected)}, but got a list of length {len(got)} for field {field}')
- for index, (item_got, item_expected) in enumerate(zip(got, expected)):
- type_got = type(item_got)
- type_expected = type(item_expected)
- self.assertEqual(
- type_expected, type_got,
- f'Type mismatch for list item at index {index} for field {field}, '
- f'expected {type_expected!r}, got {type_got!r}')
- expect_value(self, item_got, item_expected, field)
- else:
- if isinstance(expected, str) and expected.startswith('md5:'):
- self.assertTrue(
- isinstance(got, str),
- f'Expected field {field} to be a unicode object, but got value {got!r} of type {type(got)!r}')
- got = 'md5:' + md5(got)
- elif isinstance(expected, str) and re.match(r'^(?:min|max)?count:\d+', expected):
- self.assertTrue(
- isinstance(got, (list, dict)),
- f'Expected field {field} to be a list or a dict, but it is of type {type(got).__name__}')
- op, _, expected_num = expected.partition(':')
- expected_num = int(expected_num)
+def _iter_differences(got, expected, field):
+ if isinstance(expected, str):
+ op, _, val = expected.partition(':')
+ if op in ('mincount', 'maxcount', 'count'):
+ if not isinstance(got, (list, dict)):
+ yield field, f'expected either {list.__name__} or {dict.__name__}, got {type(got).__name__}'
+ return
+
+ expected_num = int(val)
+ got_num = len(got)
if op == 'mincount':
- assert_func = assertGreaterEqual
- msg_tmpl = 'Expected %d items in field %s, but only got %d'
- elif op == 'maxcount':
- assert_func = assertLessEqual
- msg_tmpl = 'Expected maximum %d items in field %s, but got %d'
- elif op == 'count':
- assert_func = assertEqual
- msg_tmpl = 'Expected exactly %d items in field %s, but got %d'
- else:
- assert False
- assert_func(
- self, len(got), expected_num,
- msg_tmpl % (expected_num, field, len(got)))
+ if got_num < expected_num:
+ yield field, f'expected at least {val} items, got {got_num}'
+ return
+
+ if op == 'maxcount':
+ if got_num > expected_num:
+ yield field, f'expected at most {val} items, got {got_num}'
+ return
+
+ assert op == 'count'
+ if got_num != expected_num:
+ yield field, f'expected exactly {val} items, got {got_num}'
+ return
+
+ if not isinstance(got, str):
+ yield field, f'expected {str.__name__}, got {type(got).__name__}'
+ return
+
+ if op == 're':
+ if not re.match(val, got):
+ yield field, f'should match {val!r}, got {got!r}'
+ return
+
+ if op == 'startswith':
+ if not val.startswith(got):
+ yield field, f'should start with {val!r}, got {got!r}'
return
- self.assertEqual(
- expected, got,
- f'Invalid value for field {field}, expected {expected!r}, got {got!r}')
+
+ if op == 'contains':
+ if not val.startswith(got):
+ yield field, f'should contain {val!r}, got {got!r}'
+ return
+
+ if op == 'md5':
+ hash_val = md5(got)
+ if hash_val != val:
+ yield field, f'expected hash {val}, got {hash_val}'
+ return
+
+ if got != expected:
+ yield field, f'expected {expected!r}, got {got!r}'
+ return
+
+ if isinstance(expected, dict) and isinstance(got, dict):
+ for key, expected_val in expected.items():
+ if key not in got:
+ yield field, f'missing key: {key!r}'
+ continue
+
+ field_name = key if field is None else f'{field}.{key}'
+ yield from _iter_differences(got[key], expected_val, field_name)
+ return
+
+ if isinstance(expected, type):
+ if not isinstance(got, expected):
+ yield field, f'expected {expected.__name__}, got {type(got).__name__}'
+ return
+
+ if isinstance(expected, list) and isinstance(got, list):
+ # TODO: clever diffing algorithm lmao
+ if len(expected) != len(got):
+ yield field, f'expected length of {len(expected)}, got {len(got)}'
+ return
+
+ for index, (got_val, expected_val) in enumerate(zip(got, expected)):
+ field_name = str(index) if field is None else f'{field}.{index}'
+ yield from _iter_differences(got_val, expected_val, field_name)
+ return
+
+ if got != expected:
+ yield field, f'expected {expected!r}, got {got!r}'
+
+
+def _expect_value(message, got, expected, field):
+ mismatches = list(_iter_differences(got, expected, field))
+ if not mismatches:
+ return
+
+ fields = [field for field, _ in mismatches if field is not None]
+ return ''.join((
+ message, f' ({", ".join(fields)})' if fields else '',
+ *(f'\n\t{field}: {message}' for field, message in mismatches)))
+
+
+def expect_value(self, got, expected, field):
+ if message := _expect_value('values differ', got, expected, field):
+ self.fail(message)
def expect_dict(self, got_dict, expected_dict):
- for info_field, expected in expected_dict.items():
- got = got_dict.get(info_field)
- expect_value(self, got, expected, info_field)
+ if message := _expect_value('dictionaries differ', got_dict, expected_dict, None):
+ self.fail(message)
def sanitize_got_info_dict(got_dict):