blob: c9758a277a01f91389347cb1a071e9d6f3cb5f8d [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
Kuang-che Wue1b329e2018-03-31 11:39:33 +080016from bisect_kit import configure
17from bisect_kit import common
Kuang-che Wu88875db2017-07-20 10:47:53 +080018from bisect_kit import core
19
20
21class TestCli(unittest.TestCase):
22 """Test functions in cli module."""
23
24 def test_argtype_int(self):
25 self.assertEqual(cli.argtype_int('123456'), '123456')
26 self.assertEqual(cli.argtype_int('-123456'), '-123456')
27 with self.assertRaises(cli.ArgTypeError):
28 cli.argtype_int('foobar')
29
30 def test_argtype_notempty(self):
31 self.assertEqual(cli.argtype_notempty('123456'), '123456')
32 with self.assertRaises(cli.ArgTypeError):
33 cli.argtype_notempty('')
34
35 def test_argtype_multiplexer(self):
36 argtype = cli.argtype_multiplexer(cli.argtype_int, 'foobar', r'^r\d+$')
37 self.assertEqual(argtype('123456'), '123456')
38 self.assertEqual(argtype('foobar'), 'foobar')
39 self.assertEqual(argtype('r123'), 'r123')
40
41 with self.assertRaises(cli.ArgTypeError):
42 argtype('hello')
43
44 def test_argtype_multiplier(self):
45 argtype = cli.argtype_multiplier(cli.argtype_int)
46 self.assertEqual(argtype('123'), ('123', 1))
47 self.assertEqual(argtype('123*5'), ('123', 5))
48
49 # Make sure there is multiplier example in the message.
50 with self.assertRaisesRegexp(cli.ArgTypeError, r'\d+\*\d+'):
51 argtype('hello')
52
53 def test_argtype_path(self):
54 self.assertEqual(cli.argtype_dir_path('/'), '/')
55 self.assertEqual(cli.argtype_dir_path('/etc/'), '/etc')
56
57 with self.assertRaises(cli.ArgTypeError):
58 cli.argtype_dir_path('')
59 with self.assertRaises(cli.ArgTypeError):
60 cli.argtype_dir_path('/foo/bar/not/exist/path')
61 with self.assertRaises(cli.ArgTypeError):
62 cli.argtype_dir_path('/etc/passwd')
63
64 def test_collect_bisect_result_values(self):
65 # pylint: disable=protected-access
66 values = []
67 cli._collect_bisect_result_values(values, '')
68 self.assertEqual(values, [])
69
70 values = []
71 cli._collect_bisect_result_values(values, 'foo\n')
72 cli._collect_bisect_result_values(values, 'bar\n')
73 self.assertEqual(values, [])
74
75 values = []
76 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=1 2\n')
77 cli._collect_bisect_result_values(values, 'fooBISECT_RESULT_VALUES=3 4\n')
78 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=5\n')
79 self.assertEqual(values, [1, 2, 5])
80
81 with self.assertRaises(core.ExecutionFatalError):
82 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=hello\n')
83
Kuang-che Wu88518882017-09-22 16:57:25 +080084 def test_check_executable(self):
85 self.assertEqual(cli.check_executable('/bin/true'), None)
86
87 self.assertRegexpMatches(cli.check_executable('/'), r'Not a file')
88 self.assertRegexpMatches(cli.check_executable('LICENSE'), r'chmod')
89 self.assertRegexpMatches(cli.check_executable('what'), r'PATH')
90 self.assertRegexpMatches(cli.check_executable('eval-manually.sh'), r'\./')
91
Kuang-che Wu88875db2017-07-20 10:47:53 +080092
93class DummyDomain(core.BisectDomain):
94 """Dummy subclass of BisectDomain."""
95 revtype = staticmethod(cli.argtype_notempty)
96
97 @staticmethod
98 def add_init_arguments(parser):
99 parser.add_argument('--num', required=True, type=int)
100 parser.add_argument('--ans', type=int)
101 parser.add_argument('--old_p', type=float, default=0.0)
102 parser.add_argument('--new_p', type=float, default=1.0)
103
104 @staticmethod
105 def init(opts):
106 config = dict(
107 ans=opts.ans,
108 old_p=opts.old_p,
109 new_p=opts.new_p,
110 old=opts.old,
111 new=opts.new)
112
113 revlist = map(str, range(opts.num))
114 return config, revlist
115
116 def __init__(self, config):
117 self.config = config
118
119 def setenv(self, env, rev):
120 env['ANS'] = str(self.config['ans'])
121 env['OLD_P'] = str(self.config['old_p'])
122 env['NEW_P'] = str(self.config['new_p'])
123 env['REV'] = rev
124
125
126@mock.patch('bisect_kit.common.config_logging', mock.Mock())
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800127class TestBisectorCommandLine(unittest.TestCase):
128 """Test cli.BisectorCommandLine class."""
Kuang-che Wu88875db2017-07-20 10:47:53 +0800129
130 def setUp(self):
131 self.session_base = tempfile.mkdtemp()
132 self.patcher = mock.patch.object(cli, 'DEFAULT_SESSION_BASE',
133 self.session_base)
134 self.patcher.start()
135
136 def tearDown(self):
137 shutil.rmtree(self.session_base)
138 self.patcher.stop()
Kuang-che Wue1b329e2018-03-31 11:39:33 +0800139 configure.reset()
Kuang-che Wu88875db2017-07-20 10:47:53 +0800140
141 def test_run_true(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800142 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800143 bisector.main('init', '--num=10', '--old=0', '--new=9')
144 bisector.main('config', 'switch', 'true')
145 bisector.main('config', 'eval', 'true')
146 with self.assertRaises(core.VerificationFailed):
147 bisector.main('run')
148
149 def test_run_false(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800150 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800151 bisector.main('init', '--num=10', '--old=0', '--new=9')
152 bisector.main('config', 'switch', 'true')
153 bisector.main('config', 'eval', 'false')
154 with self.assertRaises(core.VerificationFailed):
155 bisector.main('run')
156
157 def test_simple(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800158 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800159 bisector.main('init', '--num=20', '--old=3', '--new=15')
160 bisector.main('config', 'switch', 'true')
161 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
162 bisector.main('run')
163 self.assertEqual(bisector.strategy.get_best_guess(), 7)
164
165 def test_switch_fail(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800166 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800167 bisector.main('init', '--num=20', '--old=3', '--new=15')
168 bisector.main('config', 'switch', 'false')
169 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
170 with self.assertRaises(core.ExecutionFatalError):
171 bisector.main('run')
172
173 def test_run_classic(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800174 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800175 bisector.main('init', '--num=200', '--old=0', '--new=99')
176 bisector.main('config', 'switch', 'true')
177 bisector.main('config', 'eval', 'false')
178
179 def do_evaluate(_cmd, _domain, rev):
180 if int(rev) < 42:
181 return 'old', []
182 return 'new', []
183
184 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
185 # two verify
186 with mock.patch(
187 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
188 bisector.main('next')
189 self.assertEqual(mock_stdout.getvalue(), '99\n')
190 bisector.main('run', '-1')
191 with mock.patch(
192 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
193 bisector.main('next')
194 self.assertEqual(mock_stdout.getvalue(), '0\n')
195 bisector.main('run', '-1')
196 self.assertEqual(bisector.strategy.get_range(), (0, 99))
197
198 bisector.main('run', '-1')
199 self.assertEqual(bisector.strategy.get_range(), (0, 49))
200
201 bisector.main('run')
202 self.assertEqual(bisector.strategy.get_best_guess(), 42)
203 self.assertEqual(bisector.states.stats['eval_count'], 2 + 7)
204
205 with mock.patch(
206 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
207 bisector.main('next')
208 self.assertEqual(mock_stdout.getvalue(), 'done\n')
209
210 def test_run_noisy(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800211 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800212 bisector.main('init', '--num=100', '--old=0', '--new=99',
213 '--noisy=old=1/10,new=9/10')
214 bisector.main('config', 'switch', 'true')
215 bisector.main('config', 'eval', 'false')
216
217 def do_evaluate(_cmd, _domain, rev):
218 if int(rev) < 42:
219 p = 0.1
220 else:
221 p = 0.9
222 if random.random() < p:
223 return 'new', []
224 return 'old', []
225
226 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
227 bisector.main('run')
228 self.assertEqual(bisector.strategy.get_best_guess(), 42)
229
230 with mock.patch(
231 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
232 bisector.main('next')
233 # There might be small math error near the boundary of confidence.
234 self.assertIn(mock_stdout.getvalue(), ['done\n', '41\n', '42\n'])
235
236 def test_cmd_old_and_new(self):
237 """Tests cmd_old and cmd_new"""
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800238 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800239 bisector.main('init', '--num=100', '--old=0', '--new=99')
240 bisector.main('config', 'switch', 'true')
241 bisector.main('config', 'eval', 'false')
242 bisector.main('old', '0')
243 bisector.main('new', '99')
244 bisector.main('run', '-1')
245 self.assertEqual(bisector.strategy.get_range(), (0, 49))
246
247 bisector.main('old', '20')
248 bisector.main('new', '40')
249 bisector.main('run', '-1')
250 self.assertEqual(bisector.strategy.get_range(), (20, 30))
251
252 bisector.main('skip', '20*10')
253 bisector.main('skip', '30*10')
254 self.assertEqual(bisector.strategy.get_range(), (20, 30))
255
256 def test_cmd_switch(self):
257 """Test cmd_switch"""
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800258 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800259 bisector.main('init', '--num=100', '--old=0', '--new=99')
260 bisector.main('config', 'switch', 'true')
261 bisector.main('config', 'eval', 'false')
262
263 switched = []
264
265 def do_switch(_cmd, _domain, rev):
266 switched.append(rev)
267
268 with mock.patch('bisect_kit.cli.do_switch', do_switch):
269 bisector.main('switch', 'next')
270 bisector.main('switch', '3')
271 bisector.main('switch', '5')
272 bisector.main('switch', '4')
273 self.assertEqual(switched, ['99', '3', '5', '4'])
274
275 def test_cmd_view(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800276 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800277 bisector.main('init', '--num=100', '--old=10', '--new=90')
278 with mock.patch.object(DummyDomain, 'view') as mock_view:
279 bisector.main('view')
280 mock_view.assert_called_with('10', '90')
281
282 def test_cmd_config_confidence(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800283 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800284 bisector.main('init', '--num=100', '--old=10', '--new=90',
285 '--confidence=0.75')
286
287 with self.assertRaises(core.ExecutionFatalError):
288 bisector.main('config', 'confidence', 'foo')
289 with self.assertRaises(core.ExecutionFatalError):
290 bisector.main('config', 'confidence', '0.9', '0.8')
291
292 self.assertEqual(bisector.states.config['confidence'], 0.75)
293 bisector.main('config', 'confidence', '0.875')
294 self.assertEqual(bisector.states.config['confidence'], 0.875)
295
296 def test_cmd_config_noisy(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800297 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800298 bisector.main('init', '--num=100', '--old=10', '--new=90',
299 '--noisy=new=9/10')
300
301 with self.assertRaises(core.ExecutionFatalError):
302 bisector.main('config', 'noisy', 'hello', 'world')
303
304 self.assertEqual(bisector.states.config['noisy'], 'new=9/10')
305 bisector.main('config', 'noisy', 'old=1/10,new=8/9')
306 self.assertEqual(bisector.states.config['noisy'], 'old=1/10,new=8/9')
307
308 def test_current_status(self):
Kuang-che Wue1b329e2018-03-31 11:39:33 +0800309 common.init()
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800310 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800311 result = bisector.current_status()
312 self.assertEqual(result.get('inited'), False)
313
314 bisector.main('init', '--num=100', '--old=10', '--new=90',
315 '--noisy=new=9/10')
316 result = bisector.current_status()
317 self.assertEqual(result.get('inited'), True)
318
319
320if __name__ == '__main__':
321 unittest.main()