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 |