blob: 54d15ce5887b20c28bccfff89e6d303e394d04b4 [file] [log] [blame]
Kuang-che Wu6e4beca2018-06-27 17:45:02 +08001# -*- coding: utf-8 -*-
Kuang-che Wu88875db2017-07-20 10:47:53 +08002# Copyright 2017 The Chromium OS 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"""Test cli module."""
6
7from __future__ import print_function
8import random
9import shutil
10import tempfile
11import unittest
12import StringIO
13
14import mock
15
16from bisect_kit import cli
Kuang-che Wue1b329e2018-03-31 11:39:33 +080017from bisect_kit import common
Kuang-che Wue80bb872018-11-15 19:45:25 +080018from bisect_kit import configure
Kuang-che Wu88875db2017-07-20 10:47:53 +080019from bisect_kit import core
Kuang-che Wue121fae2018-11-09 16:18:39 +080020from bisect_kit import errors
Kuang-che Wu88875db2017-07-20 10:47:53 +080021
22
23class TestCli(unittest.TestCase):
24 """Test functions in cli module."""
25
26 def test_argtype_int(self):
27 self.assertEqual(cli.argtype_int('123456'), '123456')
28 self.assertEqual(cli.argtype_int('-123456'), '-123456')
29 with self.assertRaises(cli.ArgTypeError):
30 cli.argtype_int('foobar')
31
32 def test_argtype_notempty(self):
33 self.assertEqual(cli.argtype_notempty('123456'), '123456')
34 with self.assertRaises(cli.ArgTypeError):
35 cli.argtype_notempty('')
36
37 def test_argtype_multiplexer(self):
38 argtype = cli.argtype_multiplexer(cli.argtype_int, 'foobar', r'^r\d+$')
39 self.assertEqual(argtype('123456'), '123456')
40 self.assertEqual(argtype('foobar'), 'foobar')
41 self.assertEqual(argtype('r123'), 'r123')
42
43 with self.assertRaises(cli.ArgTypeError):
44 argtype('hello')
45
46 def test_argtype_multiplier(self):
47 argtype = cli.argtype_multiplier(cli.argtype_int)
48 self.assertEqual(argtype('123'), ('123', 1))
49 self.assertEqual(argtype('123*5'), ('123', 5))
50
51 # Make sure there is multiplier example in the message.
52 with self.assertRaisesRegexp(cli.ArgTypeError, r'\d+\*\d+'):
53 argtype('hello')
54
55 def test_argtype_path(self):
56 self.assertEqual(cli.argtype_dir_path('/'), '/')
57 self.assertEqual(cli.argtype_dir_path('/etc/'), '/etc')
58
59 with self.assertRaises(cli.ArgTypeError):
60 cli.argtype_dir_path('')
61 with self.assertRaises(cli.ArgTypeError):
62 cli.argtype_dir_path('/foo/bar/not/exist/path')
63 with self.assertRaises(cli.ArgTypeError):
64 cli.argtype_dir_path('/etc/passwd')
65
66 def test_collect_bisect_result_values(self):
67 # pylint: disable=protected-access
68 values = []
69 cli._collect_bisect_result_values(values, '')
70 self.assertEqual(values, [])
71
72 values = []
73 cli._collect_bisect_result_values(values, 'foo\n')
74 cli._collect_bisect_result_values(values, 'bar\n')
75 self.assertEqual(values, [])
76
77 values = []
78 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=1 2\n')
79 cli._collect_bisect_result_values(values, 'fooBISECT_RESULT_VALUES=3 4\n')
80 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=5\n')
81 self.assertEqual(values, [1, 2, 5])
82
Kuang-che Wue121fae2018-11-09 16:18:39 +080083 with self.assertRaises(errors.InternalError):
Kuang-che Wu88875db2017-07-20 10:47:53 +080084 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=hello\n')
85
Kuang-che Wu88518882017-09-22 16:57:25 +080086 def test_check_executable(self):
87 self.assertEqual(cli.check_executable('/bin/true'), None)
88
89 self.assertRegexpMatches(cli.check_executable('/'), r'Not a file')
90 self.assertRegexpMatches(cli.check_executable('LICENSE'), r'chmod')
91 self.assertRegexpMatches(cli.check_executable('what'), r'PATH')
92 self.assertRegexpMatches(cli.check_executable('eval-manually.sh'), r'\./')
93
Kuang-che Wu88875db2017-07-20 10:47:53 +080094
95class DummyDomain(core.BisectDomain):
96 """Dummy subclass of BisectDomain."""
97 revtype = staticmethod(cli.argtype_notempty)
98
99 @staticmethod
100 def add_init_arguments(parser):
101 parser.add_argument('--num', required=True, type=int)
102 parser.add_argument('--ans', type=int)
103 parser.add_argument('--old_p', type=float, default=0.0)
104 parser.add_argument('--new_p', type=float, default=1.0)
105
106 @staticmethod
107 def init(opts):
108 config = dict(
109 ans=opts.ans,
110 old_p=opts.old_p,
111 new_p=opts.new_p,
112 old=opts.old,
113 new=opts.new)
114
115 revlist = map(str, range(opts.num))
116 return config, revlist
117
118 def __init__(self, config):
119 self.config = config
120
121 def setenv(self, env, rev):
122 env['ANS'] = str(self.config['ans'])
123 env['OLD_P'] = str(self.config['old_p'])
124 env['NEW_P'] = str(self.config['new_p'])
125 env['REV'] = rev
126
127
128@mock.patch('bisect_kit.common.config_logging', mock.Mock())
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800129class TestBisectorCommandLine(unittest.TestCase):
130 """Test cli.BisectorCommandLine class."""
Kuang-che Wu88875db2017-07-20 10:47:53 +0800131
132 def setUp(self):
133 self.session_base = tempfile.mkdtemp()
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800134 self.patcher = mock.patch.object(common, 'DEFAULT_SESSION_BASE',
Kuang-che Wu88875db2017-07-20 10:47:53 +0800135 self.session_base)
136 self.patcher.start()
137
138 def tearDown(self):
139 shutil.rmtree(self.session_base)
140 self.patcher.stop()
Kuang-che Wue1b329e2018-03-31 11:39:33 +0800141 configure.reset()
Kuang-che Wu88875db2017-07-20 10:47:53 +0800142
143 def test_run_true(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800144 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800145 bisector.main('init', '--num=10', '--old=0', '--new=9')
146 bisector.main('config', 'switch', 'true')
147 bisector.main('config', 'eval', 'true')
Kuang-che Wue121fae2018-11-09 16:18:39 +0800148 with self.assertRaises(errors.VerificationFailed):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800149 bisector.main('run')
150
Kuang-che Wu15874b62019-01-11 21:10:27 +0800151 result = bisector.current_status()
152 self.assertEqual(result.get('inited'), True)
153 self.assertEqual(result.get('verified'), False)
154
Kuang-che Wu88875db2017-07-20 10:47:53 +0800155 def test_run_false(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800156 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800157 bisector.main('init', '--num=10', '--old=0', '--new=9')
158 bisector.main('config', 'switch', 'true')
159 bisector.main('config', 'eval', 'false')
Kuang-che Wue121fae2018-11-09 16:18:39 +0800160 with self.assertRaises(errors.VerificationFailed):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800161 bisector.main('run')
162
Kuang-che Wu15874b62019-01-11 21:10:27 +0800163 result = bisector.current_status()
164 self.assertEqual(result.get('inited'), True)
165 self.assertEqual(result.get('verified'), False)
166
Kuang-che Wu88875db2017-07-20 10:47:53 +0800167 def test_simple(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800168 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800169 bisector.main('init', '--num=20', '--old=3', '--new=15')
170 bisector.main('config', 'switch', 'true')
171 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
172 bisector.main('run')
173 self.assertEqual(bisector.strategy.get_best_guess(), 7)
174
Kuang-che Wu849da582018-09-06 18:11:52 +0800175 with mock.patch('sys.stdout', new_callable=StringIO.StringIO):
176 # Only make sure no exceptions. No output verification.
177 bisector.main('log')
178
Kuang-che Wu88875db2017-07-20 10:47:53 +0800179 def test_switch_fail(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800180 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800181 bisector.main('init', '--num=20', '--old=3', '--new=15')
182 bisector.main('config', 'switch', 'false')
183 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
Kuang-che Wue121fae2018-11-09 16:18:39 +0800184 with self.assertRaises(errors.UnableToProceed):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800185 bisector.main('run')
186
Kuang-che Wu15874b62019-01-11 21:10:27 +0800187 result = bisector.current_status()
188 self.assertEqual(result.get('inited'), True)
189 self.assertEqual(result.get('verified'), False)
190
Kuang-che Wu88875db2017-07-20 10:47:53 +0800191 def test_run_classic(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800192 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800193 bisector.main('init', '--num=200', '--old=0', '--new=99')
194 bisector.main('config', 'switch', 'true')
195 bisector.main('config', 'eval', 'false')
196
197 def do_evaluate(_cmd, _domain, rev):
198 if int(rev) < 42:
199 return 'old', []
200 return 'new', []
201
202 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
203 # two verify
204 with mock.patch(
205 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
206 bisector.main('next')
207 self.assertEqual(mock_stdout.getvalue(), '99\n')
208 bisector.main('run', '-1')
209 with mock.patch(
210 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
211 bisector.main('next')
212 self.assertEqual(mock_stdout.getvalue(), '0\n')
213 bisector.main('run', '-1')
214 self.assertEqual(bisector.strategy.get_range(), (0, 99))
215
216 bisector.main('run', '-1')
217 self.assertEqual(bisector.strategy.get_range(), (0, 49))
218
219 bisector.main('run')
220 self.assertEqual(bisector.strategy.get_best_guess(), 42)
221 self.assertEqual(bisector.states.stats['eval_count'], 2 + 7)
222
223 with mock.patch(
224 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
225 bisector.main('next')
226 self.assertEqual(mock_stdout.getvalue(), 'done\n')
227
228 def test_run_noisy(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800229 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800230 bisector.main('init', '--num=100', '--old=0', '--new=99',
231 '--noisy=old=1/10,new=9/10')
232 bisector.main('config', 'switch', 'true')
233 bisector.main('config', 'eval', 'false')
234
235 def do_evaluate(_cmd, _domain, rev):
236 if int(rev) < 42:
237 p = 0.1
238 else:
239 p = 0.9
240 if random.random() < p:
241 return 'new', []
242 return 'old', []
243
244 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
245 bisector.main('run')
246 self.assertEqual(bisector.strategy.get_best_guess(), 42)
247
248 with mock.patch(
249 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
250 bisector.main('next')
251 # There might be small math error near the boundary of confidence.
252 self.assertIn(mock_stdout.getvalue(), ['done\n', '41\n', '42\n'])
253
254 def test_cmd_old_and_new(self):
255 """Tests cmd_old and cmd_new"""
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800256 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800257 bisector.main('init', '--num=100', '--old=0', '--new=99')
258 bisector.main('config', 'switch', 'true')
259 bisector.main('config', 'eval', 'false')
260 bisector.main('old', '0')
261 bisector.main('new', '99')
262 bisector.main('run', '-1')
263 self.assertEqual(bisector.strategy.get_range(), (0, 49))
264
265 bisector.main('old', '20')
266 bisector.main('new', '40')
267 bisector.main('run', '-1')
268 self.assertEqual(bisector.strategy.get_range(), (20, 30))
269
270 bisector.main('skip', '20*10')
271 bisector.main('skip', '30*10')
272 self.assertEqual(bisector.strategy.get_range(), (20, 30))
273
274 def test_cmd_switch(self):
275 """Test cmd_switch"""
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=0', '--new=99')
278 bisector.main('config', 'switch', 'true')
279 bisector.main('config', 'eval', 'false')
280
281 switched = []
282
283 def do_switch(_cmd, _domain, rev):
284 switched.append(rev)
285
286 with mock.patch('bisect_kit.cli.do_switch', do_switch):
287 bisector.main('switch', 'next')
288 bisector.main('switch', '3')
289 bisector.main('switch', '5')
290 bisector.main('switch', '4')
291 self.assertEqual(switched, ['99', '3', '5', '4'])
292
293 def test_cmd_view(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800294 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800295 bisector.main('init', '--num=100', '--old=10', '--new=90')
Kuang-che Wue80bb872018-11-15 19:45:25 +0800296 with mock.patch.object(DummyDomain, 'fill_candidate_summary') as mock_view:
Kuang-che Wu88875db2017-07-20 10:47:53 +0800297 bisector.main('view')
Kuang-che Wue80bb872018-11-15 19:45:25 +0800298 mock_view.assert_called()
Kuang-che Wu88875db2017-07-20 10:47:53 +0800299
300 def test_cmd_config_confidence(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800301 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800302 bisector.main('init', '--num=100', '--old=10', '--new=90',
303 '--confidence=0.75')
304
Kuang-che Wue121fae2018-11-09 16:18:39 +0800305 with self.assertRaises(errors.ArgumentError):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800306 bisector.main('config', 'confidence', 'foo')
Kuang-che Wue121fae2018-11-09 16:18:39 +0800307 with self.assertRaises(errors.ArgumentError):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800308 bisector.main('config', 'confidence', '0.9', '0.8')
309
310 self.assertEqual(bisector.states.config['confidence'], 0.75)
311 bisector.main('config', 'confidence', '0.875')
312 self.assertEqual(bisector.states.config['confidence'], 0.875)
313
314 def test_cmd_config_noisy(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800315 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800316 bisector.main('init', '--num=100', '--old=10', '--new=90',
317 '--noisy=new=9/10')
318
Kuang-che Wue121fae2018-11-09 16:18:39 +0800319 with self.assertRaises(errors.ArgumentError):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800320 bisector.main('config', 'noisy', 'hello', 'world')
321
322 self.assertEqual(bisector.states.config['noisy'], 'new=9/10')
323 bisector.main('config', 'noisy', 'old=1/10,new=8/9')
324 self.assertEqual(bisector.states.config['noisy'], 'old=1/10,new=8/9')
325
Kuang-che Wua8c987f2019-01-18 14:26:43 +0800326 with mock.patch('sys.stdout', new_callable=StringIO.StringIO):
327 # Only make sure no exceptions. No output verification.
328 bisector.main('view')
329
Kuang-che Wu88875db2017-07-20 10:47:53 +0800330 def test_current_status(self):
Kuang-che Wue1b329e2018-03-31 11:39:33 +0800331 common.init()
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800332 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800333 result = bisector.current_status()
334 self.assertEqual(result.get('inited'), False)
335
336 bisector.main('init', '--num=100', '--old=10', '--new=90',
337 '--noisy=new=9/10')
338 result = bisector.current_status()
339 self.assertEqual(result.get('inited'), True)
340
341
342if __name__ == '__main__':
343 unittest.main()