blob: c582d3299ff4832601aa911cc1a15cd13462ea37 [file] [log] [blame]
Kuang-che Wu88875db2017-07-20 10:47:53 +08001# Copyright 2017 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Test cli module."""
5
6from __future__ import print_function
7import random
8import shutil
9import tempfile
10import unittest
11import StringIO
12
13import mock
14
15from bisect_kit import cli
16from bisect_kit import core
17
18
19class TestCli(unittest.TestCase):
20 """Test functions in cli module."""
21
22 def test_argtype_int(self):
23 self.assertEqual(cli.argtype_int('123456'), '123456')
24 self.assertEqual(cli.argtype_int('-123456'), '-123456')
25 with self.assertRaises(cli.ArgTypeError):
26 cli.argtype_int('foobar')
27
28 def test_argtype_notempty(self):
29 self.assertEqual(cli.argtype_notempty('123456'), '123456')
30 with self.assertRaises(cli.ArgTypeError):
31 cli.argtype_notempty('')
32
33 def test_argtype_multiplexer(self):
34 argtype = cli.argtype_multiplexer(cli.argtype_int, 'foobar', r'^r\d+$')
35 self.assertEqual(argtype('123456'), '123456')
36 self.assertEqual(argtype('foobar'), 'foobar')
37 self.assertEqual(argtype('r123'), 'r123')
38
39 with self.assertRaises(cli.ArgTypeError):
40 argtype('hello')
41
42 def test_argtype_multiplier(self):
43 argtype = cli.argtype_multiplier(cli.argtype_int)
44 self.assertEqual(argtype('123'), ('123', 1))
45 self.assertEqual(argtype('123*5'), ('123', 5))
46
47 # Make sure there is multiplier example in the message.
48 with self.assertRaisesRegexp(cli.ArgTypeError, r'\d+\*\d+'):
49 argtype('hello')
50
51 def test_argtype_path(self):
52 self.assertEqual(cli.argtype_dir_path('/'), '/')
53 self.assertEqual(cli.argtype_dir_path('/etc/'), '/etc')
54
55 with self.assertRaises(cli.ArgTypeError):
56 cli.argtype_dir_path('')
57 with self.assertRaises(cli.ArgTypeError):
58 cli.argtype_dir_path('/foo/bar/not/exist/path')
59 with self.assertRaises(cli.ArgTypeError):
60 cli.argtype_dir_path('/etc/passwd')
61
62 def test_collect_bisect_result_values(self):
63 # pylint: disable=protected-access
64 values = []
65 cli._collect_bisect_result_values(values, '')
66 self.assertEqual(values, [])
67
68 values = []
69 cli._collect_bisect_result_values(values, 'foo\n')
70 cli._collect_bisect_result_values(values, 'bar\n')
71 self.assertEqual(values, [])
72
73 values = []
74 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=1 2\n')
75 cli._collect_bisect_result_values(values, 'fooBISECT_RESULT_VALUES=3 4\n')
76 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=5\n')
77 self.assertEqual(values, [1, 2, 5])
78
79 with self.assertRaises(core.ExecutionFatalError):
80 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=hello\n')
81
82
83class DummyDomain(core.BisectDomain):
84 """Dummy subclass of BisectDomain."""
85 revtype = staticmethod(cli.argtype_notempty)
86
87 @staticmethod
88 def add_init_arguments(parser):
89 parser.add_argument('--num', required=True, type=int)
90 parser.add_argument('--ans', type=int)
91 parser.add_argument('--old_p', type=float, default=0.0)
92 parser.add_argument('--new_p', type=float, default=1.0)
93
94 @staticmethod
95 def init(opts):
96 config = dict(
97 ans=opts.ans,
98 old_p=opts.old_p,
99 new_p=opts.new_p,
100 old=opts.old,
101 new=opts.new)
102
103 revlist = map(str, range(opts.num))
104 return config, revlist
105
106 def __init__(self, config):
107 self.config = config
108
109 def setenv(self, env, rev):
110 env['ANS'] = str(self.config['ans'])
111 env['OLD_P'] = str(self.config['old_p'])
112 env['NEW_P'] = str(self.config['new_p'])
113 env['REV'] = rev
114
115
116@mock.patch('bisect_kit.common.config_logging', mock.Mock())
117# This is not an python "interface". pylint: disable=interface-not-implemented
118class TestBisectorCommandLineInterface(unittest.TestCase):
119 """Test cli.BisectorCommandLineInterface class."""
120
121 def setUp(self):
122 self.session_base = tempfile.mkdtemp()
123 self.patcher = mock.patch.object(cli, 'DEFAULT_SESSION_BASE',
124 self.session_base)
125 self.patcher.start()
126
127 def tearDown(self):
128 shutil.rmtree(self.session_base)
129 self.patcher.stop()
130
131 def test_run_true(self):
132 bisector = cli.BisectorCommandLineInterface(DummyDomain)
133 bisector.main('init', '--num=10', '--old=0', '--new=9')
134 bisector.main('config', 'switch', 'true')
135 bisector.main('config', 'eval', 'true')
136 with self.assertRaises(core.VerificationFailed):
137 bisector.main('run')
138
139 def test_run_false(self):
140 bisector = cli.BisectorCommandLineInterface(DummyDomain)
141 bisector.main('init', '--num=10', '--old=0', '--new=9')
142 bisector.main('config', 'switch', 'true')
143 bisector.main('config', 'eval', 'false')
144 with self.assertRaises(core.VerificationFailed):
145 bisector.main('run')
146
147 def test_simple(self):
148 bisector = cli.BisectorCommandLineInterface(DummyDomain)
149 bisector.main('init', '--num=20', '--old=3', '--new=15')
150 bisector.main('config', 'switch', 'true')
151 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
152 bisector.main('run')
153 self.assertEqual(bisector.strategy.get_best_guess(), 7)
154
155 def test_switch_fail(self):
156 bisector = cli.BisectorCommandLineInterface(DummyDomain)
157 bisector.main('init', '--num=20', '--old=3', '--new=15')
158 bisector.main('config', 'switch', 'false')
159 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
160 with self.assertRaises(core.ExecutionFatalError):
161 bisector.main('run')
162
163 def test_run_classic(self):
164 bisector = cli.BisectorCommandLineInterface(DummyDomain)
165 bisector.main('init', '--num=200', '--old=0', '--new=99')
166 bisector.main('config', 'switch', 'true')
167 bisector.main('config', 'eval', 'false')
168
169 def do_evaluate(_cmd, _domain, rev):
170 if int(rev) < 42:
171 return 'old', []
172 return 'new', []
173
174 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
175 # two verify
176 with mock.patch(
177 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
178 bisector.main('next')
179 self.assertEqual(mock_stdout.getvalue(), '99\n')
180 bisector.main('run', '-1')
181 with mock.patch(
182 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
183 bisector.main('next')
184 self.assertEqual(mock_stdout.getvalue(), '0\n')
185 bisector.main('run', '-1')
186 self.assertEqual(bisector.strategy.get_range(), (0, 99))
187
188 bisector.main('run', '-1')
189 self.assertEqual(bisector.strategy.get_range(), (0, 49))
190
191 bisector.main('run')
192 self.assertEqual(bisector.strategy.get_best_guess(), 42)
193 self.assertEqual(bisector.states.stats['eval_count'], 2 + 7)
194
195 with mock.patch(
196 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
197 bisector.main('next')
198 self.assertEqual(mock_stdout.getvalue(), 'done\n')
199
200 def test_run_noisy(self):
201 bisector = cli.BisectorCommandLineInterface(DummyDomain)
202 bisector.main('init', '--num=100', '--old=0', '--new=99',
203 '--noisy=old=1/10,new=9/10')
204 bisector.main('config', 'switch', 'true')
205 bisector.main('config', 'eval', 'false')
206
207 def do_evaluate(_cmd, _domain, rev):
208 if int(rev) < 42:
209 p = 0.1
210 else:
211 p = 0.9
212 if random.random() < p:
213 return 'new', []
214 return 'old', []
215
216 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
217 bisector.main('run')
218 self.assertEqual(bisector.strategy.get_best_guess(), 42)
219
220 with mock.patch(
221 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
222 bisector.main('next')
223 # There might be small math error near the boundary of confidence.
224 self.assertIn(mock_stdout.getvalue(), ['done\n', '41\n', '42\n'])
225
226 def test_cmd_old_and_new(self):
227 """Tests cmd_old and cmd_new"""
228 bisector = cli.BisectorCommandLineInterface(DummyDomain)
229 bisector.main('init', '--num=100', '--old=0', '--new=99')
230 bisector.main('config', 'switch', 'true')
231 bisector.main('config', 'eval', 'false')
232 bisector.main('old', '0')
233 bisector.main('new', '99')
234 bisector.main('run', '-1')
235 self.assertEqual(bisector.strategy.get_range(), (0, 49))
236
237 bisector.main('old', '20')
238 bisector.main('new', '40')
239 bisector.main('run', '-1')
240 self.assertEqual(bisector.strategy.get_range(), (20, 30))
241
242 bisector.main('skip', '20*10')
243 bisector.main('skip', '30*10')
244 self.assertEqual(bisector.strategy.get_range(), (20, 30))
245
246 def test_cmd_switch(self):
247 """Test cmd_switch"""
248 bisector = cli.BisectorCommandLineInterface(DummyDomain)
249 bisector.main('init', '--num=100', '--old=0', '--new=99')
250 bisector.main('config', 'switch', 'true')
251 bisector.main('config', 'eval', 'false')
252
253 switched = []
254
255 def do_switch(_cmd, _domain, rev):
256 switched.append(rev)
257
258 with mock.patch('bisect_kit.cli.do_switch', do_switch):
259 bisector.main('switch', 'next')
260 bisector.main('switch', '3')
261 bisector.main('switch', '5')
262 bisector.main('switch', '4')
263 self.assertEqual(switched, ['99', '3', '5', '4'])
264
265 def test_cmd_view(self):
266 bisector = cli.BisectorCommandLineInterface(DummyDomain)
267 bisector.main('init', '--num=100', '--old=10', '--new=90')
268 with mock.patch.object(DummyDomain, 'view') as mock_view:
269 bisector.main('view')
270 mock_view.assert_called_with('10', '90')
271
272 def test_cmd_config_confidence(self):
273 bisector = cli.BisectorCommandLineInterface(DummyDomain)
274 bisector.main('init', '--num=100', '--old=10', '--new=90',
275 '--confidence=0.75')
276
277 with self.assertRaises(core.ExecutionFatalError):
278 bisector.main('config', 'confidence', 'foo')
279 with self.assertRaises(core.ExecutionFatalError):
280 bisector.main('config', 'confidence', '0.9', '0.8')
281
282 self.assertEqual(bisector.states.config['confidence'], 0.75)
283 bisector.main('config', 'confidence', '0.875')
284 self.assertEqual(bisector.states.config['confidence'], 0.875)
285
286 def test_cmd_config_noisy(self):
287 bisector = cli.BisectorCommandLineInterface(DummyDomain)
288 bisector.main('init', '--num=100', '--old=10', '--new=90',
289 '--noisy=new=9/10')
290
291 with self.assertRaises(core.ExecutionFatalError):
292 bisector.main('config', 'noisy', 'hello', 'world')
293
294 self.assertEqual(bisector.states.config['noisy'], 'new=9/10')
295 bisector.main('config', 'noisy', 'old=1/10,new=8/9')
296 self.assertEqual(bisector.states.config['noisy'], 'old=1/10,new=8/9')
297
298 def test_current_status(self):
299 bisector = cli.BisectorCommandLineInterface(DummyDomain)
300 result = bisector.current_status()
301 self.assertEqual(result.get('inited'), False)
302
303 bisector.main('init', '--num=100', '--old=10', '--new=90',
304 '--noisy=new=9/10')
305 result = bisector.current_status()
306 self.assertEqual(result.get('inited'), True)
307
308
309if __name__ == '__main__':
310 unittest.main()