binary_search_tool_tester.py revision a19ea38e4482af3bc98365be773305b5167eddfd
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('./is_setup', '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('./is_setup')
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('./is_setup', '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(os.readlink(binary_search_state.STATE_FILE))
127    except OSError:
128      pass
129
130    cleanup_list = ['./is_setup',
131                    binary_search_state.STATE_FILE,
132                    'noinc_prune_bad',
133                    'noinc_prune_good']
134    for f in cleanup_list:
135      if os.path.exists(f):
136        os.remove(f)
137
138  def runTest(self):
139    ret = binary_search_state.Run(get_initial_items='./gen_init_list.py',
140                                  switch_to_good='./switch_to_good.py',
141                                  switch_to_bad='./switch_to_bad.py',
142                                  test_script='./is_good.py',
143                                  prune=True,
144                                  file_args=True)
145    self.assertEquals(ret, 0)
146    self.check_output()
147
148  def test_arg_parse(self):
149    args = ['--get_initial_items', './gen_init_list.py', '--switch_to_good',
150            './switch_to_good.py', '--switch_to_bad', './switch_to_bad.py',
151            '--test_script', './is_good.py', '--prune', '--file_args']
152    ret = binary_search_state.Main(args)
153    self.assertEquals(ret, 0)
154    self.check_output()
155
156  def test_test_setup_script(self):
157    os.remove('./is_setup')
158    with self.assertRaises(AssertionError):
159      ret = binary_search_state.Run(get_initial_items='./gen_init_list.py',
160                                    switch_to_good='./switch_to_good.py',
161                                    switch_to_bad='./switch_to_bad.py',
162                                    test_script='./is_good.py',
163                                    prune=True,
164                                    file_args=True)
165
166    ret = binary_search_state.Run(get_initial_items='./gen_init_list.py',
167                                  switch_to_good='./switch_to_good.py',
168                                  switch_to_bad='./switch_to_bad.py',
169                                  test_script='./is_good.py',
170                                  test_setup_script='./test_setup.py',
171                                  prune=True,
172                                  file_args=True)
173    self.assertEquals(ret, 0)
174    self.check_output()
175
176  def test_bad_test_setup_script(self):
177    with self.assertRaises(AssertionError):
178      binary_search_state.Run(get_initial_items='./gen_init_list.py',
179                              switch_to_good='./switch_to_good.py',
180                              switch_to_bad='./switch_to_bad.py',
181                              test_script='./is_good.py',
182                              test_setup_script='./test_setup_bad.py',
183                              prune=True,
184                              file_args=True)
185
186  def test_bad_save_state(self):
187    state_file = binary_search_state.STATE_FILE
188    hidden_state_file = os.path.basename(binary_search_state.HIDDEN_STATE_FILE)
189
190    with open(state_file, 'w') as f:
191      f.write('test123')
192
193    bss = binary_search_state.MockBinarySearchState()
194    with self.assertRaises(binary_search_state.Error):
195      bss.SaveState()
196
197    with open(state_file, 'r') as f:
198      self.assertEquals(f.read(), 'test123')
199
200    os.remove(state_file)
201
202    # Cleanup generated save state that has no symlink
203    files = os.listdir(os.getcwd())
204    save_states = [x for x in files if x.startswith(hidden_state_file)]
205    _ = [os.remove(x) for x in save_states]
206
207  def test_save_state(self):
208    state_file = binary_search_state.STATE_FILE
209
210    bss = binary_search_state.MockBinarySearchState()
211    bss.SaveState()
212    self.assertTrue(os.path.exists(state_file))
213    first_state = os.readlink(state_file)
214
215    bss.SaveState()
216    second_state = os.readlink(state_file)
217    self.assertTrue(os.path.exists(state_file))
218    self.assertTrue(second_state != first_state)
219    self.assertFalse(os.path.exists(first_state))
220
221    bss.RemoveState()
222    self.assertFalse(os.path.islink(state_file))
223    self.assertFalse(os.path.exists(second_state))
224
225  def test_load_state(self):
226    test_items = [1, 2, 3, 4, 5]
227
228    bss = binary_search_state.MockBinarySearchState()
229    bss.all_items = test_items
230    bss.currently_good_items = set([1, 2, 3])
231    bss.currently_bad_items = set([4, 5])
232    bss.SaveState()
233
234    bss = None
235
236    bss2 = binary_search_state.MockBinarySearchState.LoadState()
237    self.assertEquals(bss2.all_items, test_items)
238    self.assertEquals(bss2.currently_good_items, set([]))
239    self.assertEquals(bss2.currently_bad_items, set([]))
240
241  def test_tmp_cleanup(self):
242    bss = binary_search_state.MockBinarySearchState(
243        get_initial_items='echo "0\n1\n2\n3"',
244        switch_to_good='./switch_tmp.py',
245        file_args=True)
246    bss.SwitchToGood(['0', '1', '2', '3'])
247
248    tmp_file = None
249    with open('tmp_file', 'r') as f:
250      tmp_file = f.read()
251    os.remove('tmp_file')
252
253    self.assertFalse(os.path.exists(tmp_file))
254    ws = common.ReadWorkingSet()
255    for i in range(3):
256      self.assertEquals(ws[i], 42)
257
258  def test_verify_fail(self):
259    bss = binary_search_state.MockBinarySearchState(
260        get_initial_items='./gen_init_list.py',
261        switch_to_good='./switch_to_bad.py',
262        switch_to_bad='./switch_to_good.py',
263        test_script='./is_good.py',
264        prune=True,
265        file_args=True,
266        verify=True)
267    with self.assertRaises(AssertionError):
268      bss.DoVerify()
269
270  def test_early_terminate(self):
271    bss = binary_search_state.MockBinarySearchState(
272        get_initial_items='./gen_init_list.py',
273        switch_to_good='./switch_to_good.py',
274        switch_to_bad='./switch_to_bad.py',
275        test_script='./is_good.py',
276        prune=True,
277        file_args=True,
278        iterations=1)
279    bss.DoSearch()
280    self.assertFalse(bss.found_items)
281
282  def test_no_prune(self):
283    bss = binary_search_state.MockBinarySearchState(
284        get_initial_items='./gen_init_list.py',
285        switch_to_good='./switch_to_good.py',
286        switch_to_bad='./switch_to_bad.py',
287        test_script='./is_good.py',
288        test_setup_script='./test_setup.py',
289        prune=False,
290        file_args=True)
291    bss.DoSearch()
292    self.assertEquals(len(bss.found_items), 1)
293
294    bad_objs = common.ReadObjectsFile()
295    found_obj = int(bss.found_items.pop())
296    self.assertEquals(bad_objs[found_obj], 1)
297
298  def test_set_file(self):
299    binary_search_state.Run(
300        get_initial_items='./gen_init_list.py',
301        switch_to_good='./switch_to_good_set_file.py',
302        switch_to_bad='./switch_to_bad_set_file.py',
303        test_script='./is_good.py',
304        prune=True,
305        file_args=True,
306        verify=True)
307    self.check_output()
308
309  def test_noincremental_prune(self):
310    ret = binary_search_state.Run(
311        get_initial_items='./gen_init_list.py',
312        switch_to_good='./switch_to_good_noinc_prune.py',
313        switch_to_bad='./switch_to_bad_noinc_prune.py',
314        test_script='./is_good_noinc_prune.py',
315        test_setup_script='./test_setup.py',
316        prune=True,
317        noincremental=True,
318        file_args=True,
319        verify=False)
320    self.assertEquals(ret, 0)
321    self.check_output()
322
323  def check_output(self):
324    _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
325        ('grep "Bad items are: " logs/binary_search_tool_tester.py.out | '
326         'tail -n1'))
327    ls = out.splitlines()
328    self.assertEqual(len(ls), 1)
329    line = ls[0]
330
331    _, _, bad_ones = line.partition('Bad items are: ')
332    bad_ones = bad_ones.split()
333    expected_result = common.ReadObjectsFile()
334
335    # Reconstruct objects file from bad_ones and compare
336    actual_result = [0] * len(expected_result)
337    for bad_obj in bad_ones:
338      actual_result[int(bad_obj)] = 1
339
340    self.assertEqual(actual_result, expected_result)
341
342
343class BisectStressTest(unittest.TestCase):
344  """Stress tests for bisecting tool."""
345
346  def test_every_obj_bad(self):
347    amt = 25
348    gen_obj.Main(['--obj_num', str(amt), '--bad_obj_num', str(amt)])
349    ret = binary_search_state.Run(get_initial_items='./gen_init_list.py',
350                                  switch_to_good='./switch_to_good.py',
351                                  switch_to_bad='./switch_to_bad.py',
352                                  test_script='./is_good.py',
353                                  prune=True,
354                                  file_args=True,
355                                  verify=False)
356    self.assertEquals(ret, 0)
357    self.check_output()
358
359  def test_every_index_is_bad(self):
360    amt = 25
361    for i in range(amt):
362      obj_list = ['0'] * amt
363      obj_list[i] = '1'
364      obj_list = ','.join(obj_list)
365      gen_obj.Main(['--obj_list', obj_list])
366      ret = binary_search_state.Run(get_initial_items='./gen_init_list.py',
367                                    switch_to_good='./switch_to_good.py',
368                                    switch_to_bad='./switch_to_bad.py',
369                                    test_setup_script='./test_setup.py',
370                                    test_script='./is_good.py',
371                                    prune=True,
372                                    file_args=True)
373      self.assertEquals(ret, 0)
374      self.check_output()
375
376
377  def check_output(self):
378    _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput(
379        ('grep "Bad items are: " logs/binary_search_tool_tester.py.out | '
380         'tail -n1'))
381    ls = out.splitlines()
382    self.assertEqual(len(ls), 1)
383    line = ls[0]
384
385    _, _, bad_ones = line.partition('Bad items are: ')
386    bad_ones = bad_ones.split()
387    expected_result = common.ReadObjectsFile()
388
389    # Reconstruct objects file from bad_ones and compare
390    actual_result = [0] * len(expected_result)
391    for bad_obj in bad_ones:
392      actual_result[int(bad_obj)] = 1
393
394    self.assertEqual(actual_result, expected_result)
395
396
397def Main(argv):
398  num_tests = 2
399  if len(argv) > 1:
400    num_tests = int(argv[1])
401
402  suite = unittest.TestSuite()
403  for _ in range(0, num_tests):
404    suite.addTest(BisectingUtilsTest())
405  suite.addTest(BisectingUtilsTest('test_arg_parse'))
406  suite.addTest(BisectingUtilsTest('test_test_setup_script'))
407  suite.addTest(BisectingUtilsTest('test_bad_test_setup_script'))
408  suite.addTest(BisectingUtilsTest('test_bad_save_state'))
409  suite.addTest(BisectingUtilsTest('test_save_state'))
410  suite.addTest(BisectingUtilsTest('test_load_state'))
411  suite.addTest(BisectingUtilsTest('test_tmp_cleanup'))
412  suite.addTest(BisectingUtilsTest('test_verify_fail'))
413  suite.addTest(BisectingUtilsTest('test_early_terminate'))
414  suite.addTest(BisectingUtilsTest('test_no_prune'))
415  suite.addTest(BisectingUtilsTest('test_set_file'))
416  suite.addTest(BisectingUtilsTest('test_noincremental_prune'))
417  suite.addTest(BisectTest('test_full_bisector'))
418  suite.addTest(BisectStressTest('test_every_obj_bad'))
419  suite.addTest(BisectStressTest('test_every_index_is_bad'))
420  runner = unittest.TextTestRunner()
421  runner.run(suite)
422
423
424if __name__ == '__main__':
425  Main(sys.argv)
426