| #!/usr/bin/python |
| |
| import optparse |
| import os |
| import pickle |
| import sys |
| import tempfile |
| |
| # Programtically adding utils python path to PYTHONPATH |
| if os.path.isabs(sys.argv[0]): |
| utils_pythonpath = os.path.abspath( |
| '{0}/..'.format(os.path.dirname(sys.argv[0]))) |
| else: |
| wdir = os.getcwd() |
| utils_pythonpath = os.path.abspath( |
| '{0}/{1}/..'.format(wdir, os.path.dirname(sys.argv[0]))) |
| sys.path.append(utils_pythonpath) |
| # Now we do import from utils |
| from utils import command_executer |
| from utils import logger |
| |
| import binary_search_perforce |
| |
| STATE_FILE = "%s.state" % sys.argv[0] |
| |
| |
| class BinarySearchState(object): |
| def __init__(self, get_initial_items, switch_to_good, switch_to_bad, |
| test_script, incremental, prune, iterations, prune_iterations, |
| verify_level, file_args): |
| self.get_initial_items = get_initial_items |
| self.switch_to_good = switch_to_good |
| self.switch_to_bad = switch_to_bad |
| self.test_script = test_script |
| self.incremental = incremental |
| self.prune = prune |
| self.iterations = iterations |
| self.prune_iterations = prune_iterations |
| self.verify_level = verify_level |
| self.file_args = file_args |
| |
| self.l = logger.GetLogger() |
| self.ce = command_executer.GetCommandExecuter() |
| |
| self.bs = None |
| self.PopulateItemsUsingCommand(self.get_initial_items) |
| self.currently_good_items = set([]) |
| self.currently_bad_items = set([]) |
| |
| def SwitchToGood(self, item_list): |
| if self.incremental: |
| self.l.LogOutput( |
| "Incremental set. Wanted to switch %s to good" % str(item_list)) |
| incremental_items = [ |
| item for item in item_list if item not in self.currently_good_items] |
| item_list = incremental_items |
| self.l.LogOutput( |
| "Incremental set. Actually switching %s to good" % str(item_list)) |
| |
| if not item_list: |
| return |
| |
| self.l.LogOutput("Switching %s to good" % str(item_list)) |
| self.RunSwitchScript(self.switch_to_good, item_list) |
| self.currently_good_items = self.currently_good_items.union(set(item_list)) |
| self.currently_bad_items.difference_update(set(item_list)) |
| |
| def SwitchToBad(self, item_list): |
| if self.incremental: |
| self.l.LogOutput( |
| "Incremental set. Wanted to switch %s to bad" % str(item_list)) |
| incremental_items = [ |
| item for item in item_list if item not in self.currently_bad_items] |
| item_list = incremental_items |
| self.l.LogOutput( |
| "Incremental set. Actually switching %s to bad" % str(item_list)) |
| |
| if not item_list: |
| return |
| |
| self.l.LogOutput("Switching %s to bad" % str(item_list)) |
| self.RunSwitchScript(self.switch_to_bad, item_list) |
| self.currently_bad_items = self.currently_bad_items.union(set(item_list)) |
| self.currently_good_items.difference_update(set(item_list)) |
| |
| def RunSwitchScript(self, switch_script, item_list): |
| if self.file_args: |
| temp_file = tempfile.mktemp() |
| f = open(temp_file, "wb") |
| f.write("\n".join(item_list)) |
| f.close() |
| command = "%s %s" % (switch_script, temp_file) |
| else: |
| command = "%s %s" % (switch_script, " ".join(item_list)) |
| ret = self.ce.RunCommand(command) |
| assert ret == 0, "Switch script %s returned %d" % (switch_script, ret) |
| |
| def TestScript(self): |
| command = self.test_script |
| return self.ce.RunCommand(command) |
| |
| def DoVerify(self): |
| for _ in range(int(self.verify_level)): |
| self.l.LogOutput("Resetting all items to good to verify.") |
| self.SwitchToGood(self.all_items) |
| status = self.TestScript() |
| assert status == 0, "When reset_to_good, status should be 0." |
| |
| self.l.LogOutput("Resetting all items to bad to verify.") |
| self.SwitchToBad(self.all_items) |
| status = self.TestScript() |
| assert status == 1, "When reset_to_bad, status should be 1." |
| |
| def DoSearch(self): |
| num_bad_items_history = [] |
| i = 0 |
| while True and len(self.all_items) > 1 and i < self.prune_iterations: |
| i += 1 |
| terminated = self.DoBinarySearch() |
| if not terminated: |
| break |
| if not self.prune: |
| self.l.LogOutput("Not continuning further, --prune is not set") |
| break |
| # Prune is set. |
| prune_index = self.bs.current |
| |
| if prune_index == len(self.all_items) - 1: |
| self.l.LogOutput("First bad item is the last item. Breaking.") |
| self.l.LogOutput("Only bad item is: %s" % self.all_items[-1]) |
| break |
| |
| num_bad_items = len(self.all_items) - prune_index |
| num_bad_items_history.append(num_bad_items) |
| |
| if (num_bad_items_history[-num_bad_items:] == |
| [num_bad_items for _ in range(num_bad_items)]): |
| self.l.LogOutput("num_bad_items_history: %s for past %d iterations. " |
| "Breaking." % |
| (str(num_bad_items_history), |
| num_bad_items)) |
| self.l.LogOutput( |
| "Bad items are: %s" % " ".join(self.all_items[prune_index:])) |
| break |
| |
| new_all_items = list(self.all_items) |
| # Move prune item to the end of the list. |
| new_all_items.append(new_all_items.pop(prune_index)) |
| |
| if prune_index: |
| new_all_items = new_all_items[prune_index - 1:] |
| |
| self.l.LogOutput("Old list: %s. New list: %s" % |
| (str(self.all_items), |
| str(new_all_items))) |
| |
| # FIXME: Do we need to Convert the currently good items to bad |
| self.PopulateItemsUsingList(new_all_items) |
| |
| def DoBinarySearch(self): |
| i = 0 |
| terminated = False |
| while i < self.iterations and not terminated: |
| i += 1 |
| [bad_items, good_items] = self.GetNextItems(self.incremental) |
| |
| # TODO: bad_items should come first. |
| self.SwitchToGood(good_items) |
| self.SwitchToBad(bad_items) |
| status = self.TestScript() |
| terminated = self.bs.SetStatus(status) |
| |
| if terminated: |
| self.l.LogOutput("Terminated!") |
| if not terminated: |
| self.l.LogOutput("Ran out of iterations searching...") |
| self.l.LogOutput(str(self)) |
| return terminated |
| |
| def PopulateItemsUsingCommand(self, command): |
| ce = command_executer.GetCommandExecuter() |
| [_, out, _] = ce.RunCommand(command, return_output=True) |
| all_items = out.split() |
| self.PopulateItemsUsingList(all_items) |
| |
| def PopulateItemsUsingList(self, all_items): |
| self.all_items = all_items |
| self.bs = binary_search_perforce.BinarySearcher() |
| self.bs.SetSortedList(self.all_items) |
| |
| def SaveState(self): |
| self.l = None |
| self.ce = None |
| # TODO Implement save/restore |
| ### return |
| f = open(STATE_FILE, "wb") |
| pickle.dump(self, f) |
| f.close() |
| |
| @classmethod |
| def LoadState(cls): |
| if not os.path.isfile(STATE_FILE): |
| return None |
| return pickle.load(file(STATE_FILE)) |
| |
| @classmethod |
| def GetInstance(cls, command): |
| # Disable state loading for now. |
| # obj = cls.LoadState() |
| obj = None |
| if not obj: |
| obj = cls() |
| return obj |
| return obj |
| |
| def GetNextItems(self, incremental=True): |
| border_item = self.bs.GetNext() |
| index = self.all_items.index(border_item) |
| |
| next_bad_items = self.all_items[:index+1] |
| next_good_items = self.all_items[index+1:] |
| |
| return [next_bad_items, next_good_items] |
| |
| def __str__(self): |
| ret = "" |
| ret += "all: %s\n" % str(self.all_items) |
| ret += "currently_good: %s\n" % str(self.currently_good_items) |
| ret += "currently_bad: %s\n" % str(self.currently_bad_items) |
| ret += str(self.bs) |
| return ret |
| |
| |
| def _CanonicalizeScript(script_name): |
| script_name = os.path.expanduser(script_name) |
| if not script_name.startswith("/"): |
| return os.path.join(".", script_name) |
| |
| |
| def Main(argv): |
| """The main function.""" |
| # Common initializations |
| |
| parser = optparse.OptionParser() |
| parser.add_option("-n", |
| "--iterations", |
| dest="iterations", |
| help="Number of iterations to try in the search.", |
| default="50") |
| parser.add_option("-i", |
| "--get_initial_items", |
| dest="get_initial_items", |
| help="Script to run to get the initial objects.") |
| parser.add_option("-g", |
| "--switch_to_good", |
| dest="switch_to_good", |
| help="Script to run to switch to good.") |
| parser.add_option("-b", |
| "--switch_to_bad", |
| dest="switch_to_bad", |
| help="Script to run to switch to bad.") |
| parser.add_option("-t", |
| "--test_script", |
| dest="test_script", |
| help=("Script to run to test the " |
| "output after packages are built.")) |
| parser.add_option("-p", |
| "--prune", |
| dest="prune", |
| action="store_true", |
| default=False, |
| help=("Script to run to test the output after " |
| "packages are built.")) |
| parser.add_option("-c", |
| "--noincremental", |
| dest="noincremental", |
| action="store_true", |
| default=False, |
| help="Do not propagate good/bad changes incrementally.") |
| parser.add_option("-f", |
| "--file_args", |
| dest="file_args", |
| action="store_true", |
| default=False, |
| help="Use a file to pass arguments to scripts.") |
| parser.add_option("-v", |
| "--verify_level", |
| dest="verify_level", |
| default="1", |
| help=("Check binary search assumptions N times " |
| "before starting.")) |
| parser.add_option("-N", |
| "--prune_iterations", |
| dest="prune_iterations", |
| help="Number of prune iterations to try in the search.", |
| default="100") |
| |
| logger.GetLogger().LogOutput(" ".join(argv)) |
| [options, _] = parser.parse_args(argv) |
| |
| if not (options.get_initial_items and options.switch_to_good and |
| options.switch_to_bad and options.test_script): |
| parser.print_help() |
| return 1 |
| |
| iterations = int(options.iterations) |
| switch_to_good = _CanonicalizeScript(options.switch_to_good) |
| switch_to_bad = _CanonicalizeScript(options.switch_to_bad) |
| test_script = _CanonicalizeScript(options.test_script) |
| get_initial_items = _CanonicalizeScript(options.get_initial_items) |
| prune = options.prune |
| prune_iterations = options.prune_iterations |
| verify_level = options.verify_level |
| file_args = options.file_args |
| |
| if options.noincremental: |
| incremental = False |
| else: |
| incremental = True |
| |
| try: |
| bss = BinarySearchState(get_initial_items, switch_to_good, switch_to_bad, |
| test_script, incremental, prune, iterations, |
| prune_iterations, verify_level, file_args) |
| bss.DoVerify() |
| bss.DoSearch() |
| |
| except (KeyboardInterrupt, SystemExit): |
| print "C-c pressed" |
| bss.SaveState() |
| return 0 |
| |
| if __name__ == "__main__": |
| sys.exit(Main(sys.argv)) |