blob: c103cb3cdec18484a3648316491d267e210f0dae [file] [log] [blame]
#!/usr/bin/python2
"""The binary search wrapper."""
from __future__ import print_function
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):
"""The binary search state class."""
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.all_items = 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()
# 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.RunCommandWOutput(command)
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))
def GetNextItems(self):
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))