| OLD | NEW |
| (Empty) |
| 1 #!/usr/bin/env python | |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 3 # Use of this source code is governed by a BSD-style license that can be | |
| 4 # found in the LICENSE file. | |
| 5 | |
| 6 import itertools | |
| 7 import os | |
| 8 import sys | |
| 9 import unittest | |
| 10 | |
| 11 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| 12 sys.path.insert(0, ROOT_DIR) | |
| 13 | |
| 14 from utils.short_expression_finder import ShortExpressionFinder, partitions | |
| 15 | |
| 16 | |
| 17 def matching_configs(expr, variables_and_values): | |
| 18 """Return configs for which expr evaluates to true.""" | |
| 19 variables, values = zip(*variables_and_values) | |
| 20 return tuple( | |
| 21 config for config in itertools.product(*values) | |
| 22 if eval(expr, dict(zip(variables, config))) | |
| 23 ) | |
| 24 | |
| 25 | |
| 26 class Tests(unittest.TestCase): | |
| 27 def test_simple(self): | |
| 28 sef = ShortExpressionFinder([('var', [17])]) | |
| 29 self.assertEqual('var==17', sef.get_expr([(17,)])) | |
| 30 | |
| 31 def test_two_variables_four_values(self): | |
| 32 vals1, vals2 = (1, 2), ("ab", "cd") | |
| 33 sef = ShortExpressionFinder([('var1', vals1), ('var2', vals2)]) | |
| 34 all_configs = tuple(itertools.product(vals1, vals2)) | |
| 35 for val1 in vals1: | |
| 36 for val2 in vals2: | |
| 37 self.assertEqual('var1==%d and var2=="%s"' % (val1, val2), | |
| 38 sef.get_expr([(val1, val2)])) | |
| 39 for val1 in vals1: | |
| 40 self.assertEqual('var1==%d and (var2=="ab" or var2=="cd")' % val1, | |
| 41 sef.get_expr([(val1, "ab"), (val1, "cd")])) | |
| 42 for val2 in vals2: | |
| 43 self.assertEqual('(var1==1 or var1==2) and var2=="%s"' % val2, | |
| 44 sef.get_expr([(1, val2), (2, val2)])) | |
| 45 self.assertEqual('(var1==1 or var1==2) and (var2=="ab" or var2=="cd")', | |
| 46 sef.get_expr(all_configs)) | |
| 47 | |
| 48 def check_expr(self, expr, variables_and_values): | |
| 49 """Check that ShortExpressionFinder returns an expression that's logically | |
| 50 equivalent to |expr| and equally simple, when given the matching configs. | |
| 51 """ | |
| 52 configs = matching_configs(expr, variables_and_values) | |
| 53 output_expr = ShortExpressionFinder(variables_and_values).get_expr(configs) | |
| 54 output_configs = matching_configs(output_expr, variables_and_values) | |
| 55 self.assertEqual(configs, output_configs) | |
| 56 self.assertEqual(expr.count('=='), output_expr.count('==')) | |
| 57 self.assertEqual(expr.count('('), output_expr.count('(')) | |
| 58 | |
| 59 def test_single_nontrivial_region(self): | |
| 60 self.check_expr('((OS=="linux" or OS=="mac" or OS=="win") and chromeos==0)' | |
| 61 ' or (OS=="linux" and chromeos==1)', | |
| 62 [('OS', 'linux mac win'.split()), ('chromeos', (0, 1))]) | |
| 63 | |
| 64 self.check_expr('(OS=="linux" or OS=="mac" or OS=="win") and chromeos==0', | |
| 65 [('OS', 'linux mac win'.split()), ('chromeos', (0, 1))]) | |
| 66 | |
| 67 self.check_expr('OS=="linux" and (chromeos==0 or chromeos==1)', | |
| 68 [('OS', 'linux mac win'.split()), ('chromeos', (0, 1))]) | |
| 69 | |
| 70 def test_multiple_nontrivial_regions(self): | |
| 71 # two disjoint regions of 1*2*1=2 and 2*1*2=4 configs, no overlap | |
| 72 self.check_expr( | |
| 73 '(p==2 and (q=="a" or q=="b") and r==10)' | |
| 74 ' or ((p==1 or p==2) and q=="c" and (r==8 or r==10))', | |
| 75 [('p', (1, 2, 3)), ('q', ('a', 'b', 'c')), ('r', (8, 9, 10))]) | |
| 76 | |
| 77 # two regions of 4 and 8 configs with overlap | |
| 78 self.check_expr( | |
| 79 '((p==1 or p==2) and (q=="a" or q=="b") and r==9)' | |
| 80 ' or ((p==2 or p==3) and q=="a" and (r==8 or r==9))', | |
| 81 [('p', (1, 2, 3)), ('q', ('a', 'b', 'c')), ('r', (8, 9, 10))]) | |
| 82 | |
| 83 # All configs except (p, q, r) == (2, 4, 6). There are simpler expressions | |
| 84 # for this that don't fit ShortExpressionFinder's grammar. | |
| 85 self.check_expr('((p==1 or p==2) and (q==3 or q==4) and r==5)' | |
| 86 ' or ((p==1 or p==2) and q==3 and r==6)' | |
| 87 ' or (p==1 and q==4 and r==6)', | |
| 88 [('p', (1, 2)), ('q', (3, 4)), ('r', (5, 6))]) | |
| 89 | |
| 90 def test_100_variables(self): | |
| 91 # This is kind of a cheat because the complexity is ((2^k)-1)^n for n | |
| 92 # variables of k values each. With k=2 this would be hopelessly slow. | |
| 93 self.check_expr(' and '.join('var%d=="val%d"' % (n, n) for n in range(100)), | |
| 94 [('var%d' % n, ('val%d' % n,)) for n in range(100)]) | |
| 95 | |
| 96 def test_short_expression_finder_failure(self): | |
| 97 # End users should never see these failures so it doesn't matter much how | |
| 98 # exactly they fail. | |
| 99 self.assertRaises(AssertionError, ShortExpressionFinder, []) | |
| 100 self.assertRaises(AssertionError, | |
| 101 ShortExpressionFinder, [('x', (1, 2)), ('no_values', ())]) | |
| 102 self.assertRaises(TypeError, | |
| 103 ShortExpressionFinder, [('bad_type', (1, 2.5))]) | |
| 104 self.assertRaises(AssertionError, | |
| 105 ShortExpressionFinder, [('bad name', (1, 2))]) | |
| 106 self.assertRaises(AssertionError, | |
| 107 ShortExpressionFinder, [('x', ('bad value',))]) | |
| 108 self.assertRaises(TypeError, | |
| 109 ShortExpressionFinder, [('x', (1, 'mixed_values'))]) | |
| 110 self.assertRaises(AssertionError, | |
| 111 ShortExpressionFinder([('no_configs', (1, 2))]).get_expr, []) | |
| 112 self.assertRaises(AssertionError, | |
| 113 ShortExpressionFinder([('no_such_value', (1, 2))]).get_expr, [(3,)]) | |
| 114 self.assertRaises(AssertionError, | |
| 115 ShortExpressionFinder([('wrong_length', (1, 2))]).get_expr, [(1, 3)]) | |
| 116 | |
| 117 def test_partitions(self): | |
| 118 self.assertEqual(((None, None),), tuple(partitions(0, 2))) | |
| 119 self.assertEqual((), tuple(partitions(1, 2))) | |
| 120 | |
| 121 def count_partitions(parts, remaining): | |
| 122 parts = tuple(parts) | |
| 123 if parts == ((None, None),): | |
| 124 self.assertEqual(0, remaining) | |
| 125 return 1 | |
| 126 return sum(count_partitions(ns, remaining - n) for n, ns in parts) | |
| 127 | |
| 128 # http://oeis.org/A002865 | |
| 129 expected = [1, 0, 1, 1, 2, 2, 4, 4, 7, 8, 12, 14, 21, 24, 34, 41, 55, 66] | |
| 130 for n, count in enumerate(expected): | |
| 131 actual_count = count_partitions(partitions(n, 2), n) | |
| 132 self.assertEqual(count, actual_count) | |
| 133 | |
| 134 # This also checks that the call doesn't compute all | |
| 135 # 23,127,843,459,154,899,464,880,444,632,250 partitions up front. | |
| 136 self.assertEqual(501, len(tuple(partitions(1000, 1)))) | |
| 137 | |
| 138 | |
| 139 if __name__ == '__main__': | |
| 140 VERBOSE = '-v' in sys.argv | |
| 141 unittest.main() | |
| OLD | NEW |