def _compare_values(expected_val, actual_val, full_path=None, subset_lists=False): missing_keys = [] extra_keys = [] mismatching_keys = [] mismatching_values = {} if isinstance(expected_val, dict) and isinstance(actual_val, dict): # Expected value is a dict, iterate recursively if expected_val: missing_sub_keys, mismatching_sub_keys, mismatching_sub_values, extra_sub_keys = _check_subset_dict( expected=expected_val, actual=actual_val, subset_lists=subset_lists, prefix=full_path, ) missing_keys.extend(missing_sub_keys) mismatching_keys.extend(mismatching_sub_keys) mismatching_values.update(mismatching_sub_values) extra_keys.extend(extra_sub_keys) elif actual_val: # expected empty dict but got a populated one mismatching_keys.append(full_path) mismatching_values[full_path] = { 'expected': expected_val, 'actual': actual_val, } extra_keys.extend(get_all_paths(actual_val, current_path=full_path)) elif isinstance(actual_val, list) and (subset_lists or isinstance(expected_val, list)): # Expected value is a list, iterate recursively if expected_val: missing_sub_keys, mismatching_sub_keys, mismatching_sub_values, extra_sub_keys = _check_subset_list( expected=expected_val, actual=actual_val, subset_lists=subset_lists, prefix=full_path, ) missing_keys.extend(missing_sub_keys) mismatching_keys.extend(mismatching_sub_keys) mismatching_values.update(mismatching_sub_values) extra_keys.extend(extra_sub_keys) elif actual_val: # expected empty list but got a populated one mismatching_keys.append(full_path) mismatching_values[full_path] = { 'expected': expected_val, 'actual': actual_val, } extra_keys.extend(get_all_paths(actual_val, current_path=full_path)) elif expected_val != actual_val: mismatching_keys.append(full_path) mismatching_values[full_path] = { 'expected': expected_val, 'actual': actual_val, } return sorted(missing_keys), sorted(mismatching_keys), mismatching_values, sorted(extra_keys)
def assert_not_expected(not_expected, actual, msg=None): """ Assert that the given not_expected values are not in actual Note that this allows the keys to exist (be present) so long as they have different values. """ (missing, mismatch, mismatch_values, extra) = _check_subset_dict(not_expected, actual, True) count = len(get_all_paths(not_expected)) # For cases where we are sub-setting lists but the list actual is None, call mismatches good and count # them among the missing. for field, details in six.iteritems(mismatch_values): if details['actual'] is None and details['expected'] is not None: missing.append(field) if len(missing) != count: msg = make_error_msg_header(msg) msg = make_not_expected_error_message( set(get_all_paths(not_expected)) - set(missing), msg ) msg += make_failed_comparision_error_message( 'NOT expected', pprint.pformat(not_expected), pprint.pformat(actual), ) raise AssertionError(msg)
def _check_subset_dict(expected, actual, subset_lists=False, prefix=None): """ Contrasts `expected` and `actual` dicts and annotates missing/mismatching values. """ missing_keys = [] extra_keys = [] mismatching_keys = [] mismatching_values = {} if not isinstance(expected, dict): raise AssertionError('expected value is not a dict') if not isinstance(actual, dict): raise AssertionError('actual value is not a dict') for expected_key, expected_val in six.iteritems(expected): full_path = expected_key if '.' in full_path and '{' not in full_path: full_path = '{{{}}}'.format(full_path) if prefix is not None: full_path = '{}.{}'.format(prefix, full_path) if expected_key not in actual: for missing_sub_path in get_all_paths(expected_val): if missing_sub_path: missing_keys.append('{}.{}'.format(full_path, missing_sub_path)) else: missing_keys.append(full_path) continue actual_val = actual[expected_key] missing_sub_keys, mismatching_sub_keys, mismatching_sub_values, extra_sub_keys = _compare_values( expected_val, actual_val, full_path, subset_lists=subset_lists ) missing_keys.extend(missing_sub_keys) mismatching_keys.extend(mismatching_sub_keys) mismatching_values.update(mismatching_sub_values) extra_keys.extend(extra_sub_keys) continue # check for extra unexpected paths for actual_key, actual_val in six.iteritems(actual): full_path = actual_key if '.' in full_path and '{' not in full_path: full_path = '{{{}}}'.format(full_path) if prefix is not None: full_path = '{}.{}'.format(prefix, full_path) if actual_key not in expected: extra_keys.extend(get_all_paths(actual_val, current_path=full_path)) continue return sorted(missing_keys), sorted(mismatching_keys), mismatching_values, sorted(extra_keys)
def test_018_get_all_paths(self): path_list = [ 'foo.bar', 'foo.{bar.baz}', 'foo.{yea_bar.baz}.gar', 'foo.aba_bar.0', 'foo.sba_bar.0.baz', 'foo.nu_bar.0.0.baz', 'foo.ba_bar.2.baz', 'foo.re_bar.{2}.baz', '{record_transaction.0}.inputs.foo', 'transaction.metadata.references.0.reference_type', 'transaction.metadata.references.0.reference_ids.0', ] out = {} # type: Dict[six.text_type, Any] for path in path_list: path_put(out, path, 'blah_blah') self.assertEqual(sorted(path_list), sorted(get_all_paths(out)))
def assert_not_present(not_present, actual, msg=None): """ Assert that none of the keys in not_present exist in actual """ present = [] for path in get_all_paths(not_present): try: path_get(actual, path) present.append(path) except (KeyError, IndexError): pass if present: msg = make_error_msg_header(msg, 'Not present') msg += make_failed_comparision_error_message( 'SHOULD NOT be present', pprint.pformat(present), pprint.pformat(actual), ) raise AssertionError(msg)
def assert_not_present( not_present, # type: Union[Mapping, List, Tuple, AbstractSet] actual, # type: Union[Mapping, List, Tuple, AbstractSet] msg=None, # type: Optional[six.text_type] ): # type: (...) -> None """ Assert that none of the keys in not_present exist in actual """ present = [] # type: List[six.text_type] for path in get_all_paths(not_present): try: path_get(actual, path) present.append(path) except (KeyError, IndexError): pass if present: msg = make_error_msg_header(msg, 'Not present') msg += make_failed_comparision_error_message( 'SHOULD NOT be present', pprint.pformat(present), pprint.pformat(actual), ) raise AssertionError(msg)
def test_021_path_listing_for_empty_structures(self): self.assertEqual(get_all_paths({}), []) self.assertEqual(get_all_paths([]), [])
def test_020_path_listing_for_complex_dict(self): data = { 'foo': { 'aba_bar': ['test'], 'ba_bar': [{}, { 'baz': 'test' }, {}], 'bar': 'test', 'bar.baz': 'test', 'nu_bar': [[{ 'baz': 'test' }]], 're_bar': { '2': { 'baz': 'test' } }, 'sba_bar': [{ 'baz': 'test' }, [], {}], 'yea_bar.baz': { 'gar': 'test' }, }, 'record_transaction.0': { 'inputs': { 'bar': [], 'foo': 'test', 're_bar': {}, }, }, 'transaction': { 'metadata': { 'references': [ { 'reference_ids': ['blah_blah'], 'reference_type': 'blah_blah' }, {}, ], }, }, } actual = get_all_paths(data, allow_blank=True) expected = [ 'foo.aba_bar.0', 'foo.ba_bar.0', 'foo.ba_bar.1.baz', 'foo.ba_bar.2', 'foo.bar', 'foo.nu_bar.0.0.baz', 'foo.re_bar.{2}.baz', 'foo.sba_bar.0.baz', 'foo.sba_bar.1', 'foo.sba_bar.2', 'foo.{bar.baz}', 'foo.{yea_bar.baz}.gar', 'transaction.metadata.references.0.reference_ids.0', 'transaction.metadata.references.0.reference_type', 'transaction.metadata.references.1', '{record_transaction.0}.inputs.bar', '{record_transaction.0}.inputs.foo', '{record_transaction.0}.inputs.re_bar', ] self.assertEqual(sorted(actual), sorted(expected))
def _finalize_test_case(self, active_string, location, _): # type: (six.text_type, int, Optional[ParseResults]) -> None """ Called by PyParsing at the end of each test case. :param active_string: The contents of the fixture file :param location: The file location of the current parsing activity """ self._fixture_source.append('') if self._working_action_case: # We're done parsing the test case and still need to wrap up the last action in the test case for dc in get_all_directives(): dc().post_parse_test_case_action( self._working_action_case, self._working_test_case or self._global_directives, ) self._working_action_case = {} if not self._working_test_case: # just a blank line before any test cases, probably after globals or an extra blank line between tests return self._working_test_case['line_number'] = self._working_test_case_line_number self._working_test_case_line_number = 0 self._working_test_case['fixture_name'] = self._fixture_name self._working_test_case['fixture_file_name'] = self._fixture_file_name self._working_test_case['source'] = self._working_test_case_source line_number = get_parse_line_number(location, active_string) if not self._working_test_case.get('name'): raise FixtureSyntaxError( '{}:{}: Test case without name'.format(self._fixture_file_name, line_number), file_name=self._fixture_file_name, line_number=line_number - 1, ) if not self._working_test_case.get('description'): raise FixtureSyntaxError( '{}:{}: Test case without description'.format(self._fixture_file_name, line_number), file_name=self._fixture_file_name, line_number=line_number - 1, ) if not self._working_test_case.get('actions') and not self._global_directives: raise FixtureSyntaxError( '{}:{}: Empty test case'.format(self._fixture_file_name, line_number), file_name=self._fixture_file_name, line_number=line_number - 1, ) if self._global_directives: # merge, but make sure current overlays global where there is conflict test_case = {} # type: TestCase for path in get_all_paths(self._global_directives, allow_blank=True): try: value = path_get(self._global_directives, path) path_put(test_case, path, copy.copy(value)) except (KeyError, IndexError): raise FixtureSyntaxError( 'Invalid path: `{}`'.format(path), file_name=self._fixture_file_name, line_number=line_number, ) for path in get_all_paths(self._working_test_case, allow_blank=True): try: path_put(test_case, path, path_get(self._working_test_case, path)) except (KeyError, IndexError): raise FixtureSyntaxError( 'Invalid path: `{}`'.format(path), file_name=self._fixture_file_name, line_number=line_number, ) for directive_class in get_all_directives(): directive_class().post_parse_test_case(test_case) else: for directive_class in get_all_directives(): directive_class().post_parse_test_case(self._working_test_case) test_case = copy.deepcopy(self._working_test_case) test_case['fixture_source'] = self._fixture_source self.test_cases.append(test_case) self._working_test_case.clear() self._working_test_case_source = []
def _compare_values( expected_val, # type: Any actual_val, # type: Any full_path=None, # type: Optional[six.text_type] subset_lists=False, # type: bool ): # type: (...) -> CompareReturn missing_keys = [] # type: List[six.text_type] extra_keys = [] # type: List[six.text_type] mismatching_keys = [] # type: List[six.text_type] mismatching_values = {} # type: Dict[six.text_type, Dict[six.text_type, Any]] if isinstance(expected_val, dict) and isinstance(actual_val, dict): # Expected value is a dict, iterate recursively if expected_val: missing_sub_keys, mismatching_sub_keys, mismatching_sub_values, extra_sub_keys = _check_subset_dict( expected=expected_val, actual=actual_val, subset_lists=subset_lists, prefix=full_path, ) missing_keys.extend(missing_sub_keys) mismatching_keys.extend(mismatching_sub_keys) mismatching_values.update(mismatching_sub_values) extra_keys.extend(extra_sub_keys) elif actual_val: # expected empty dict but got a populated one mismatching_keys.append(full_path or '') mismatching_values[full_path or ''] = { 'expected': expected_val, 'actual': actual_val, } extra_keys.extend(get_all_paths(actual_val, current_path=full_path or '')) elif isinstance(actual_val, list) and (subset_lists or isinstance(expected_val, list)): # Expected value is a list, iterate recursively if expected_val: missing_sub_keys, mismatching_sub_keys, mismatching_sub_values, extra_sub_keys = _check_subset_list( expected=expected_val, actual=actual_val, subset_lists=subset_lists, prefix=full_path, ) missing_keys.extend(missing_sub_keys) mismatching_keys.extend(mismatching_sub_keys) mismatching_values.update(mismatching_sub_values) extra_keys.extend(extra_sub_keys) elif actual_val: # expected empty list but got a populated one mismatching_keys.append(full_path or '') mismatching_values[full_path or ''] = { 'expected': expected_val, 'actual': actual_val, } extra_keys.extend(get_all_paths(actual_val, current_path=full_path or '')) elif expected_val != actual_val: mismatching_keys.append(full_path or '') mismatching_values[full_path or ''] = { 'expected': expected_val, 'actual': actual_val, } return sorted(missing_keys), sorted(mismatching_keys), mismatching_values, sorted(extra_keys)