blob: 9bbbb78763fe6ff7a9c52017417406c46bc65e1e [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 configure
18from bisect_kit import common
Kuang-che Wu88875db2017-07-20 10:47:53 +080019from bisect_kit import core
20
21
22class TestCli(unittest.TestCase):
23 """Test functions in cli module."""
24
25 def test_argtype_int(self):
26 self.assertEqual(cli.argtype_int('123456'), '123456')
27 self.assertEqual(cli.argtype_int('-123456'), '-123456')
28 with self.assertRaises(cli.ArgTypeError):
29 cli.argtype_int('foobar')
30
31 def test_argtype_notempty(self):
32 self.assertEqual(cli.argtype_notempty('123456'), '123456')
33 with self.assertRaises(cli.ArgTypeError):
34 cli.argtype_notempty('')
35
36 def test_argtype_multiplexer(self):
37 argtype = cli.argtype_multiplexer(cli.argtype_int, 'foobar', r'^r\d+$')
38 self.assertEqual(argtype('123456'), '123456')
39 self.assertEqual(argtype('foobar'), 'foobar')
40 self.assertEqual(argtype('r123'), 'r123')
41
42 with self.assertRaises(cli.ArgTypeError):
43 argtype('hello')
44
45 def test_argtype_multiplier(self):
46 argtype = cli.argtype_multiplier(cli.argtype_int)
47 self.assertEqual(argtype('123'), ('123', 1))
48 self.assertEqual(argtype('123*5'), ('123', 5))
49
50 # Make sure there is multiplier example in the message.
51 with self.assertRaisesRegexp(cli.ArgTypeError, r'\d+\*\d+'):
52 argtype('hello')
53
54 def test_argtype_path(self):
55 self.assertEqual(cli.argtype_dir_path('/'), '/')
56 self.assertEqual(cli.argtype_dir_path('/etc/'), '/etc')
57
58 with self.assertRaises(cli.ArgTypeError):
59 cli.argtype_dir_path('')
60 with self.assertRaises(cli.ArgTypeError):
61 cli.argtype_dir_path('/foo/bar/not/exist/path')
62 with self.assertRaises(cli.ArgTypeError):
63 cli.argtype_dir_path('/etc/passwd')
64
65 def test_collect_bisect_result_values(self):
66 # pylint: disable=protected-access
67 values = []
68 cli._collect_bisect_result_values(values, '')
69 self.assertEqual(values, [])
70
71 values = []
72 cli._collect_bisect_result_values(values, 'foo\n')
73 cli._collect_bisect_result_values(values, 'bar\n')
74 self.assertEqual(values, [])
75
76 values = []
77 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=1 2\n')
78 cli._collect_bisect_result_values(values, 'fooBISECT_RESULT_VALUES=3 4\n')
79 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=5\n')
80 self.assertEqual(values, [1, 2, 5])
81
82 with self.assertRaises(core.ExecutionFatalError):
83 cli._collect_bisect_result_values(values, 'BISECT_RESULT_VALUES=hello\n')
84
Kuang-che Wu88518882017-09-22 16:57:25 +080085 def test_check_executable(self):
86 self.assertEqual(cli.check_executable('/bin/true'), None)
87
88 self.assertRegexpMatches(cli.check_executable('/'), r'Not a file')
89 self.assertRegexpMatches(cli.check_executable('LICENSE'), r'chmod')
90 self.assertRegexpMatches(cli.check_executable('what'), r'PATH')
91 self.assertRegexpMatches(cli.check_executable('eval-manually.sh'), r'\./')
92
Kuang-che Wu88875db2017-07-20 10:47:53 +080093
94class DummyDomain(core.BisectDomain):
95 """Dummy subclass of BisectDomain."""
96 revtype = staticmethod(cli.argtype_notempty)
97
98 @staticmethod
99 def add_init_arguments(parser):
100 parser.add_argument('--num', required=True, type=int)
101 parser.add_argument('--ans', type=int)
102 parser.add_argument('--old_p', type=float, default=0.0)
103 parser.add_argument('--new_p', type=float, default=1.0)
104
105 @staticmethod
106 def init(opts):
107 config = dict(
108 ans=opts.ans,
109 old_p=opts.old_p,
110 new_p=opts.new_p,
111 old=opts.old,
112 new=opts.new)
113
114 revlist = map(str, range(opts.num))
115 return config, revlist
116
117 def __init__(self, config):
118 self.config = config
119
120 def setenv(self, env, rev):
121 env['ANS'] = str(self.config['ans'])
122 env['OLD_P'] = str(self.config['old_p'])
123 env['NEW_P'] = str(self.config['new_p'])
124 env['REV'] = rev
125
126
127@mock.patch('bisect_kit.common.config_logging', mock.Mock())
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800128class TestBisectorCommandLine(unittest.TestCase):
129 """Test cli.BisectorCommandLine class."""
Kuang-che Wu88875db2017-07-20 10:47:53 +0800130
131 def setUp(self):
132 self.session_base = tempfile.mkdtemp()
133 self.patcher = mock.patch.object(cli, 'DEFAULT_SESSION_BASE',
134 self.session_base)
135 self.patcher.start()
136
137 def tearDown(self):
138 shutil.rmtree(self.session_base)
139 self.patcher.stop()
Kuang-che Wue1b329e2018-03-31 11:39:33 +0800140 configure.reset()
Kuang-che Wu88875db2017-07-20 10:47:53 +0800141
142 def test_run_true(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800143 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800144 bisector.main('init', '--num=10', '--old=0', '--new=9')
145 bisector.main('config', 'switch', 'true')
146 bisector.main('config', 'eval', 'true')
147 with self.assertRaises(core.VerificationFailed):
148 bisector.main('run')
149
150 def test_run_false(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800151 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800152 bisector.main('init', '--num=10', '--old=0', '--new=9')
153 bisector.main('config', 'switch', 'true')
154 bisector.main('config', 'eval', 'false')
155 with self.assertRaises(core.VerificationFailed):
156 bisector.main('run')
157
158 def test_simple(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800159 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800160 bisector.main('init', '--num=20', '--old=3', '--new=15')
161 bisector.main('config', 'switch', 'true')
162 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
163 bisector.main('run')
164 self.assertEqual(bisector.strategy.get_best_guess(), 7)
165
Kuang-che Wu849da582018-09-06 18:11:52 +0800166 with mock.patch('sys.stdout', new_callable=StringIO.StringIO):
167 # Only make sure no exceptions. No output verification.
168 bisector.main('log')
169
Kuang-che Wu88875db2017-07-20 10:47:53 +0800170 def test_switch_fail(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800171 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800172 bisector.main('init', '--num=20', '--old=3', '--new=15')
173 bisector.main('config', 'switch', 'false')
174 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
175 with self.assertRaises(core.ExecutionFatalError):
176 bisector.main('run')
177
178 def test_run_classic(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800179 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800180 bisector.main('init', '--num=200', '--old=0', '--new=99')
181 bisector.main('config', 'switch', 'true')
182 bisector.main('config', 'eval', 'false')
183
184 def do_evaluate(_cmd, _domain, rev):
185 if int(rev) < 42:
186 return 'old', []
187 return 'new', []
188
189 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
190 # two verify
191 with mock.patch(
192 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
193 bisector.main('next')
194 self.assertEqual(mock_stdout.getvalue(), '99\n')
195 bisector.main('run', '-1')
196 with mock.patch(
197 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
198 bisector.main('next')
199 self.assertEqual(mock_stdout.getvalue(), '0\n')
200 bisector.main('run', '-1')
201 self.assertEqual(bisector.strategy.get_range(), (0, 99))
202
203 bisector.main('run', '-1')
204 self.assertEqual(bisector.strategy.get_range(), (0, 49))
205
206 bisector.main('run')
207 self.assertEqual(bisector.strategy.get_best_guess(), 42)
208 self.assertEqual(bisector.states.stats['eval_count'], 2 + 7)
209
210 with mock.patch(
211 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
212 bisector.main('next')
213 self.assertEqual(mock_stdout.getvalue(), 'done\n')
214
215 def test_run_noisy(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800216 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800217 bisector.main('init', '--num=100', '--old=0', '--new=99',
218 '--noisy=old=1/10,new=9/10')
219 bisector.main('config', 'switch', 'true')
220 bisector.main('config', 'eval', 'false')
221
222 def do_evaluate(_cmd, _domain, rev):
223 if int(rev) < 42:
224 p = 0.1
225 else:
226 p = 0.9
227 if random.random() < p:
228 return 'new', []
229 return 'old', []
230
231 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
232 bisector.main('run')
233 self.assertEqual(bisector.strategy.get_best_guess(), 42)
234
235 with mock.patch(
236 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
237 bisector.main('next')
238 # There might be small math error near the boundary of confidence.
239 self.assertIn(mock_stdout.getvalue(), ['done\n', '41\n', '42\n'])
240
241 def test_cmd_old_and_new(self):
242 """Tests cmd_old and cmd_new"""
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800243 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800244 bisector.main('init', '--num=100', '--old=0', '--new=99')
245 bisector.main('config', 'switch', 'true')
246 bisector.main('config', 'eval', 'false')
247 bisector.main('old', '0')
248 bisector.main('new', '99')
249 bisector.main('run', '-1')
250 self.assertEqual(bisector.strategy.get_range(), (0, 49))
251
252 bisector.main('old', '20')
253 bisector.main('new', '40')
254 bisector.main('run', '-1')
255 self.assertEqual(bisector.strategy.get_range(), (20, 30))
256
257 bisector.main('skip', '20*10')
258 bisector.main('skip', '30*10')
259 self.assertEqual(bisector.strategy.get_range(), (20, 30))
260
261 def test_cmd_switch(self):
262 """Test cmd_switch"""
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800263 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800264 bisector.main('init', '--num=100', '--old=0', '--new=99')
265 bisector.main('config', 'switch', 'true')
266 bisector.main('config', 'eval', 'false')
267
268 switched = []
269
270 def do_switch(_cmd, _domain, rev):
271 switched.append(rev)
272
273 with mock.patch('bisect_kit.cli.do_switch', do_switch):
274 bisector.main('switch', 'next')
275 bisector.main('switch', '3')
276 bisector.main('switch', '5')
277 bisector.main('switch', '4')
278 self.assertEqual(switched, ['99', '3', '5', '4'])
279
280 def test_cmd_view(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800281 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800282 bisector.main('init', '--num=100', '--old=10', '--new=90')
283 with mock.patch.object(DummyDomain, 'view') as mock_view:
284 bisector.main('view')
285 mock_view.assert_called_with('10', '90')
286
287 def test_cmd_config_confidence(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800288 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800289 bisector.main('init', '--num=100', '--old=10', '--new=90',
290 '--confidence=0.75')
291
292 with self.assertRaises(core.ExecutionFatalError):
293 bisector.main('config', 'confidence', 'foo')
294 with self.assertRaises(core.ExecutionFatalError):
295 bisector.main('config', 'confidence', '0.9', '0.8')
296
297 self.assertEqual(bisector.states.config['confidence'], 0.75)
298 bisector.main('config', 'confidence', '0.875')
299 self.assertEqual(bisector.states.config['confidence'], 0.875)
300
301 def test_cmd_config_noisy(self):
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800302 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800303 bisector.main('init', '--num=100', '--old=10', '--new=90',
304 '--noisy=new=9/10')
305
306 with self.assertRaises(core.ExecutionFatalError):
307 bisector.main('config', 'noisy', 'hello', 'world')
308
309 self.assertEqual(bisector.states.config['noisy'], 'new=9/10')
310 bisector.main('config', 'noisy', 'old=1/10,new=8/9')
311 self.assertEqual(bisector.states.config['noisy'], 'old=1/10,new=8/9')
312
313 def test_current_status(self):
Kuang-che Wue1b329e2018-03-31 11:39:33 +0800314 common.init()
Kuang-che Wu68db08a2018-03-30 11:50:34 +0800315 bisector = cli.BisectorCommandLine(DummyDomain)
Kuang-che Wu88875db2017-07-20 10:47:53 +0800316 result = bisector.current_status()
317 self.assertEqual(result.get('inited'), False)
318
319 bisector.main('init', '--num=100', '--old=10', '--new=90',
320 '--noisy=new=9/10')
321 result = bisector.current_status()
322 self.assertEqual(result.get('inited'), True)
323
324
325if __name__ == '__main__':
326 unittest.main()