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