def test_accepts_other(self): '''check that expanding non-processed objects returns exactly the object''' for no_prc_case in self.no_proc_cases: instance = ExpandCombinations(self.input_case[no_prc_case]) submsg = repr(self.input_case[no_prc_case]) with self.subTest(msg=submsg, case=no_prc_case): self.assertIsInstance( instance, ExpandCombinations, 'for {!r}'.format(self.input_case[no_prc_case], ctx='instance')) iterator = iter(instance) with self.subTest(msg=submsg, case=no_prc_case, ctx='iterator'):
def test_uses_copy_of_flat_dict(self): '''check that content of a dict is copied to output without references to input''' for exp_case in self.fd_cases: src_obj = self.input_case[exp_case] # reference to input object src_keys = list(src_obj.keys()) if not src_obj: with self.subTest(msg=repr(src_keys), ctx='empty'): # take a (fresh) copy of input case that is safe to modify in_obj = odict(copy.deepcopy(src_obj)) instance = ExpandCombinations(in_obj) captured = [] for out_ele in instance: captured.append(out_ele) self.assertEqual(1, len(captured), 'should be only single output element') self.assertEqual(self.expected_out[exp_case], captured, 'exp == out list') in_obj['new'] = 'break' self.assertEqual(self.expected_out[exp_case], captured, 'input key added') continue for perm in permutations(src_keys): with self.subTest(msg=repr(perm), ctx='permutation'): # take a (fresh) copy of input case that is safe to modify in_obj = odict(copy.deepcopy(src_obj)) for key in perm: # adjust odict keys to match permutation order in_obj.move_to_end(key) self.assertEqual(perm, list(in_obj.keys()), 'keys should match permutation') instance = ExpandCombinations(in_obj) captured = [] for out_ele in instance: captured.append(out_ele) # «single» output should be same as input for this simple case self.assertEqual(src_obj, out_ele, 'src == out ele') self.assertEqual(self.expected_out[exp_case][0], out_ele, 'exp == out ele') self.assertEqual(1, len(captured), 'should be only single output element') self.assertEqual(self.expected_out[exp_case], captured, 'exp == out list') in_obj[perm[0]] = 'changed' self.assertEqual(self.expected_out[exp_case], captured, 'input key changed') del in_obj[perm[-1]] self.assertEqual(self.expected_out[exp_case], captured, 'input key deleted') in_obj['new'] = 'break' self.assertEqual(self.expected_out[exp_case], captured, 'input key added')
def test_accepts_flat_dict(self): '''check that a flat dictionary is valid, and uses custom iterator''' for exp_case in self.fd_cases: instance = ExpandCombinations(self.input_case[exp_case]) with self.subTest(): self.assertIsInstance( instance, ExpandCombinations, 'for {!r}'.format(self.input_case[exp_case])) iterator = iter(instance) with self.subTest(): self.assertIsInstance( iterator, ExpandCombinations, 'for {!r}'.format(self.input_case[exp_case]))
def test_gen_variant_dict(self): '''check that dictionary entries generate a variant per list element''' for exp_case in self.vd_cases: expect_ele_cnt = len(self.expected_out[exp_case]) src_obj = self.input_case[exp_case] # reference to input object for perm_set in multiple_key_permutations([d_ref for d_ref in self._get_dict_from_expandable(src_obj)]): # for each key permutation (all dictionaries) with self.subTest(msg=repr(perm_set), ctx='permutation', case=exp_case): # take a (fresh) copy of the input case that is safe to modify in_obj = odict(copy.deepcopy(src_obj)) # get (matching) references to the dictionaries in the copy in_ref = [ref for ref in self._get_dict_from_expandable(in_obj)] for idx, ref in enumerate(in_ref): # set key order for permutation set for key in perm_set[idx]: ref.move_to_end(key) self.assertEqual( perm_set[idx], list(ref.keys()), 'keys should match permutation') instance = ExpandCombinations(in_obj) # using adjusted key sequence captured = [] # would prefer set, but dict not hashable for out_ele in instance: with self.subTest(msg=repr(perm_set), ctx='inline'): # inline check; each output element should match a unique expected case with self.subTest(msg=repr(perm_set), ctx='duplicate', dup=out_ele): self.assertNotIn(out_ele, captured, 'generated ele not unique') captured.append(out_ele) self.assertIn( out_ele, self.expected_out[exp_case], 'generated ele not expected') self.assertEqual(expect_ele_cnt, len(captured), 'wrong output element count') # full post iteration check, elements should change one step to the next with self.subTest(msg=repr(perm_set), ctx='post check'): self._unordered_list_compare(self.expected_out[exp_case], captured) # make sure that (post iteration) input changes do not affect collected data for idx, perm in enumerate(perm_set): if isinstance(in_ref[idx][perm[0]], list): in_ref[idx][perm[0]].append('extra') subcase = 'input ele appended' else: in_ref[idx][perm[0]] = 'changed' subcase = 'input ele changed' with self.subTest(msg=repr(perm_set), ctx=subcase, prm=perm, idx=idx): self._unordered_list_compare(self.expected_out[exp_case], captured) del in_ref[idx][perm[-1]] with self.subTest(msg=repr(perm_set), ctx='input key deleted', prm=perm, idx=idx): self._unordered_list_compare(self.expected_out[exp_case], captured) in_ref[idx]['pluskey'] = 'added' with self.subTest(msg=repr(perm), ctx='input key added', prm=perm, idx=idx): self._unordered_list_compare(self.expected_out[exp_case], captured)
def test_accepts_flat_list(self): '''check that a flat list is valid, and uses standard iterator''' for exp_case in self.fl_cases: instance = ExpandCombinations(self.input_case[exp_case]) with self.subTest(): self.assertIsInstance( instance, ExpandCombinations, 'for {!r}'.format(self.input_case[exp_case])) iterator = iter(instance) with self.subTest(): self.assertNotIsInstance( iterator, ExpandCombinations, 'for {!r}, and should not be'.format(self.input_case[exp_case])) with self.subTest(): self.assertIsInstance( iterator, iter([]).__class__, # iterator, list_iterator, # NameError: name 'list_iterator' is not defined 'for {!r}'.format(self.input_case[exp_case]))
def test_flatten_nested(self): '''check that nested lists are flattened on output, and not references''' for nest_case in self.nl_cases: in_obj = copy.deepcopy(self.input_case[nest_case]) instance = ExpandCombinations(in_obj) captured = [] ele_idx = 0 for out_ele in instance: captured.append(out_ele) with self.subTest(msg=repr(self.input_case[nest_case]), ctx='for'): # Do an on the fly check while iterating self.assertEqual(self.expected_out[nest_case][ele_idx], out_ele) ele_idx += 1 with self.subTest(msg=repr(self.input_case[nest_case]), ctx='captured'): self.assertEqual(self.expected_out[nest_case], captured) # modify all elements in the input list for idx in range(len(self.input_case[nest_case])): in_obj[idx] = 'changed' # verify that the caputure ouput has not been changed with self.subTest(msg=repr(self.input_case[nest_case]), ctx='detect reference'): self.assertEqual(self.expected_out[nest_case], captured)
def test_uses_copyof_flat_list(self): '''check that the content of a flat list is not linked (referenced) by output''' for exp_case in self.fl_cases: in_obj = self.input_case[exp_case].copy() instance = ExpandCombinations(in_obj) captured = [] ele_idx = 0 for out_ele in instance: captured.append(out_ele) with self.subTest(msg=repr(self.input_case[exp_case]), ctx='iterate'): # Do an on the fly check self.assertEqual(self.input_case[exp_case][ele_idx], out_ele, 'src != out') self.assertEqual(in_obj[ele_idx], out_ele, 'in != out') # better be same # now modify the input element, and make sure that the output did not change too # in_obj[ele_idx] = '@' in_obj[ele_idx] = '@' self.assertNotEqual(in_obj[ele_idx], out_ele, 'mod in == out') self.assertEqual(self.input_case[exp_case][ele_idx], out_ele, 'src chg != out') ele_idx += 1 with self.subTest(msg=repr(self.input_case[exp_case]), ctx='post iteration'): self.assertEqual(self.input_case[exp_case], captured, 'expected == captured') if in_obj: # only not equal when input was not an empty list self.assertNotEqual(in_obj, captured, 'in != captured')
def mymain(*supplied_keys): '''wrapper for test/start code so that variables do not look like constants''' smpl_keys = supplied_keys samples = { 's1': {'key1': 'constant value', 'key2': ['option 1', 'option 2', ], }, 's2': {'key1': 'constant value', 'key2': ['option 1', 'option 2', ], 'key3': ['option 3', 'option 4', ], }, 's3': {'key1': 'default value', 'key2': [ 'option 1', {'key3': 'fixed value', 'key4': ['option 2', 'option 3'], }, {'key1': 'override', 'key2': 'keep key'}, ], }, 'list1': ['first', 'second', 'third', ], 'list2': ['dup', 'dup', 'third', ], 'l1': ['a', ['b', 'c'], 'd', ], 'l2': [ 'a', {'k1': [1, 2, ], 'k2': 'c'}, {'k3': ['a', 'b', ], 'k4': 5}, ], 'dup1': { 'cmn': 'always', 'hasdup': ['dup', 'dup', 'other'], }, 'equiv1': {'key': 'value', }, 'equiv2': {'key': ['value', ], }, 'equiv3': {'key': [{'key': 'value', }, ], }, 'equiv4': {'key': [{'key': [{'key': 'value', }, ], }, ], }, 'equiv5': {'other': [{'key': 'value'}, ], }, 'equiv6': {'key': [{'other': [{'key': 'value', }, ], }, ], }, 'equiv7': {'other1': [{'other2': [{'key': 'value', }, ], }, ], }, 'test0': 'simple string', 'test1': {'key': 'value', 'dummy': [], }, 'sub1': { 'family': 'bjt', 'footprint': '', 'package': ['TO220', { 'footprint': ['SIL', 'triangle', ], 'package': 'TO92', }, 'SOT23', ], }, 'sub2': { 'footprint': '', 'mounting': 'THT', 'package': [ { 'package': 'TO92', 'footprint': ['SIL', 'triangle', ], }, { 'package': 'SOT23', 'mounting': 'SMD', }, 'TO220', ], 'label': ['pin', '', ], 'type': ['NPN', 'PNP', ], 'pinout': ['BCE', 'BEC', 'CBE', 'CEB', 'EBC', 'ECB', ], }, 'sub3': { 'footprint': '', 'mounting': 'THT', 'package': ['TO220', { 'package': 'TO92', 'footprint': ['SIL', 'triangle', ], }, { 'package': 'SOT23', 'mounting': 'SMD', }, ], 'label': ['pin', '', ], 'type': ['NPN', 'PNP', ], 'pinout': ['BCE', 'BEC', 'CBE', 'CEB', 'EBC', 'ECB', ], }, 'meals': { 'appetizer': ['calamari', 'potatoe skins', 'cheesy nachos', 'escargot', ], 'entrée': [ 'chicken', {'entrée': ['white fish', 'rainbow trout'], 'wine': 'white'}, {'entrée': 'steak', 'wine': 'red'}, ], 'desert': ['pie', 'tiramisu', 'ice cream', 'apple crisp', ], }, } cheeses = ['cheddar', 'goat', 'feta', 'parmesan'] vegetables = ['avocado', 'mushrooms', 'onions', 'spinach'] meats = ['bacon', 'beef', 'pepperoni'] seafood = ['shrimp', 'anchovies', 'prawns'] samples['pizza'] = [ {'first': [cheeses, vegetables, meats, seafood, ], }, { 'first': [cheeses, vegetables, meats, seafood, ], 'second': [cheeses, vegetables, meats, seafood, ], }, { 'first': [cheeses, vegetables, meats, seafood, ], 'second': [cheeses, vegetables, meats, seafood, ], 'third': [cheeses, vegetables, meats, seafood, ], }, ] # print(samples['pizza']) # DEBUG samples['pizza2'] = { 'toppings': (cheeses, meats) } # print(samples['pizza2']) # DEBUG if len(sys.argv) > 1: smpl_keys = sys.argv[1:] pizza_keys = ['first', 'second', 'third'] for smpl in smpl_keys: if smpl not in samples: print('sample {!r} does not exist'.format(smpl)) continue print('\nsample {} input data:'.format(smpl)) print(samples[smpl]) test_iter = ExpandCombinations(samples[smpl]) dedup = [] # only used for pizza print('sample {} expanded outputs:'.format(smpl)) expansion_count = 0 for cmb in test_iter: expansion_count += 1 print(cmb) # pizza is special. Get the unique topping Combinations if smpl == 'pizza': cmb_toppings = [] for pkey in pizza_keys: if pkey in cmb: cmb_toppings.append(cmb[pkey]) cmb_toppings.sort() if cmb_toppings not in dedup: dedup.append(cmb_toppings) print('{} expanded combinations'.format(expansion_count)) if smpl == 'pizza': for cmb in dedup: print(cmb) print('{} unique pizza topping combinations'.format(len(dedup)))