diff options
-rwxr-xr-x | binary_search_tool/android/generate_cmd.sh | 11 | ||||
-rwxr-xr-x | binary_search_tool/binary_search_perforce.py | 93 | ||||
-rwxr-xr-x | binary_search_tool/binary_search_state.py | 285 | ||||
-rw-r--r-- | binary_search_tool/common.py | 7 | ||||
-rw-r--r-- | binary_search_tool/pass_mapping.py | 32 | ||||
-rwxr-xr-x | binary_search_tool/test/binary_search_tool_tester.py | 26 |
6 files changed, 397 insertions, 57 deletions
diff --git a/binary_search_tool/android/generate_cmd.sh b/binary_search_tool/android/generate_cmd.sh index eaae738b..7db3e7e0 100755 --- a/binary_search_tool/android/generate_cmd.sh +++ b/binary_search_tool/android/generate_cmd.sh @@ -38,4 +38,15 @@ output+=${result} echo -e "${output}" > android/cmd_script.sh +chmod u+x android/cmd_script.sh + echo 'Script created as android/cmd_script.sh' + +# Check if compiler is LLVM. +if grep -q "clang" android/cmd_script.sh +then + exit 0 +else + echo 'Pass/transformation level bisection only works for LLVM compiler.' + exit 1 +fi
\ No newline at end of file diff --git a/binary_search_tool/binary_search_perforce.py b/binary_search_tool/binary_search_perforce.py index aaa09eef..a4f8c1c6 100755 --- a/binary_search_tool/binary_search_perforce.py +++ b/binary_search_tool/binary_search_perforce.py @@ -1,4 +1,8 @@ #!/usr/bin/env python2 + +# Copyright 2018 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. """Module of binary serch for perforce.""" from __future__ import print_function @@ -63,6 +67,66 @@ class BinarySearchPoint(object): self.tag = tag +class BinarySearcherForPass(object): + """Class of pass level binary searcher.""" + + def __init__(self, logger_to_set=None): + self.current = 0 + self.lo = 0 + self.hi = 0 + self.total = 0 + if logger_to_set is not None: + self.logger = logger_to_set + else: + self.logger = logger.GetLogger() + + def GetNext(self): + # For the first run, update self.hi with total pass/transformation count + if self.hi == 0: + self.hi = self.total + self.current = (self.hi + self.lo) / 2 + message = ('Bisecting between: (%d, %d)' % (self.lo, self.hi)) + self.logger.LogOutput(message, print_to_console=verbose) + message = ('Current limit number: %d' % self.current) + self.logger.LogOutput(message, print_to_console=verbose) + return self.current + + def SetStatus(self, status): + """Set lo/hi status based on test script result + + If status == 0, it means that runtime error is not introduced until current + pass/transformation, so we need to increase lower bound for binary search. + + If status == 1, it means that runtime error still happens with current pass/ + transformation, so we need to decrease upper bound for binary search. + + Return: + True if we find the bad pass/transformation, or cannot find bad one after + decreasing to the first pass/transformation. Otherwise False. + """ + assert status == 0 or status == 1 or status == 125 + + if self.current == 0: + message = ('Runtime error occurs before first pass/transformation. ' + 'Stop binary searching.') + self.logger.LogOutput(message, print_to_console=verbose) + return True + + if status == 0: + message = ('Runtime error is not reproduced, increasing lower bound.') + self.logger.LogOutput(message, print_to_console=verbose) + self.lo = self.current + 1 + elif status == 1: + message = ('Runtime error is reproduced, decreasing upper bound..') + self.logger.LogOutput(message, print_to_console=verbose) + self.hi = self.current + + if self.lo >= self.hi: + return True + + return False + + class BinarySearcher(object): """Class of binary searcher.""" @@ -167,9 +231,8 @@ class BinarySearcher(object): self.GetNextFlakyBinary() # TODO: Add an estimated time remaining as well. - message = ('Estimated tries: min: %d max: %d\n' % - (1 + math.log(self.hi - self.lo, 2), - self.hi - self.lo - len(self.skipped_indices))) + message = ('Estimated tries: min: %d max: %d\n' % (1 + math.log( + self.hi - self.lo, 2), self.hi - self.lo - len(self.skipped_indices))) self.logger.LogOutput(message, print_to_console=verbose) message = ('lo: %d hi: %d current: %d version: %s\n' % (self.lo, self.hi, self.current, self.sorted_list[self.current])) @@ -186,8 +249,8 @@ class BinarySearcher(object): def GetAllPoints(self): to_return = '' for i in range(len(self.sorted_list)): - to_return += ('%d %d %s\n' % (self.points[i].status, i, - self.points[i].revision)) + to_return += ( + '%d %d %s\n' % (self.points[i].status, i, self.points[i].revision)) return to_return @@ -276,8 +339,9 @@ class P4BinarySearcher(VCSBinarySearcher): command = 'cd %s && g4 changes ...' % self.checkout_dir _, out, _ = self.ce.RunCommandWOutput(command) self.changes = re.findall(r'Change (\d+)', out) - change_infos = re.findall(r'Change (\d+) on ([\d/]+) by ' - r"([^\s]+) ('[^']*')", out) + change_infos = re.findall( + r'Change (\d+) on ([\d/]+) by ' + r"([^\s]+) ('[^']*')", out) for change_info in change_infos: ri = RevisionInfo(change_info[1], change_info[2], change_info[3]) self.rim[change_info[0]] = ri @@ -330,9 +394,8 @@ class P4BinarySearcher(VCSBinarySearcher): else: to_return += ('%s\t%d\t%s\t%s\t%s\t%s\t%s\t%s\n' % (change, ri.status, ri.date, ri.client, ri.description, - self.job_log_root + change + '.cmd', - self.job_log_root + change + '.out', - self.job_log_root + change + '.err')) + self.job_log_root + change + '.cmd', self.job_log_root + + change + '.out', self.job_log_root + change + '.err')) return to_return @@ -367,9 +430,9 @@ class P4GCCBinarySearcher(P4BinarySearcher): self.CleanupCLs() # Change the revision of only the gcc part of the toolchain. - command = ('cd %s/gcctools/google_vendor_src_branch/gcc ' - '&& g4 revert ...; g4 sync @%s' % (self.checkout_dir, - current_revision)) + command = ( + 'cd %s/gcctools/google_vendor_src_branch/gcc ' + '&& g4 revert ...; g4 sync @%s' % (self.checkout_dir, current_revision)) self.current_ce.RunCommand(command) self.HandleBrokenCLs(current_revision) @@ -427,8 +490,8 @@ def Main(argv): ce = command_executer.GetCommandExecuter() command = '%s %s' % (script, p4gccbs.checkout_dir) status = ce.RunCommand(command) - message = ('Revision: %s produced: %d status\n' % (current_revision, - status)) + message = ( + 'Revision: %s produced: %d status\n' % (current_revision, status)) logger.GetLogger().LogOutput(message, print_to_console=verbose) terminated = p4gccbs.SetStatus(status) num_tries -= 1 diff --git a/binary_search_tool/binary_search_state.py b/binary_search_tool/binary_search_state.py index 645e613c..0d5810c3 100755 --- a/binary_search_tool/binary_search_state.py +++ b/binary_search_tool/binary_search_state.py @@ -3,7 +3,6 @@ # Copyright 2018 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. - """The binary search wrapper.""" from __future__ import print_function @@ -14,6 +13,7 @@ import errno import math import os import pickle +import re import sys import tempfile import time @@ -26,6 +26,7 @@ from cros_utils import command_executer from cros_utils import logger import binary_search_perforce +import pass_mapping GOOD_SET_VAR = 'BISECT_GOOD_SET' BAD_SET_VAR = 'BISECT_BAD_SET' @@ -93,6 +94,8 @@ class BinarySearchState(object): self.search_cycles = 0 self.binary_search = None self.all_items = None + self.cmd_script = None + self.mode = None self.PopulateItemsUsingCommand(self.get_initial_items) self.currently_good_items = set([]) self.currently_bad_items = set([]) @@ -188,6 +191,17 @@ class BinarySearchState(object): ret, _, _ = self.ce.RunCommandWExceptionCleanup(command) return ret + def GenerateBadCommandScript(self, bad_items): + """Generate command line script for building bad item.""" + assert not self.prune, 'Prune must be false if pass_bisect is set.' + assert len(bad_items) == 1, 'Pruning is off, but number of bad ' \ + 'items found was not 1.' + item = list(bad_items)[0] + command = '%s %s' % (self.pass_bisect, item) + ret, _, _ = self.ce.RunCommandWExceptionCleanup( + command, print_to_console=self.verbose) + return ret + def DoVerify(self): """Verify correctness of test environment. @@ -222,14 +236,14 @@ class BinarySearchState(object): status = self.TestScript() assert status == 1, 'When reset_to_bad, status should be 1.' - def DoSearch(self): + def DoSearchBadItems(self): """Perform full search for bad items. Perform full search until prune_iterations number of bad items are found. """ while (True and len(self.all_items) > 1 and self.prune_cycles < self.prune_iterations): - terminated = self.DoBinarySearch() + terminated = self.DoBinarySearchBadItems() self.prune_cycles += 1 if not terminated: break @@ -240,6 +254,7 @@ class BinarySearchState(object): if prune_index == len(self.all_items) - 1: self.l.LogOutput('First bad item is the last item. Breaking.') self.l.LogOutput('Bad items are: %s' % self.all_items[-1]) + self.found_items.add(self.all_items[-1]) break # If already seen item we have no new bad items to find, finish up @@ -279,24 +294,15 @@ class BinarySearchState(object): # If pass level bisecting is set, generate a script which contains command # line options to rebuild bad item. if self.pass_bisect: - assert not self.prune, 'Prune must be false if pass_bisect is set.' status = self.GenerateBadCommandScript(self.found_items) if status == 0: - self.l.LogOutput('Command script generated.') + self.cmd_script = os.path.join( + os.path.dirname(self.pass_bisect), 'cmd_script.sh') + self.l.LogOutput('Command script generated at %s.' % self.cmd_script) else: raise RuntimeError('Error while generating command script.') - def GenerateBadCommandScript(self, bad_items): - assert len(bad_items) == 1, 'Pruning is off, but number of bad items' \ - 'found was not 1.' - item = list(bad_items)[0] - - command = '%s %s' % (self.pass_bisect, item) - ret, _, _ = self.ce.RunCommandWExceptionCleanup(command, - print_to_console=self.verbose) - return ret - - def DoBinarySearch(self): + def DoBinarySearchBadItems(self): """Perform single iteration of binary search.""" # If in resume mode don't reset search_cycles if not self.resumed: @@ -307,7 +313,7 @@ class BinarySearchState(object): terminated = False while self.search_cycles < self.iterations and not terminated: self.SaveState() - self.OutputIterationProgress() + self.OutputIterationProgressBadItem() self.search_cycles += 1 [bad_items, good_items] = self.GetNextItems() @@ -328,6 +334,199 @@ class BinarySearchState(object): self.l.LogOutput(str(self), print_to_console=self.verbose) return terminated + def CollectPassName(self, pass_info): + """Mapping opt-bisect output of pass info to debugcounter name.""" + self.l.LogOutput('Pass info: %s' % pass_info, print_to_console=self.verbose) + + for desc in pass_mapping.pass_name: + if desc in pass_info: + return pass_mapping.pass_name[desc] + + # If pass not found, return None + return None + + def BuildWithPassLimit(self, limit): + """ Rebuild bad item with pass level bisect limit + + Run command line script generated by GenerateBadCommandScript(), with + pass level limit flags. + + Return: + pass_num: current number of the pass, or total number of passes if + limit set to -1. + pass_name: The debugcounter name of current limit pass. + """ + os.environ['LIMIT_FLAGS'] = '-mllvm -opt-bisect-limit=' + str(limit) + self.l.LogOutput( + 'Limit flags: %s' % os.environ['LIMIT_FLAGS'], + print_to_console=self.verbose) + command = self.cmd_script + _, _, msg = self.ce.RunCommandWOutput(command, print_to_console=False) + + # Massages we get will be like this: + # BISECT: running pass (9) <Pass Description> on <function> (<file>) + # BISECT: running pass (10) <Pass Description> on <module> (<file>) + # BISECT: NOT running pass (11) <Pass Description> on <SCG> (<file>) + # BISECT: NOT running pass (12) <Pass Description> on <SCG> (<file>) + # We want to get the pass description of last running pass, to have + # transformation level bisect on it. + if 'BISECT: ' not in msg: + raise RuntimeError('No bisect info printed, OptBisect may not be ' + 'supported by the compiler.') + + lines = msg.split('\n') + pass_num = 0 + last_pass = '' + for l in lines: + if 'running pass' in l: + # For situation of limit==-1, we want the total number of passes + if limit != -1 and 'BISECT: NOT ' in l: + break + pass_num += 1 + last_pass = l + if limit != -1 and pass_num != limit: + raise ValueError('[Error] While building, limit number does not match.') + return pass_num, self.CollectPassName(last_pass) + + def BuildWithTransformLimit(self, limit, pass_name=None, pass_limit=-1): + """ Rebuild bad item with transformation level bisect limit + + Run command line script generated by GenerateBadCommandScript(), with + pass level limit flags and transformation level limit flags. + + Args: + limit: transformation level limit for bad item + pass_name: name of bad pass debugcounter from pass level bisect result + pass_limit: pass level limit from pass level bisect result + Return: Total number of transformations if limit set to -1, else return 0. + """ + counter_name = pass_name + + os.environ['LIMIT_FLAGS'] = '-mllvm -opt-bisect-limit=' + \ + str(pass_limit) + \ + ' -mllvm -debug-counter=' + counter_name + \ + '-count=' + str(limit) + \ + ' -mllvm -print-debug-counter' + self.l.LogOutput( + 'Limit flags: %s' % os.environ['LIMIT_FLAGS'], + print_to_console=self.verbose) + command = self.cmd_script + _, _, msg = self.ce.RunCommandWOutput(command, print_to_console=False) + + if 'Counters and values:' not in msg: + raise RuntimeError('No bisect info printed, DebugCounter may not be ' + 'supported by the compiler.') + + # With debugcounter enabled, there will be DebugCounter counting info in + # the output. + lines = msg.split('\n') + for l in lines: + if pass_name in l: + # Output of debugcounter will be like: + # instcombine-visit: {10, 0, 20} + # dce-transform: {1, 0, -1} + # which indicates {Count, Skip, StopAfter}. + # The last number should be the limit we set. + # We want the first number as the total transformation count. + # Split each line by ,|{|} and we can get l_list as: + # ['instcombine: ', '10', '0', '20', ''] + # and we will need the second item in it. + l_list = re.split(',|{|}', l) + count = int(l_list[1]) + if limit == -1: + return count + # The returned value is only useful when limit == -1, which shows total + # transformation count. + return 0 + + def DoSearchBadPass(self): + """Perform full search for bad pass of bad item.""" + logger.GetLogger().LogOutput('Starting to bisect bad pass for bad item.') + + # Pass level bisection + self.mode = 'pass' + self.binary_search = binary_search_perforce.BinarySearcherForPass( + logger_to_set=self.l) + self.binary_search.total, _ = self.BuildWithPassLimit(-1) + logger.GetLogger().LogOutput( + 'Total %s number: %d' % (self.mode, self.binary_search.total)) + + pass_index, pass_name = self.DoBinarySearchBadPass() + + if (not pass_name and pass_index == 0): + raise ValueError('Bisecting passes cannot reproduce good result.') + logger.GetLogger().LogOutput('Bad pass found: %s.' % pass_name) + + # Transformation level bisection. + logger.GetLogger().LogOutput('Starting to bisect at transformation level.') + + self.mode = 'transform' + self.binary_search = binary_search_perforce.BinarySearcherForPass( + logger_to_set=self.l) + self.binary_search.total = self.BuildWithTransformLimit( + -1, pass_name, pass_index) + logger.GetLogger().LogOutput( + 'Total %s number: %d' % (self.mode, self.binary_search.total)) + + trans_index, _ = self.DoBinarySearchBadPass(pass_index, pass_name) + if (trans_index == 0): + raise ValueError('Bisecting %s cannot reproduce good result.' % pass_name) + logger.GetLogger().LogOutput( + 'Bisection result for bad item %s:\n' + 'Bad pass: %s at number %d\n' + 'Bad transformation number: %d' % (self.found_items, pass_name, + pass_index, trans_index)) + + def DoBinarySearchBadPass(self, pass_index=-1, pass_name=None): + """Perform single iteration of binary search at pass level + + Args: + pass_index: Works for transformation level bisection, indicates the limit + number of pass from pass level bisecting result. + pass_name: Works for transformation level bisection, indicates + DebugCounter name of the bad pass from pass level bisecting result. + Return: + index: Index of problematic pass/transformation. + pass_name: Works for pass level bisection, returns DebugCounter name for + bad pass. + """ + # If in resume mode don't reset search_cycles + if not self.resumed: + self.search_cycles = 0 + else: + self.resumed = False + + terminated = False + index = 0 + while self.search_cycles < self.iterations and not terminated: + self.SaveState() + self.OutputIterationProgressBadPass() + + self.search_cycles += 1 + current = self.binary_search.GetNext() + + if self.mode == 'pass': + index, pass_name = self.BuildWithPassLimit(current) + else: + self.BuildWithTransformLimit(current, pass_name, pass_index) + index = current + + # TODO: Newly generated object should not directly replace original + # one, need to put it somewhere and symbol link original one to it. + # Will update cmd_script to do it. + + status = self.TestSetupScript() + assert status == 0, 'Test setup should succeed.' + status = self.TestScript() + terminated = self.binary_search.SetStatus(status) + + if terminated: + self.l.LogOutput('Terminated!', print_to_console=self.verbose) + if not terminated: + self.l.LogOutput('Ran out of iterations searching...') + self.l.LogOutput(str(self), print_to_console=self.verbose) + return index, pass_name + def PopulateItemsUsingCommand(self, command): """Update all_items and binary search logic from executable. @@ -460,15 +659,21 @@ class BinarySearchState(object): progress = progress % (self.ElapsedTimeString(), progress_text) self.l.LogOutput(progress) - def OutputIterationProgress(self): + def OutputIterationProgressBadItem(self): out = ('Search %d of estimated %d.\n' 'Prune %d of max %d.\n' 'Current bad items found:\n' '%s\n') out = out % (self.search_cycles + 1, - math.ceil(math.log(len(self.all_items), 2)), - self.prune_cycles + 1, self.prune_iterations, - ', '.join(self.found_items)) + math.ceil(math.log(len(self.all_items), 2)), self.prune_cycles + + 1, self.prune_iterations, ', '.join(self.found_items)) + self._OutputProgress(out) + + def OutputIterationProgressBadPass(self): + out = ('Search %d of estimated %d.\n' 'Current limit: %s\n') + out = out % (self.search_cycles + 1, + math.ceil(math.log(self.binary_search.total, 2)), + self.binary_search.current) self._OutputProgress(out) def __str__(self): @@ -532,30 +737,34 @@ def Run(get_initial_items, prune_iterations=100, verbose=False, resume=False): - """Run binary search tool. Equivalent to running through terminal. + """Run binary search tool. + + Equivalent to running through terminal. Args: get_initial_items: Script to enumerate all items being binary searched switch_to_good: Script that will take items as input and switch them to good - set + set switch_to_bad: Script that will take items as input and switch them to bad - set + set test_script: Script that will determine if the current combination of good - and bad items make a "good" or "bad" result. + and bad items make a "good" or "bad" result. test_setup_script: Script to do necessary setup (building, compilation, - etc.) for test_script. + etc.) for test_script. iterations: How many binary search iterations to run before exiting. prune: If False the binary search tool will stop when the first bad item is - found. Otherwise then binary search tool will continue searching - until all bad items are found (or prune_iterations is reached). - pass_bisect: Script that takes single bad item from POPULATE_BAD and - returns the compiler command used to generate the bad item. - Requires that 'prune' be set to False. + found. Otherwise then binary search tool will continue searching until all + bad items are found (or prune_iterations is reached). + pass_bisect: Script that takes single bad item from POPULATE_BAD and returns + the compiler command used to generate the bad item. This will turn on + pass/ transformation level bisection for the bad item. Requires that + 'prune' be set to False, and needs support of `-opt-bisect-limit`(pass) + and `-print-debug-counter`(transformation) from LLVM. noincremental: Whether to send "diffs" of good/bad items to switch scripts. file_args: If True then arguments to switch scripts will be a file name - containing a newline separated list of the items to switch. - verify: If True, run tests to ensure initial good/bad sets actually - produce a good/bad result. + containing a newline separated list of the items to switch. + verify: If True, run tests to ensure initial good/bad sets actually produce + a good/bad result. prune_iterations: Max number of bad items to search for. verbose: If True will print extra debug information to user. resume: If True will resume using STATE_FILE. @@ -578,8 +787,8 @@ def Run(get_initial_items, 'ignoring given options and loading saved ' 'options instead.') else: - if not (get_initial_items and switch_to_good and - switch_to_bad and test_script): + if not (get_initial_items and switch_to_good and switch_to_bad and + test_script): logger.GetLogger().LogOutput('The following options are required: ' '[-i, -g, -b, -t] | [-r]') return 1 @@ -606,7 +815,9 @@ def Run(get_initial_items, bss.DoVerify() try: - bss.DoSearch() + bss.DoSearchBadItems() + if pass_bisect: + bss.DoSearchBadPass() bss.RemoveState() logger.GetLogger().LogOutput( 'Total execution time: %s' % bss.ElapsedTimeString()) diff --git a/binary_search_tool/common.py b/binary_search_tool/common.py index 1d950ced..2850801c 100644 --- a/binary_search_tool/common.py +++ b/binary_search_tool/common.py @@ -196,9 +196,12 @@ def _BuildArgsDict(args): dest='pass_bisect', default=None, help='Script to generate another script for pass level bisect, ' - 'which contains command line options to build bad item.' + 'which contains command line options to build bad item. ' + 'This will also turn on pass/transformation level bisection. ' + 'Needs support of `-opt-bisect-limit`(pass) and ' + '`-print-debug-counter`(transformation) from LLVM. ' 'For now it only supports one single bad item, so to use it, ' - 'prune must be set to false.') + 'prune must be set to False.') # No input (evals to False), # --noincremental (evals to True), # --noincremental=False, diff --git a/binary_search_tool/pass_mapping.py b/binary_search_tool/pass_mapping.py new file mode 100644 index 00000000..cb80910c --- /dev/null +++ b/binary_search_tool/pass_mapping.py @@ -0,0 +1,32 @@ +# Copyright 2018 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +"""Config file for pass level bisection + +Provides a mapping from pass info from -opt-bisect result to DebugCounter name. +""" +pass_name = { + # The list now contains all the passes in LLVM that support DebugCounter at + # transformation level. + # We will need to keep updating this map after more DebugCounter added to + # each pass in LLVM. + # For users who make local changes to passes, please add a map from pass + # description to newly introduced DebugCounter name for transformation + # level bisection purpose. + 'Hoist/decompose integer division and remainder': + 'div-rem-pairs-transform', + 'Early CSE': + 'early-cse', + 'Falkor HW Prefetch Fix Late Phase': + 'falkor-hwpf', + 'Combine redundant instructions': + 'instcombine-visit', + 'Machine Copy Propagation Pass': + 'machine-cp-fwd', + 'Global Value Numbering': + 'newgvn-phi', + 'PredicateInfo Printer': + 'predicateinfo-rename', + 'SI Insert Waitcnts': + 'si-insert-waitcnts-forceexp', +} diff --git a/binary_search_tool/test/binary_search_tool_tester.py b/binary_search_tool/test/binary_search_tool_tester.py index e733d9c4..923ea112 100755 --- a/binary_search_tool/test/binary_search_tool_tester.py +++ b/binary_search_tool/test/binary_search_tool_tester.py @@ -1,6 +1,8 @@ #!/usr/bin/env python2 -# Copyright 2012 Google Inc. All Rights Reserved. +# Copyright 2018 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. """Tests for bisecting tool.""" from __future__ import print_function @@ -283,7 +285,7 @@ class BisectingUtilsTest(unittest.TestCase): prune=True, file_args=True, iterations=1) - bss.DoSearch() + bss.DoSearchBadItems() self.assertFalse(bss.found_items) def test_no_prune(self): @@ -295,7 +297,25 @@ class BisectingUtilsTest(unittest.TestCase): test_setup_script='./test_setup.py', prune=False, file_args=True) - bss.DoSearch() + bss.DoSearchBadItems() + self.assertEquals(len(bss.found_items), 1) + + bad_objs = common.ReadObjectsFile() + found_obj = int(bss.found_items.pop()) + self.assertEquals(bad_objs[found_obj], 1) + + def test_pass_bisect(self): + bss = binary_search_state.MockBinarySearchState( + get_initial_items='./gen_init_list.py', + switch_to_good='./switch_to_good.py', + switch_to_bad='./switch_to_bad.py', + pass_bisect='./generate_cmd.py', + test_script='./is_good.py', + test_setup_script='./test_setup.py', + prune=False, + file_args=True) + # TODO: Need to design unit tests for pass level bisection + bss.DoSearchBadItems() self.assertEquals(len(bss.found_items), 1) bad_objs = common.ReadObjectsFile() |