blob: 8002581e84fba53a29f83ca1fb5e1b973f4a3ed8 [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
Kuang-che Wu88518882017-09-22 16:57:25 +080082 def test_check_executable(self):
83 self.assertEqual(cli.check_executable('/bin/true'), None)
84
85 self.assertRegexpMatches(cli.check_executable('/'), r'Not a file')
86 self.assertRegexpMatches(cli.check_executable('LICENSE'), r'chmod')
87 self.assertRegexpMatches(cli.check_executable('what'), r'PATH')
88 self.assertRegexpMatches(cli.check_executable('eval-manually.sh'), r'\./')
89
Kuang-che Wu88875db2017-07-20 10:47:53 +080090
91class DummyDomain(core.BisectDomain):
92 """Dummy subclass of BisectDomain."""
93 revtype = staticmethod(cli.argtype_notempty)
94
95 @staticmethod
96 def add_init_arguments(parser):
97 parser.add_argument('--num', required=True, type=int)
98 parser.add_argument('--ans', type=int)
99 parser.add_argument('--old_p', type=float, default=0.0)
100 parser.add_argument('--new_p', type=float, default=1.0)
101
102 @staticmethod
103 def init(opts):
104 config = dict(
105 ans=opts.ans,
106 old_p=opts.old_p,
107 new_p=opts.new_p,
108 old=opts.old,
109 new=opts.new)
110
111 revlist = map(str, range(opts.num))
112 return config, revlist
113
114 def __init__(self, config):
115 self.config = config
116
117 def setenv(self, env, rev):
118 env['ANS'] = str(self.config['ans'])
119 env['OLD_P'] = str(self.config['old_p'])
120 env['NEW_P'] = str(self.config['new_p'])
121 env['REV'] = rev
122
123
124@mock.patch('bisect_kit.common.config_logging', mock.Mock())
125# This is not an python "interface". pylint: disable=interface-not-implemented
126class TestBisectorCommandLineInterface(unittest.TestCase):
127 """Test cli.BisectorCommandLineInterface class."""
128
129 def setUp(self):
130 self.session_base = tempfile.mkdtemp()
131 self.patcher = mock.patch.object(cli, 'DEFAULT_SESSION_BASE',
132 self.session_base)
133 self.patcher.start()
134
135 def tearDown(self):
136 shutil.rmtree(self.session_base)
137 self.patcher.stop()
138
139 def test_run_true(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', 'true')
144 with self.assertRaises(core.VerificationFailed):
145 bisector.main('run')
146
147 def test_run_false(self):
148 bisector = cli.BisectorCommandLineInterface(DummyDomain)
149 bisector.main('init', '--num=10', '--old=0', '--new=9')
150 bisector.main('config', 'switch', 'true')
151 bisector.main('config', 'eval', 'false')
152 with self.assertRaises(core.VerificationFailed):
153 bisector.main('run')
154
155 def test_simple(self):
156 bisector = cli.BisectorCommandLineInterface(DummyDomain)
157 bisector.main('init', '--num=20', '--old=3', '--new=15')
158 bisector.main('config', 'switch', 'true')
159 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
160 bisector.main('run')
161 self.assertEqual(bisector.strategy.get_best_guess(), 7)
162
163 def test_switch_fail(self):
164 bisector = cli.BisectorCommandLineInterface(DummyDomain)
165 bisector.main('init', '--num=20', '--old=3', '--new=15')
166 bisector.main('config', 'switch', 'false')
167 bisector.main('config', 'eval', 'sh', '-c', '[ "$BISECT_REV" -lt 7 ]')
168 with self.assertRaises(core.ExecutionFatalError):
169 bisector.main('run')
170
171 def test_run_classic(self):
172 bisector = cli.BisectorCommandLineInterface(DummyDomain)
173 bisector.main('init', '--num=200', '--old=0', '--new=99')
174 bisector.main('config', 'switch', 'true')
175 bisector.main('config', 'eval', 'false')
176
177 def do_evaluate(_cmd, _domain, rev):
178 if int(rev) < 42:
179 return 'old', []
180 return 'new', []
181
182 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
183 # two verify
184 with mock.patch(
185 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
186 bisector.main('next')
187 self.assertEqual(mock_stdout.getvalue(), '99\n')
188 bisector.main('run', '-1')
189 with mock.patch(
190 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
191 bisector.main('next')
192 self.assertEqual(mock_stdout.getvalue(), '0\n')
193 bisector.main('run', '-1')
194 self.assertEqual(bisector.strategy.get_range(), (0, 99))
195
196 bisector.main('run', '-1')
197 self.assertEqual(bisector.strategy.get_range(), (0, 49))
198
199 bisector.main('run')
200 self.assertEqual(bisector.strategy.get_best_guess(), 42)
201 self.assertEqual(bisector.states.stats['eval_count'], 2 + 7)
202
203 with mock.patch(
204 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
205 bisector.main('next')
206 self.assertEqual(mock_stdout.getvalue(), 'done\n')
207
208 def test_run_noisy(self):
209 bisector = cli.BisectorCommandLineInterface(DummyDomain)
210 bisector.main('init', '--num=100', '--old=0', '--new=99',
211 '--noisy=old=1/10,new=9/10')
212 bisector.main('config', 'switch', 'true')
213 bisector.main('config', 'eval', 'false')
214
215 def do_evaluate(_cmd, _domain, rev):
216 if int(rev) < 42:
217 p = 0.1
218 else:
219 p = 0.9
220 if random.random() < p:
221 return 'new', []
222 return 'old', []
223
224 with mock.patch('bisect_kit.cli.do_evaluate', do_evaluate):
225 bisector.main('run')
226 self.assertEqual(bisector.strategy.get_best_guess(), 42)
227
228 with mock.patch(
229 'sys.stdout', new_callable=StringIO.StringIO) as mock_stdout:
230 bisector.main('next')
231 # There might be small math error near the boundary of confidence.
232 self.assertIn(mock_stdout.getvalue(), ['done\n', '41\n', '42\n'])
233
234 def test_cmd_old_and_new(self):
235 """Tests cmd_old and cmd_new"""
236 bisector = cli.BisectorCommandLineInterface(DummyDomain)
237 bisector.main('init', '--num=100', '--old=0', '--new=99')
238 bisector.main('config', 'switch', 'true')
239 bisector.main('config', 'eval', 'false')
240 bisector.main('old', '0')
241 bisector.main('new', '99')
242 bisector.main('run', '-1')
243 self.assertEqual(bisector.strategy.get_range(), (0, 49))
244
245 bisector.main('old', '20')
246 bisector.main('new', '40')
247 bisector.main('run', '-1')
248 self.assertEqual(bisector.strategy.get_range(), (20, 30))
249
250 bisector.main('skip', '20*10')
251 bisector.main('skip', '30*10')
252 self.assertEqual(bisector.strategy.get_range(), (20, 30))
253
254 def test_cmd_switch(self):
255 """Test cmd_switch"""
256 bisector = cli.BisectorCommandLineInterface(DummyDomain)
257 bisector.main('init', '--num=100', '--old=0', '--new=99')
258 bisector.main('config', 'switch', 'true')
259 bisector.main('config', 'eval', 'false')
260
261 switched = []
262
263 def do_switch(_cmd, _domain, rev):
264 switched.append(rev)
265
266 with mock.patch('bisect_kit.cli.do_switch', do_switch):
267 bisector.main('switch', 'next')
268 bisector.main('switch', '3')
269 bisector.main('switch', '5')
270 bisector.main('switch', '4')
271 self.assertEqual(switched, ['99', '3', '5', '4'])
272
273 def test_cmd_view(self):
274 bisector = cli.BisectorCommandLineInterface(DummyDomain)
275 bisector.main('init', '--num=100', '--old=10', '--new=90')
276 with mock.patch.object(DummyDomain, 'view') as mock_view:
277 bisector.main('view')
278 mock_view.assert_called_with('10', '90')
279
280 def test_cmd_config_confidence(self):
281 bisector = cli.BisectorCommandLineInterface(DummyDomain)
282 bisector.main('init', '--num=100', '--old=10', '--new=90',
283 '--confidence=0.75')
284
285 with self.assertRaises(core.ExecutionFatalError):
286 bisector.main('config', 'confidence', 'foo')
287 with self.assertRaises(core.ExecutionFatalError):
288 bisector.main('config', 'confidence', '0.9', '0.8')
289
290 self.assertEqual(bisector.states.config['confidence'], 0.75)
291 bisector.main('config', 'confidence', '0.875')
292 self.assertEqual(bisector.states.config['confidence'], 0.875)
293
294 def test_cmd_config_noisy(self):
295 bisector = cli.BisectorCommandLineInterface(DummyDomain)
296 bisector.main('init', '--num=100', '--old=10', '--new=90',
297 '--noisy=new=9/10')
298
299 with self.assertRaises(core.ExecutionFatalError):
300 bisector.main('config', 'noisy', 'hello', 'world')
301
302 self.assertEqual(bisector.states.config['noisy'], 'new=9/10')
303 bisector.main('config', 'noisy', 'old=1/10,new=8/9')
304 self.assertEqual(bisector.states.config['noisy'], 'old=1/10,new=8/9')
305
306 def test_current_status(self):
307 bisector = cli.BisectorCommandLineInterface(DummyDomain)
308 result = bisector.current_status()
309 self.assertEqual(result.get('inited'), False)
310
311 bisector.main('init', '--num=100', '--old=10', '--new=90',
312 '--noisy=new=9/10')
313 result = bisector.current_status()
314 self.assertEqual(result.get('inited'), True)
315
316
317if __name__ == '__main__':
318 unittest.main()