binary_search_tool_tester.py revision a8af9a7a2462b00e72deff99327bdb452a715277
1#!/usr/bin/python2
2
3# Copyright 2012 Google Inc. All Rights Reserved.
4"""Tests for bisecting tool."""
5
6from __future__ import print_function
7
8__author__ = 'shenhan@google.com (Han Shen)'
9
10import os
11import random
12import sys
13import unittest
14
15from cros_utils import command_executer
16from binary_search_tool import binary_search_state
17from binary_search_tool import bisect
18
19import common
20import gen_obj
21
22
23def GenObj():
24  obj_num = random.randint(100, 1000)
25  bad_obj_num = random.randint(obj_num / 100, obj_num / 20)
26  if bad_obj_num == 0:
27    bad_obj_num = 1
28  gen_obj.Main(['--obj_num', str(obj_num), '--bad_obj_num', str(bad_obj_num)])
29
30
31def CleanObj():
32  os.remove(common.OBJECTS_FILE)
33  os.remove(common.WORKING_SET_FILE)
34  print('Deleted "{0}" and "{1}"'.format(common.OBJECTS_FILE,
35                                         common.WORKING_SET_FILE))
36
37
38class BisectTest(unittest.TestCase):
39  """Tests for bisect.py"""
40
41  def setUp(self):
42    with open('./installed', 'w'):
43      pass
44
45    try:
46      os.remove(binary_search_state.STATE_FILE)
47    except OSError:
48      pass
49
50  def tearDown(self):
51    try:
52      os.remove('./installed')
53      os.remove(os.readlink(binary_search_state.STATE_FILE))
54      os.remove(binary_search_state.STATE_FILE)
55    except OSError:
56      pass
57
58  class FullBisector(bisect.Bisector):
59    """Test bisector to test bisect.py with"""
60
61    def __init__(self, options, overrides):
62      super(BisectTest.FullBisector, self).__init__(options, overrides)
63
64    def PreRun(self):
65      GenObj()
66      return 0
67
68    def Run(self):
69      return binary_search_state.Run(get_initial_items='./gen_init_list.py',
70                                     switch_to_good='./switch_to_good.py',
71                                     switch_to_bad='./switch_to_bad.py',
72                                     test_script='./is_good.py',
73                                     prune=True,
74                                     file_args=True)
75
76    def PostRun(self):
77      CleanObj()
78      return 0
79
80  def test_full_bisector(self):
81    ret = bisect.Run(self.FullBisector({}, {}))
82    self.assertEquals(ret, 0)
83    self.assertFalse(os.path.exists(common.OBJECTS_FILE))
84    self.assertFalse(os.path.exists(common.WORKING_SET_FILE))
85
86  def check_output(self):
87    _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
88        ('grep "Bad items are: " logs/binary_search_tool_tester.py.out | '
89         'tail -n1'))
90    ls = out.splitlines()
91    self.assertEqual(len(ls), 1)
92    line = ls[0]
93
94    _, _, bad_ones = line.partition('Bad items are: ')
95    bad_ones = bad_ones.split()
96    expected_result = common.ReadObjectsFile()
97
98    # Reconstruct objects file from bad_ones and compare
99    actual_result = [0] * len(expected_result)
100    for bad_obj in bad_ones:
101      actual_result[int(bad_obj)] = 1
102
103    self.assertEqual(actual_result, expected_result)
104
105
106class BisectingUtilsTest(unittest.TestCase):
107  """Tests for bisecting tool."""
108
109  def setUp(self):
110    """Generate [100-1000] object files, and 1-5% of which are bad ones."""
111    GenObj()
112
113    with open('./installed', 'w'):
114      pass
115
116    try:
117      os.remove(binary_search_state.STATE_FILE)
118    except OSError:
119      pass
120
121  def tearDown(self):
122    """Cleanup temp files."""
123    CleanObj()
124
125    try:
126      os.remove('./installed')
127      os.remove(os.readlink(binary_search_state.STATE_FILE))
128      os.remove(binary_search_state.STATE_FILE)
129    except OSError:
130      pass
131
132  def runTest(self):
133    ret = binary_search_state.Run(get_initial_items='./gen_init_list.py',
134                                  switch_to_good='./switch_to_good.py',
135                                  switch_to_bad='./switch_to_bad.py',
136                                  test_script='./is_good.py',
137                                  prune=True,
138                                  file_args=True)
139    self.assertEquals(ret, 0)
140    self.check_output()
141
142  def test_arg_parse(self):
143    args = ['--get_initial_items', './gen_init_list.py', '--switch_to_good',
144            './switch_to_good.py', '--switch_to_bad', './switch_to_bad.py',
145            '--test_script', './is_good.py', '--prune', '--file_args']
146    ret = binary_search_state.Main(args)
147    self.assertEquals(ret, 0)
148    self.check_output()
149
150  def test_install_script(self):
151    os.remove('./installed')
152    with self.assertRaises(AssertionError):
153      ret = binary_search_state.Run(get_initial_items='./gen_init_list.py',
154                                    switch_to_good='./switch_to_good.py',
155                                    switch_to_bad='./switch_to_bad.py',
156                                    test_script='./is_good.py',
157                                    prune=True,
158                                    file_args=True)
159
160    ret = binary_search_state.Run(get_initial_items='./gen_init_list.py',
161                                  switch_to_good='./switch_to_good.py',
162                                  switch_to_bad='./switch_to_bad.py',
163                                  test_script='./is_good.py',
164                                  install_script='./install.py',
165                                  prune=True,
166                                  file_args=True)
167    self.assertEquals(ret, 0)
168    self.check_output()
169
170  def test_bad_install_script(self):
171    with self.assertRaises(AssertionError):
172      binary_search_state.Run(get_initial_items='./gen_init_list.py',
173                              switch_to_good='./switch_to_good.py',
174                              switch_to_bad='./switch_to_bad.py',
175                              test_script='./is_good.py',
176                              install_script='./install_bad.py',
177                              prune=True,
178                              file_args=True)
179
180  def test_bad_save_state(self):
181    state_file = binary_search_state.STATE_FILE
182    hidden_state_file = os.path.basename(binary_search_state.HIDDEN_STATE_FILE)
183
184    with open(state_file, 'w') as f:
185      f.write('test123')
186
187    bss = binary_search_state.MockBinarySearchState()
188    with self.assertRaises(binary_search_state.Error):
189      bss.SaveState()
190
191    with open(state_file, 'r') as f:
192      self.assertEquals(f.read(), 'test123')
193
194    os.remove(state_file)
195
196    # Cleanup generated save state that has no symlink
197    files = os.listdir(os.getcwd())
198    save_states = [x for x in files if x.startswith(hidden_state_file)]
199    _ = [os.remove(x) for x in save_states]
200
201  def test_save_state(self):
202    state_file = binary_search_state.STATE_FILE
203
204    bss = binary_search_state.MockBinarySearchState()
205    bss.SaveState()
206    self.assertTrue(os.path.exists(state_file))
207    first_state = os.readlink(state_file)
208
209    bss.SaveState()
210    second_state = os.readlink(state_file)
211    self.assertTrue(os.path.exists(state_file))
212    self.assertTrue(second_state != first_state)
213    self.assertFalse(os.path.exists(first_state))
214
215    bss.RemoveState()
216    self.assertFalse(os.path.islink(state_file))
217    self.assertFalse(os.path.exists(second_state))
218
219  def test_load_state(self):
220    test_items = [1, 2, 3, 4, 5]
221
222    bss = binary_search_state.MockBinarySearchState()
223    bss.all_items = test_items
224    bss.SaveState()
225
226    bss = None
227
228    bss2 = binary_search_state.MockBinarySearchState.LoadState()
229    self.assertEquals(bss2.all_items, test_items)
230
231  def test_tmp_cleanup(self):
232    bss = binary_search_state.MockBinarySearchState(
233        get_initial_items='echo "0\n1\n2\n3"',
234        switch_to_good='./switch_tmp.py',
235        file_args=True)
236    bss.SwitchToGood(['0', '1', '2', '3'])
237
238    tmp_file = None
239    with open('tmp_file', 'r') as f:
240      tmp_file = f.read()
241    os.remove('tmp_file')
242
243    self.assertFalse(os.path.exists(tmp_file))
244    ws = common.ReadWorkingSet()
245    for i in range(3):
246      self.assertEquals(ws[i], 42)
247
248  def test_verify_fail(self):
249    bss = binary_search_state.MockBinarySearchState(
250        get_initial_items='./gen_init_list.py',
251        switch_to_good='./switch_to_bad.py',
252        switch_to_bad='./switch_to_good.py',
253        test_script='./is_good.py',
254        prune=True,
255        file_args=True,
256        verify_level=1)
257    with self.assertRaises(AssertionError):
258      bss.DoVerify()
259
260  def test_early_terminate(self):
261    bss = binary_search_state.MockBinarySearchState(
262        get_initial_items='./gen_init_list.py',
263        switch_to_good='./switch_to_good.py',
264        switch_to_bad='./switch_to_bad.py',
265        test_script='./is_good.py',
266        prune=True,
267        file_args=True,
268        iterations=1)
269    bss.DoSearch()
270    self.assertFalse(bss.found_items)
271
272  def test_no_prune(self):
273    bss = binary_search_state.MockBinarySearchState(
274        get_initial_items='./gen_init_list.py',
275        switch_to_good='./switch_to_good.py',
276        switch_to_bad='./switch_to_bad.py',
277        test_script='./is_good.py',
278        install_script='./install.py',
279        prune=False,
280        file_args=True)
281    bss.DoSearch()
282    self.assertEquals(len(bss.found_items), 1)
283
284    bad_objs = common.ReadObjectsFile()
285    found_obj = int(bss.found_items.pop())
286    self.assertEquals(bad_objs[found_obj], 1)
287
288  def check_output(self):
289    _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
290        ('grep "Bad items are: " logs/binary_search_tool_tester.py.out | '
291         'tail -n1'))
292    ls = out.splitlines()
293    self.assertEqual(len(ls), 1)
294    line = ls[0]
295
296    _, _, bad_ones = line.partition('Bad items are: ')
297    bad_ones = bad_ones.split()
298    expected_result = common.ReadObjectsFile()
299
300    # Reconstruct objects file from bad_ones and compare
301    actual_result = [0] * len(expected_result)
302    for bad_obj in bad_ones:
303      actual_result[int(bad_obj)] = 1
304
305    self.assertEqual(actual_result, expected_result)
306
307
308def Main(argv):
309  num_tests = 2
310  if len(argv) > 1:
311    num_tests = int(argv[1])
312
313  suite = unittest.TestSuite()
314  for _ in range(0, num_tests):
315    suite.addTest(BisectingUtilsTest())
316  suite.addTest(BisectingUtilsTest('test_arg_parse'))
317  suite.addTest(BisectingUtilsTest('test_install_script'))
318  suite.addTest(BisectingUtilsTest('test_bad_install_script'))
319  suite.addTest(BisectingUtilsTest('test_bad_save_state'))
320  suite.addTest(BisectingUtilsTest('test_save_state'))
321  suite.addTest(BisectingUtilsTest('test_load_state'))
322  suite.addTest(BisectingUtilsTest('test_tmp_cleanup'))
323  suite.addTest(BisectingUtilsTest('test_verify_fail'))
324  suite.addTest(BisectingUtilsTest('test_early_terminate'))
325  suite.addTest(BisectingUtilsTest('test_no_prune'))
326  suite.addTest(BisectTest('test_full_bisector'))
327  runner = unittest.TextTestRunner()
328  runner.run(suite)
329
330
331if __name__ == '__main__':
332  Main(sys.argv)
333