blob: 348b07349ffd18d2bb39d670581fbc5280d84071 [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
Kuang-che Wu603cdad2019-01-18 21:32:55 +080037 def test_argtype_re(self):
38 argtype = cli.argtype_re(r'^r\d+$', 'r123')
39 self.assertEqual(argtype('r123'), 'r123')
40
41 with self.assertRaises(cli.ArgTypeError):
42 argtype('hello')
43
Kuang-che Wu88875db2017-07-20 10:47:53 +080044 def test_argtype_multiplexer(self):
Kuang-che Wu603cdad2019-01-18 21:32:55 +080045 argtype = cli.argtype_multiplexer(cli.argtype_int,
46 cli.argtype_re('foobar', 'foobar'),
47 cli.argtype_re(r'^r\d+$', 'r123'))
Kuang-che Wu88875db2017-07-20 10:47:53 +080048 self.assertEqual(argtype('123456'), '123456')
49 self.assertEqual(argtype('foobar'), 'foobar')
50 self.assertEqual(argtype('r123'), 'r123')
51
52 with self.assertRaises(cli.ArgTypeError):
53 argtype('hello')
54
55 def test_argtype_multiplier(self):
56 argtype = cli.argtype_multiplier(cli.argtype_int)
57 self.assertEqual(argtype('123'), ('123', 1))
58 self.assertEqual(argtype('123*5'), ('123', 5))
59
60 # Make sure there is multiplier example in the message.
61 with self.assertRaisesRegexp(cli.ArgTypeError, r'\d+\*\d+'):
62 argtype('hello')
63
64 def test_argtype_path(self):
65 self.assertEqual(cli.argtype_dir_path('/'), '/')
66 self.assertEqual(cli.argtype_dir_path('/etc/'), '/etc')
67
68 with self.assertRaises(cli.ArgTypeError):
69 cli.argtype_dir_path('')
70 with self.assertRaises(cli.ArgTypeError):
71 cli.argtype_dir_path('/foo/bar/not/exist/path')
72 with self.assertRaises(cli.ArgTypeError):
73 cli.argtype_dir_path('/etc/passwd')
74
75 def test_collect_bisect_result_values(self):
76 # pylint: disable=protected-access
77 values = []
78 cli._collect_bisect_result_values(values, '')
79 self.assertEqual(values, [])
80
81 values = []
82 cli._collect_bisect_result_values(values, 'foo\n')
83 cli._collect_bisect_result_values(values, 'bar\n')
84 self.assertEqual(values, [])
85
86 values = []
87 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=1 2\n')
88 cli._collect_bisect_result_values(values, 'fooBISECT_RESULT_VALUES=3 4\n')
89 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=5\n')
90 self.assertEqual(values, [1, 2, 5])
91
Kuang-che Wue121fae2018-11-09 16:18:39 +080092 with self.assertRaises(errors.InternalError):
Kuang-che Wu88875db2017-07-20 10:47:53 +080093 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=hello\n')
94
Kuang-che Wu88518882017-09-22 16:57:25 +080095 def test_check_executable(self):
96 self.assertEqual(cli.check_executable('/bin/true'), None)
97
98 self.assertRegexpMatches(cli.check_executable('/'), r'Not a file')
99 self.assertRegexpMatches(cli.check_executable('LICENSE'), r'chmod')
100 self.assertRegexpMatches(cli.check_executable('what'), r'PATH')
101 self.assertRegexpMatches(cli.check_executable('eval-manually.sh'), r'\./')
102
Kuang-che Wubc7bfce2019-05-21 18:43:16 +0800103 def test_lookup_signal_name(self):
104 self.assertEqual(cli.lookup_signal_name(15), 'SIGTERM')
105 self.assertEqual(cli.lookup_signal_name(99), 'Unknown')
106
Kuang-che Wu88875db2017-07-20 10:47:53 +0800107
108class DummyDomain(core.BisectDomain):
109 """Dummy subclass of BisectDomain."""
110 revtype = staticmethod(cli.argtype_notempty)
111
112 @staticmethod
113 def add_init_arguments(parser):
114 parser.add_argument('--num', required=True, type=int)
115 parser.add_argument('--ans', type=int)
116 parser.add_argument('--old_p', type=float, default=0.0)
117 parser.add_argument('--new_p', type=float, default=1.0)
118
119 @staticmethod
120 def init(opts):
121 config = dict(
122 ans=opts.ans,
123 old_p=opts.old_p,
124 new_p=opts.new_p,
125 old=opts.old,
126 new=opts.new)
127
128 revlist = map(str, range(opts.num))
129 return config, revlist
130
131 def __init__(self, config):
132 self.config = config
133
134 def setenv(self, env, rev):
135 env['ANS'] = str(self.config['ans'])
136 env['OLD_P'] = str(self.config['old_p'])
137 env['NEW_P'] = str(self.config['new_p'])
138 env['REV'] = rev
139
140
141@mock.patch('bisect_kit.common.config_logging', mock.Mock())
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800142class TestBisectorCommandLine(unittest.TestCase):
143 """Test cli.BisectorCommandLine class."""
Kuang-che Wu88875db2017-07-20 10:47:53 +0800144
145 def setUp(self):
146 self.session_base = tempfile.mkdtemp()
Kuang-che Wu41e8b592018-09-25 17:01:30 +0800147 self.patcher = mock.patch.object(common, 'DEFAULT_SESSION_BASE',
Kuang-che Wu88875db2017-07-20 10:47:53 +0800148 self.session_base)
149 self.patcher.start()
150
151 def tearDown(self):
152 shutil.rmtree(self.session_base)
153 self.patcher.stop()
Kuang-che Wue1b329e2018-03-31 11:39:33 +0800154 configure.reset()
Kuang-che Wu88875db2017-07-20 10:47:53 +0800155
156 def test_run_true(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800157 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800158 bisector.main('init', '--num=10', '--old=0', '--new=9')
159 bisector.main('config', 'switch', 'true')
160 bisector.main('config', 'eval', 'true')
Kuang-che Wue121fae2018-11-09 16:18:39 +0800161 with self.assertRaises(errors.VerificationFailed):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800162 bisector.main('run')
163
Kuang-che Wu15874b62019-01-11 21:10:27 +0800164 result = bisector.current_status()
165 self.assertEqual(result.get('inited'), True)
166 self.assertEqual(result.get('verified'), False)
167
Kuang-che Wu88875db2017-07-20 10:47:53 +0800168 def test_run_false(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800169 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800170 bisector.main('init', '--num=10', '--old=0', '--new=9')
171 bisector.main('config', 'switch', 'true')
172 bisector.main('config', 'eval', 'false')
Kuang-che Wue121fae2018-11-09 16:18:39 +0800173 with self.assertRaises(errors.VerificationFailed):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800174 bisector.main('run')
175
Kuang-che Wu15874b62019-01-11 21:10:27 +0800176 result = bisector.current_status()
177 self.assertEqual(result.get('inited'), True)
178 self.assertEqual(result.get('verified'), False)
179
Kuang-che Wu88875db2017-07-20 10:47:53 +0800180 def test_simple(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800181 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800182 bisector.main('init', '--num=20', '--old=3', '--new=15')
183 bisector.main('config', 'switch', 'true')
184 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
185 bisector.main('run')
186 self.assertEqual(bisector.strategy.get_best_guess(), 7)
187
Kuang-che Wu849da582018-09-06 18:11:52 +0800188 with mock.patch('sys.stdout', new_callable=StringIO.StringIO):
189 # Only make sure no exceptions. No output verification.
190 bisector.main('log')
191
Kuang-che Wu88875db2017-07-20 10:47:53 +0800192 def test_switch_fail(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800193 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800194 bisector.main('init', '--num=20', '--old=3', '--new=15')
195 bisector.main('config', 'switch', 'false')
196 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
Kuang-che Wue121fae2018-11-09 16:18:39 +0800197 with self.assertRaises(errors.UnableToProceed):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800198 bisector.main('run')
199
Kuang-che Wu15874b62019-01-11 21:10:27 +0800200 result = bisector.current_status()
201 self.assertEqual(result.get('inited'), True)
202 self.assertEqual(result.get('verified'), False)
203
Kuang-che Wu88875db2017-07-20 10:47:53 +0800204 def test_run_classic(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800205 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800206 bisector.main('init', '--num=200', '--old=0', '--new=99')
207 bisector.main('config', 'switch', 'true')
208 bisector.main('config', 'eval', 'false')
209
210 def do_evaluate(_cmd, _domain, rev):
211 if int(rev) < 42:
212 return 'old', []
213 return 'new', []
214
215 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
216 # two verify
217 with mock.patch(
218 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
219 bisector.main('next')
220 self.assertEqual(mock_stdout.getvalue(), '99\n')
221 bisector.main('run', '-1')
222 with mock.patch(
223 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
224 bisector.main('next')
225 self.assertEqual(mock_stdout.getvalue(), '0\n')
226 bisector.main('run', '-1')
227 self.assertEqual(bisector.strategy.get_range(), (0, 99))
228
229 bisector.main('run', '-1')
230 self.assertEqual(bisector.strategy.get_range(), (0, 49))
231
232 bisector.main('run')
233 self.assertEqual(bisector.strategy.get_best_guess(), 42)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800234
235 with mock.patch(
236 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
237 bisector.main('next')
238 self.assertEqual(mock_stdout.getvalue(), 'done\n')
239
240 def test_run_noisy(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800241 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800242 bisector.main('init', '--num=100', '--old=0', '--new=99',
243 '--noisy=old=1/10,new=9/10')
244 bisector.main('config', 'switch', 'true')
245 bisector.main('config', 'eval', 'false')
246
247 def do_evaluate(_cmd, _domain, rev):
248 if int(rev) < 42:
249 p = 0.1
250 else:
251 p = 0.9
252 if random.random() < p:
253 return 'new', []
254 return 'old', []
255
256 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
257 bisector.main('run')
258 self.assertEqual(bisector.strategy.get_best_guess(), 42)
259
260 with mock.patch(
261 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
262 bisector.main('next')
263 # There might be small math error near the boundary of confidence.
264 self.assertIn(mock_stdout.getvalue(), ['done\n', '41\n', '42\n'])
265
266 def test_cmd_old_and_new(self):
267 """Tests cmd_old and cmd_new"""
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800268 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800269 bisector.main('init', '--num=100', '--old=0', '--new=99')
270 bisector.main('config', 'switch', 'true')
271 bisector.main('config', 'eval', 'false')
272 bisector.main('old', '0')
273 bisector.main('new', '99')
274 bisector.main('run', '-1')
275 self.assertEqual(bisector.strategy.get_range(), (0, 49))
276
277 bisector.main('old', '20')
278 bisector.main('new', '40')
279 bisector.main('run', '-1')
280 self.assertEqual(bisector.strategy.get_range(), (20, 30))
281
Kuang-che Wu978b65a2019-03-12 09:50:40 +0800282 with self.assertRaises(errors.UnableToProceed):
283 bisector.main('skip', '20*10')
284 with self.assertRaises(errors.UnableToProceed):
285 bisector.main('skip', '30*10')
Kuang-che Wu88875db2017-07-20 10:47:53 +0800286 self.assertEqual(bisector.strategy.get_range(), (20, 30))
287
288 def test_cmd_switch(self):
289 """Test cmd_switch"""
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800290 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800291 bisector.main('init', '--num=100', '--old=0', '--new=99')
292 bisector.main('config', 'switch', 'true')
293 bisector.main('config', 'eval', 'false')
294
295 switched = []
296
297 def do_switch(_cmd, _domain, rev):
298 switched.append(rev)
299
300 with mock.patch('bisect_kit.cli.do_switch', do_switch):
301 bisector.main('switch', 'next')
302 bisector.main('switch', '3')
303 bisector.main('switch', '5')
304 bisector.main('switch', '4')
305 self.assertEqual(switched, ['99', '3', '5', '4'])
306
307 def test_cmd_view(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800308 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800309 bisector.main('init', '--num=100', '--old=10', '--new=90')
Kuang-che Wue80bb872018-11-15 19:45:25 +0800310 with mock.patch.object(DummyDomain, 'fill_candidate_summary') as mock_view:
Kuang-che Wu88875db2017-07-20 10:47:53 +0800311 bisector.main('view')
Kuang-che Wue80bb872018-11-15 19:45:25 +0800312 mock_view.assert_called()
Kuang-che Wu88875db2017-07-20 10:47:53 +0800313
314 def test_cmd_config_confidence(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 '--confidence=0.75')
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', 'confidence', 'foo')
Kuang-che Wue121fae2018-11-09 16:18:39 +0800321 with self.assertRaises(errors.ArgumentError):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800322 bisector.main('config', 'confidence', '0.9', '0.8')
323
324 self.assertEqual(bisector.states.config['confidence'], 0.75)
325 bisector.main('config', 'confidence', '0.875')
326 self.assertEqual(bisector.states.config['confidence'], 0.875)
327
328 def test_cmd_config_noisy(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800329 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800330 bisector.main('init', '--num=100', '--old=10', '--new=90',
331 '--noisy=new=9/10')
332
Kuang-che Wue121fae2018-11-09 16:18:39 +0800333 with self.assertRaises(errors.ArgumentError):
Kuang-che Wu88875db2017-07-20 10:47:53 +0800334 bisector.main('config', 'noisy', 'hello', 'world')
335
336 self.assertEqual(bisector.states.config['noisy'], 'new=9/10')
337 bisector.main('config', 'noisy', 'old=1/10,new=8/9')
338 self.assertEqual(bisector.states.config['noisy'], 'old=1/10,new=8/9')
339
Kuang-che Wua8c987f2019-01-18 14:26:43 +0800340 with mock.patch('sys.stdout', new_callable=StringIO.StringIO):
341 # Only make sure no exceptions. No output verification.
342 bisector.main('view')
343
Kuang-che Wu88875db2017-07-20 10:47:53 +0800344 def test_current_status(self):
Kuang-che Wue1b329e2018-03-31 11:39:33 +0800345 common.init()
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800346 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800347 result = bisector.current_status()
348 self.assertEqual(result.get('inited'), False)
349
350 bisector.main('init', '--num=100', '--old=10', '--new=90',
351 '--noisy=new=9/10')
352 result = bisector.current_status()
353 self.assertEqual(result.get('inited'), True)
354
355
356if __name__ == '__main__':
357 unittest.main()