diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:01:57 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:01:57 +0000 |
commit | 64769040d196139ecb1a70883b923bb3d86327f8 (patch) | |
tree | 77dc031614745bb406dbd90cea9a082a1b5cdd54 /binary_search_tool | |
parent | 94cf0e3aac8620e8d9a3eb6356f6229293ac5a59 (diff) | |
parent | 40214b48188358a80b7478bfff21d4814dd9177c (diff) | |
download | toolchain-utils-android14-mainline-extservices-release.tar.gz |
Snap for 10453563 from 40214b48188358a80b7478bfff21d4814dd9177c to mainline-extservices-releaseaml_ext_341716000aml_ext_341620040aml_ext_341518010aml_ext_341414010aml_ext_341317010aml_ext_341131030aml_ext_341027030android14-mainline-extservices-release
Change-Id: Ic68e29ad0fec09d765a40aed7465d9f33d38ef8d
Diffstat (limited to 'binary_search_tool')
59 files changed, 3737 insertions, 3377 deletions
diff --git a/binary_search_tool/MAINTENANCE b/binary_search_tool/MAINTENANCE index 8f96ff10..90ac582d 100644 --- a/binary_search_tool/MAINTENANCE +++ b/binary_search_tool/MAINTENANCE @@ -1,4 +1,4 @@ -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/binary_search_tool/__init__.py b/binary_search_tool/__init__.py index 76500def..6e3ade4a 100644 --- a/binary_search_tool/__init__.py +++ b/binary_search_tool/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/binary_search_tool/android/boot_test.sh b/binary_search_tool/android/boot_test.sh index dc871601..4c0c77e2 100755 --- a/binary_search_tool/android/boot_test.sh +++ b/binary_search_tool/android/boot_test.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script pings the android device to determine if it successfully booted. # diff --git a/binary_search_tool/android/cleanup.sh b/binary_search_tool/android/cleanup.sh index 759b3ed4..480b830b 100755 --- a/binary_search_tool/android/cleanup.sh +++ b/binary_search_tool/android/cleanup.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script is part of the Android binary search triage process. # It should be the last script called by the user, after the user has diff --git a/binary_search_tool/android/generate_cmd.sh b/binary_search_tool/android/generate_cmd.sh index 78a39b12..6d0e5692 100755 --- a/binary_search_tool/android/generate_cmd.sh +++ b/binary_search_tool/android/generate_cmd.sh @@ -1,6 +1,6 @@ #!/bin/bash -eu -# Copyright 2018 The Chromium OS Authors. All rights reserved. +# Copyright 2018 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/binary_search_tool/android/get_initial_items.sh b/binary_search_tool/android/get_initial_items.sh index 2a1eda3a..1ed30425 100755 --- a/binary_search_tool/android/get_initial_items.sh +++ b/binary_search_tool/android/get_initial_items.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script is intended to be used by binary_search_state.py, as # part of the binary search triage on the Android source tree. This script @@ -11,4 +11,3 @@ source android/common.sh cat ${BISECT_GOOD_BUILD}/_LIST - diff --git a/binary_search_tool/android/interactive_test.sh b/binary_search_tool/android/interactive_test.sh index e506b236..0a8a4b8c 100755 --- a/binary_search_tool/android/interactive_test.sh +++ b/binary_search_tool/android/interactive_test.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script pings the android device to determine if it successfully booted. # It then asks the user if the image is good or not, allowing the user to diff --git a/binary_search_tool/android/setup.sh b/binary_search_tool/android/setup.sh index 7f8ba0e9..06918226 100755 --- a/binary_search_tool/android/setup.sh +++ b/binary_search_tool/android/setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script is part of the Android binary search triage process. # It should be the first script called by the user, after the user has set up diff --git a/binary_search_tool/android/switch_to_bad.sh b/binary_search_tool/android/switch_to_bad.sh index d44f9f13..2100ed43 100755 --- a/binary_search_tool/android/switch_to_bad.sh +++ b/binary_search_tool/android/switch_to_bad.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script is intended to be used by binary_search_state.py, as # part of the binary search triage on the Android source tree. This script diff --git a/binary_search_tool/android/switch_to_good.sh b/binary_search_tool/android/switch_to_good.sh index 557553c3..a5be3c3e 100755 --- a/binary_search_tool/android/switch_to_good.sh +++ b/binary_search_tool/android/switch_to_good.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script is intended to be used by binary_search_state.py, as # part of the binary search triage on the Android source tree. This script diff --git a/binary_search_tool/android/test_setup.sh b/binary_search_tool/android/test_setup.sh index 26f8ec22..be4a0b76 100755 --- a/binary_search_tool/android/test_setup.sh +++ b/binary_search_tool/android/test_setup.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This is the test setup script for generating an Android image based off the # current working build tree. make is called to relink the object files and diff --git a/binary_search_tool/binary_search_perforce.py b/binary_search_tool/binary_search_perforce.py index f2a3c8d5..01756b8e 100755 --- a/binary_search_tool/binary_search_perforce.py +++ b/binary_search_tool/binary_search_perforce.py @@ -1,15 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # 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 division -from __future__ import print_function -import math import argparse +import math import os import re import sys @@ -18,496 +16,562 @@ import tempfile from cros_utils import command_executer from cros_utils import logger + verbose = True def _GetP4ClientSpec(client_name, p4_paths): - p4_string = '' - for p4_path in p4_paths: - if ' ' not in p4_path: - p4_string += ' -a %s' % p4_path - else: - p4_string += ' -a "' + (' //' + client_name + '/').join(p4_path) + '"' - - return p4_string - - -def GetP4Command(client_name, p4_port, p4_paths, checkoutdir, p4_snapshot=''): - command = '' - - if p4_snapshot: - command += 'mkdir -p ' + checkoutdir + p4_string = "" for p4_path in p4_paths: - real_path = p4_path[1] - if real_path.endswith('...'): - real_path = real_path.replace('/...', '') - command += ( - '; mkdir -p ' + checkoutdir + '/' + os.path.dirname(real_path)) - command += ('&& rsync -lr ' + p4_snapshot + '/' + real_path + ' ' + - checkoutdir + '/' + os.path.dirname(real_path)) + if " " not in p4_path: + p4_string += " -a %s" % p4_path + else: + p4_string += ( + ' -a "' + (" //" + client_name + "/").join(p4_path) + '"' + ) + + return p4_string + + +def GetP4Command(client_name, p4_port, p4_paths, checkoutdir, p4_snapshot=""): + command = "" + + if p4_snapshot: + command += "mkdir -p " + checkoutdir + for p4_path in p4_paths: + real_path = p4_path[1] + if real_path.endswith("..."): + real_path = real_path.replace("/...", "") + command += ( + "; mkdir -p " + + checkoutdir + + "/" + + os.path.dirname(real_path) + ) + command += ( + "&& rsync -lr " + + p4_snapshot + + "/" + + real_path + + " " + + checkoutdir + + "/" + + os.path.dirname(real_path) + ) + return command + + command += " export P4CONFIG=.p4config" + command += " && mkdir -p " + checkoutdir + command += " && cd " + checkoutdir + command += " && cp ${HOME}/.p4config ." + command += " && chmod u+w .p4config" + command += ' && echo "P4PORT=' + p4_port + '" >> .p4config' + command += ' && echo "P4CLIENT=' + client_name + '" >> .p4config' + command += " && g4 client " + _GetP4ClientSpec(client_name, p4_paths) + command += " && g4 sync " + command += " && cd -" return command - command += ' export P4CONFIG=.p4config' - command += ' && mkdir -p ' + checkoutdir - command += ' && cd ' + checkoutdir - command += ' && cp ${HOME}/.p4config .' - command += ' && chmod u+w .p4config' - command += ' && echo "P4PORT=' + p4_port + '" >> .p4config' - command += ' && echo "P4CLIENT=' + client_name + '" >> .p4config' - command += (' && g4 client ' + _GetP4ClientSpec(client_name, p4_paths)) - command += ' && g4 sync ' - command += ' && cd -' - return command - class BinarySearchPoint(object): - """Class of binary search point.""" + """Class of binary search point.""" - def __init__(self, revision, status, tag=None): - self.revision = revision - self.status = status - self.tag = tag + def __init__(self, revision, status, tag=None): + self.revision = revision + self.status = status + 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. - - Returns: - True if we find the bad pass/transformation, or cannot find bad one after - decreasing to the first pass/transformation. Otherwise False. - """ - assert status in (0, 1, 125), status - - 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 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. + + Returns: + True if we find the bad pass/transformation, or cannot find bad one after + decreasing to the first pass/transformation. Otherwise False. + """ + assert status in (0, 1, 125), status + + 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.""" - - def __init__(self, logger_to_set=None): - self.sorted_list = [] - self.index_log = [] - self.status_log = [] - self.skipped_indices = [] - self.current = 0 - self.points = {} - self.lo = 0 - self.hi = 0 - if logger_to_set is not None: - self.logger = logger_to_set - else: - self.logger = logger.GetLogger() - - def SetSortedList(self, sorted_list): - assert sorted_list - self.sorted_list = sorted_list - self.index_log = [] - self.hi = len(sorted_list) - 1 - self.lo = 0 - self.points = {} - for i in range(len(self.sorted_list)): - bsp = BinarySearchPoint(self.sorted_list[i], -1, 'Not yet done.') - self.points[i] = bsp - - def SetStatus(self, status, tag=None): - message = ('Revision: %s index: %d returned: %d' % - (self.sorted_list[self.current], self.current, status)) - self.logger.LogOutput(message, print_to_console=verbose) - assert status in (0, 1, 125), status - self.index_log.append(self.current) - self.status_log.append(status) - bsp = BinarySearchPoint(self.sorted_list[self.current], status, tag) - self.points[self.current] = bsp - - if status == 125: - self.skipped_indices.append(self.current) - - if status in (0, 1): - if status == 0: - self.lo = self.current + 1 - elif status == 1: - self.hi = self.current - self.logger.LogOutput('lo: %d hi: %d\n' % (self.lo, self.hi)) - self.current = (self.lo + self.hi) // 2 - - if self.lo == self.hi: - message = ('Search complete. First bad version: %s' - ' at index: %d' % (self.sorted_list[self.current], self.lo)) - self.logger.LogOutput(message) - return True - - for index in range(self.lo, self.hi): - if index not in self.skipped_indices: - return False - self.logger.LogOutput( - 'All skipped indices between: %d and %d\n' % (self.lo, self.hi), - print_to_console=verbose) - return True - - # Does a better job with chromeos flakiness. - def GetNextFlakyBinary(self): - t = (self.lo, self.current, self.hi) - q = [t] - while q: - element = q.pop(0) - if element[1] in self.skipped_indices: - # Go top - to_add = (element[0], (element[0] + element[1]) // 2, element[1]) - q.append(to_add) - # Go bottom - to_add = (element[1], (element[1] + element[2]) // 2, element[2]) - q.append(to_add) - else: - self.current = element[1] - return - assert q, 'Queue should never be 0-size!' - - def GetNextFlakyLinear(self): - current_hi = self.current - current_lo = self.current - while True: - if current_hi < self.hi and current_hi not in self.skipped_indices: - self.current = current_hi - break - if current_lo >= self.lo and current_lo not in self.skipped_indices: - self.current = current_lo - break - if current_lo < self.lo and current_hi >= self.hi: - break - - current_hi += 1 - current_lo -= 1 - - def GetNext(self): - self.current = (self.hi + self.lo) // 2 - # Try going forward if current is skipped. - if self.current in self.skipped_indices: - 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))) - 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])) - self.logger.LogOutput(message, print_to_console=verbose) - self.logger.LogOutput(str(self), print_to_console=verbose) - return self.sorted_list[self.current] - - def SetLoRevision(self, lo_revision): - self.lo = self.sorted_list.index(lo_revision) - - def SetHiRevision(self, hi_revision): - self.hi = self.sorted_list.index(hi_revision) - - 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)) - - return to_return - - def __str__(self): - to_return = '' - to_return += 'Current: %d\n' % self.current - to_return += str(self.index_log) + '\n' - revision_log = [] - for index in self.index_log: - revision_log.append(self.sorted_list[index]) - to_return += str(revision_log) + '\n' - to_return += str(self.status_log) + '\n' - to_return += 'Skipped indices:\n' - to_return += str(self.skipped_indices) + '\n' - to_return += self.GetAllPoints() - return to_return + """Class of binary searcher.""" + + def __init__(self, logger_to_set=None): + self.sorted_list = [] + self.index_log = [] + self.status_log = [] + self.skipped_indices = [] + self.current = 0 + self.points = {} + self.lo = 0 + self.hi = 0 + if logger_to_set is not None: + self.logger = logger_to_set + else: + self.logger = logger.GetLogger() + + def SetSortedList(self, sorted_list): + assert sorted_list + self.sorted_list = sorted_list + self.index_log = [] + self.hi = len(sorted_list) - 1 + self.lo = 0 + self.points = {} + for i in range(len(self.sorted_list)): + bsp = BinarySearchPoint(self.sorted_list[i], -1, "Not yet done.") + self.points[i] = bsp + + def SetStatus(self, status, tag=None): + message = "Revision: %s index: %d returned: %d" % ( + self.sorted_list[self.current], + self.current, + status, + ) + self.logger.LogOutput(message, print_to_console=verbose) + assert status in (0, 1, 125), status + self.index_log.append(self.current) + self.status_log.append(status) + bsp = BinarySearchPoint(self.sorted_list[self.current], status, tag) + self.points[self.current] = bsp + + if status == 125: + self.skipped_indices.append(self.current) + + if status in (0, 1): + if status == 0: + self.lo = self.current + 1 + elif status == 1: + self.hi = self.current + self.logger.LogOutput("lo: %d hi: %d\n" % (self.lo, self.hi)) + self.current = (self.lo + self.hi) // 2 + + if self.lo == self.hi: + message = ( + "Search complete. First bad version: %s" + " at index: %d" + % ( + self.sorted_list[self.current], + self.lo, + ) + ) + self.logger.LogOutput(message) + return True + + for index in range(self.lo, self.hi): + if index not in self.skipped_indices: + return False + self.logger.LogOutput( + "All skipped indices between: %d and %d\n" % (self.lo, self.hi), + print_to_console=verbose, + ) + return True + + # Does a better job with chromeos flakiness. + def GetNextFlakyBinary(self): + t = (self.lo, self.current, self.hi) + q = [t] + while q: + element = q.pop(0) + if element[1] in self.skipped_indices: + # Go top + to_add = ( + element[0], + (element[0] + element[1]) // 2, + element[1], + ) + q.append(to_add) + # Go bottom + to_add = ( + element[1], + (element[1] + element[2]) // 2, + element[2], + ) + q.append(to_add) + else: + self.current = element[1] + return + assert q, "Queue should never be 0-size!" + + def GetNextFlakyLinear(self): + current_hi = self.current + current_lo = self.current + while True: + if current_hi < self.hi and current_hi not in self.skipped_indices: + self.current = current_hi + break + if current_lo >= self.lo and current_lo not in self.skipped_indices: + self.current = current_lo + break + if current_lo < self.lo and current_hi >= self.hi: + break + + current_hi += 1 + current_lo -= 1 + + def GetNext(self): + self.current = (self.hi + self.lo) // 2 + # Try going forward if current is skipped. + if self.current in self.skipped_indices: + 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), + ) + 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], + ) + self.logger.LogOutput(message, print_to_console=verbose) + self.logger.LogOutput(str(self), print_to_console=verbose) + return self.sorted_list[self.current] + + def SetLoRevision(self, lo_revision): + self.lo = self.sorted_list.index(lo_revision) + + def SetHiRevision(self, hi_revision): + self.hi = self.sorted_list.index(hi_revision) + + 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, + ) + + return to_return + + def __str__(self): + to_return = "" + to_return += "Current: %d\n" % self.current + to_return += str(self.index_log) + "\n" + revision_log = [] + for index in self.index_log: + revision_log.append(self.sorted_list[index]) + to_return += str(revision_log) + "\n" + to_return += str(self.status_log) + "\n" + to_return += "Skipped indices:\n" + to_return += str(self.skipped_indices) + "\n" + to_return += self.GetAllPoints() + return to_return class RevisionInfo(object): - """Class of reversion info.""" + """Class of reversion info.""" - def __init__(self, date, client, description): - self.date = date - self.client = client - self.description = description - self.status = -1 + def __init__(self, date, client, description): + self.date = date + self.client = client + self.description = description + self.status = -1 class VCSBinarySearcher(object): - """Class of VCS binary searcher.""" + """Class of VCS binary searcher.""" - def __init__(self): - self.bs = BinarySearcher() - self.rim = {} - self.current_ce = None - self.checkout_dir = None - self.current_revision = None + def __init__(self): + self.bs = BinarySearcher() + self.rim = {} + self.current_ce = None + self.checkout_dir = None + self.current_revision = None - def Initialize(self): - pass + def Initialize(self): + pass - def GetNextRevision(self): - pass + def GetNextRevision(self): + pass - def CheckoutRevision(self, current_revision): - pass + def CheckoutRevision(self, current_revision): + pass - def SetStatus(self, status): - pass + def SetStatus(self, status): + pass - def Cleanup(self): - pass + def Cleanup(self): + pass - def SetGoodRevision(self, revision): - if revision is None: - return - assert revision in self.bs.sorted_list - self.bs.SetLoRevision(revision) + def SetGoodRevision(self, revision): + if revision is None: + return + assert revision in self.bs.sorted_list + self.bs.SetLoRevision(revision) - def SetBadRevision(self, revision): - if revision is None: - return - assert revision in self.bs.sorted_list - self.bs.SetHiRevision(revision) + def SetBadRevision(self, revision): + if revision is None: + return + assert revision in self.bs.sorted_list + self.bs.SetHiRevision(revision) class P4BinarySearcher(VCSBinarySearcher): - """Class of P4 binary searcher.""" - - def __init__(self, p4_port, p4_paths, test_command): - VCSBinarySearcher.__init__(self) - self.p4_port = p4_port - self.p4_paths = p4_paths - self.test_command = test_command - self.checkout_dir = tempfile.mkdtemp() - self.ce = command_executer.GetCommandExecuter() - self.client_name = 'binary-searcher-$HOSTNAME-$USER' - self.job_log_root = '/home/asharif/www/coreboot_triage/' - self.changes = None - - def Initialize(self): - self.Cleanup() - command = GetP4Command(self.client_name, self.p4_port, self.p4_paths, 1, - self.checkout_dir) - self.ce.RunCommand(command) - 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) - for change_info in change_infos: - ri = RevisionInfo(change_info[1], change_info[2], change_info[3]) - self.rim[change_info[0]] = ri - # g4 gives changes in reverse chronological order. - self.changes.reverse() - self.bs.SetSortedList(self.changes) - - def SetStatus(self, status): - self.rim[self.current_revision].status = status - return self.bs.SetStatus(status) - - def GetNextRevision(self): - next_revision = self.bs.GetNext() - self.current_revision = next_revision - return next_revision - - def CleanupCLs(self): - if not os.path.isfile(self.checkout_dir + '/.p4config'): - command = 'cd %s' % self.checkout_dir - command += ' && cp ${HOME}/.p4config .' - command += ' && echo "P4PORT=' + self.p4_port + '" >> .p4config' - command += ' && echo "P4CLIENT=' + self.client_name + '" >> .p4config' - self.ce.RunCommand(command) - command = 'cd %s' % self.checkout_dir - command += '; g4 changes -c %s' % self.client_name - _, out, _ = self.ce.RunCommandWOutput(command) - changes = re.findall(r'Change (\d+)', out) - if changes: - command = 'cd %s' % self.checkout_dir - for change in changes: - command += '; g4 revert -c %s' % change - self.ce.RunCommand(command) - - def CleanupClient(self): - command = 'cd %s' % self.checkout_dir - command += '; g4 revert ...' - command += '; g4 client -d %s' % self.client_name - self.ce.RunCommand(command) - - def Cleanup(self): - self.CleanupCLs() - self.CleanupClient() - - def __str__(self): - to_return = '' - for change in self.changes: - ri = self.rim[change] - if ri.status == -1: - to_return = '%s\t%d\n' % (change, ri.status) - 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')) - return to_return + """Class of P4 binary searcher.""" + + def __init__(self, p4_port, p4_paths, test_command): + VCSBinarySearcher.__init__(self) + self.p4_port = p4_port + self.p4_paths = p4_paths + self.test_command = test_command + self.checkout_dir = tempfile.mkdtemp() + self.ce = command_executer.GetCommandExecuter() + self.client_name = "binary-searcher-$HOSTNAME-$USER" + self.job_log_root = "/home/asharif/www/coreboot_triage/" + self.changes = None + + def Initialize(self): + self.Cleanup() + command = GetP4Command( + self.client_name, self.p4_port, self.p4_paths, 1, self.checkout_dir + ) + self.ce.RunCommand(command) + 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 + ) + for change_info in change_infos: + ri = RevisionInfo(change_info[1], change_info[2], change_info[3]) + self.rim[change_info[0]] = ri + # g4 gives changes in reverse chronological order. + self.changes.reverse() + self.bs.SetSortedList(self.changes) + + def SetStatus(self, status): + self.rim[self.current_revision].status = status + return self.bs.SetStatus(status) + + def GetNextRevision(self): + next_revision = self.bs.GetNext() + self.current_revision = next_revision + return next_revision + + def CleanupCLs(self): + if not os.path.isfile(self.checkout_dir + "/.p4config"): + command = "cd %s" % self.checkout_dir + command += " && cp ${HOME}/.p4config ." + command += ' && echo "P4PORT=' + self.p4_port + '" >> .p4config' + command += ( + ' && echo "P4CLIENT=' + self.client_name + '" >> .p4config' + ) + self.ce.RunCommand(command) + command = "cd %s" % self.checkout_dir + command += "; g4 changes -c %s" % self.client_name + _, out, _ = self.ce.RunCommandWOutput(command) + changes = re.findall(r"Change (\d+)", out) + if changes: + command = "cd %s" % self.checkout_dir + for change in changes: + command += "; g4 revert -c %s" % change + self.ce.RunCommand(command) + + def CleanupClient(self): + command = "cd %s" % self.checkout_dir + command += "; g4 revert ..." + command += "; g4 client -d %s" % self.client_name + self.ce.RunCommand(command) + + def Cleanup(self): + self.CleanupCLs() + self.CleanupClient() + + def __str__(self): + to_return = "" + for change in self.changes: + ri = self.rim[change] + if ri.status == -1: + to_return = "%s\t%d\n" % (change, ri.status) + 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", + ) + return to_return class P4GCCBinarySearcher(P4BinarySearcher): - """Class of P4 gcc binary searcher.""" - - # TODO: eventually get these patches from g4 instead of creating them manually - def HandleBrokenCLs(self, current_revision): - cr = int(current_revision) - problematic_ranges = [] - problematic_ranges.append([44528, 44539]) - problematic_ranges.append([44528, 44760]) - problematic_ranges.append([44335, 44882]) - command = 'pwd' - for pr in problematic_ranges: - if cr in range(pr[0], pr[1]): - patch_file = '/home/asharif/triage_tool/%d-%d.patch' % (pr[0], pr[1]) - with open(patch_file, encoding='utf-8') as f: - patch = f.read() - files = re.findall('--- (//.*)', patch) - command += '; cd %s' % self.checkout_dir - for f in files: - command += '; g4 open %s' % f - command += '; patch -p2 < %s' % patch_file - self.current_ce.RunCommand(command) - - def CheckoutRevision(self, current_revision): - job_logger = logger.Logger( - self.job_log_root, current_revision, True, subdir='') - self.current_ce = command_executer.GetCommandExecuter(job_logger) - - 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)) - self.current_ce.RunCommand(command) - - self.HandleBrokenCLs(current_revision) + """Class of P4 gcc binary searcher.""" + + # TODO: eventually get these patches from g4 instead of creating them manually + def HandleBrokenCLs(self, current_revision): + cr = int(current_revision) + problematic_ranges = [] + problematic_ranges.append([44528, 44539]) + problematic_ranges.append([44528, 44760]) + problematic_ranges.append([44335, 44882]) + command = "pwd" + for pr in problematic_ranges: + if cr in range(pr[0], pr[1]): + patch_file = "/home/asharif/triage_tool/%d-%d.patch" % ( + pr[0], + pr[1], + ) + with open(patch_file, encoding="utf-8") as f: + patch = f.read() + files = re.findall("--- (//.*)", patch) + command += "; cd %s" % self.checkout_dir + for f in files: + command += "; g4 open %s" % f + command += "; patch -p2 < %s" % patch_file + self.current_ce.RunCommand(command) + + def CheckoutRevision(self, current_revision): + job_logger = logger.Logger( + self.job_log_root, current_revision, True, subdir="" + ) + self.current_ce = command_executer.GetCommandExecuter(job_logger) + + 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) + ) + self.current_ce.RunCommand(command) + + self.HandleBrokenCLs(current_revision) def Main(argv): - """The main function.""" - # Common initializations - ### command_executer.InitCommandExecuter(True) - ce = command_executer.GetCommandExecuter() - - parser = argparse.ArgumentParser() - parser.add_argument( - '-n', - '--num_tries', - dest='num_tries', - default='100', - help='Number of tries.') - parser.add_argument( - '-g', - '--good_revision', - dest='good_revision', - help='Last known good revision.') - parser.add_argument( - '-b', - '--bad_revision', - dest='bad_revision', - help='Last known bad revision.') - parser.add_argument( - '-s', '--script', dest='script', help='Script to run for every version.') - options = parser.parse_args(argv) - # First get all revisions - p4_paths = [ - '//depot2/gcctools/google_vendor_src_branch/gcc/gcc-4.4.3/...', - '//depot2/gcctools/google_vendor_src_branch/binutils/' - 'binutils-2.20.1-mobile/...', - '//depot2/gcctools/google_vendor_src_branch/' - 'binutils/binutils-20100303/...' - ] - p4gccbs = P4GCCBinarySearcher('perforce2:2666', p4_paths, '') - - # Main loop: - terminated = False - num_tries = int(options.num_tries) - script = os.path.expanduser(options.script) - - try: - p4gccbs.Initialize() - p4gccbs.SetGoodRevision(options.good_revision) - p4gccbs.SetBadRevision(options.bad_revision) - while not terminated and num_tries > 0: - current_revision = p4gccbs.GetNextRevision() - - # Now run command to get the status - 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)) - logger.GetLogger().LogOutput(message, print_to_console=verbose) - terminated = p4gccbs.SetStatus(status) - num_tries -= 1 - logger.GetLogger().LogOutput(str(p4gccbs), print_to_console=verbose) - - if not terminated: - logger.GetLogger().LogOutput( - 'Tries: %d expired.' % num_tries, print_to_console=verbose) - logger.GetLogger().LogOutput(str(p4gccbs.bs), print_to_console=verbose) - except (KeyboardInterrupt, SystemExit): - logger.GetLogger().LogOutput('Cleaning up...') - finally: - logger.GetLogger().LogOutput(str(p4gccbs.bs), print_to_console=verbose) - p4gccbs.Cleanup() - - -if __name__ == '__main__': - Main(sys.argv[1:]) + """The main function.""" + # Common initializations + ### command_executer.InitCommandExecuter(True) + ce = command_executer.GetCommandExecuter() + + parser = argparse.ArgumentParser() + parser.add_argument( + "-n", + "--num_tries", + dest="num_tries", + default="100", + help="Number of tries.", + ) + parser.add_argument( + "-g", + "--good_revision", + dest="good_revision", + help="Last known good revision.", + ) + parser.add_argument( + "-b", + "--bad_revision", + dest="bad_revision", + help="Last known bad revision.", + ) + parser.add_argument( + "-s", "--script", dest="script", help="Script to run for every version." + ) + options = parser.parse_args(argv) + # First get all revisions + p4_paths = [ + "//depot2/gcctools/google_vendor_src_branch/gcc/gcc-4.4.3/...", + "//depot2/gcctools/google_vendor_src_branch/binutils/" + "binutils-2.20.1-mobile/...", + "//depot2/gcctools/google_vendor_src_branch/" + "binutils/binutils-20100303/...", + ] + p4gccbs = P4GCCBinarySearcher("perforce2:2666", p4_paths, "") + + # Main loop: + terminated = False + num_tries = int(options.num_tries) + script = os.path.expanduser(options.script) + + try: + p4gccbs.Initialize() + p4gccbs.SetGoodRevision(options.good_revision) + p4gccbs.SetBadRevision(options.bad_revision) + while not terminated and num_tries > 0: + current_revision = p4gccbs.GetNextRevision() + + # Now run command to get the status + 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, + ) + logger.GetLogger().LogOutput(message, print_to_console=verbose) + terminated = p4gccbs.SetStatus(status) + num_tries -= 1 + logger.GetLogger().LogOutput(str(p4gccbs), print_to_console=verbose) + + if not terminated: + logger.GetLogger().LogOutput( + "Tries: %d expired." % num_tries, print_to_console=verbose + ) + logger.GetLogger().LogOutput(str(p4gccbs.bs), print_to_console=verbose) + except (KeyboardInterrupt, SystemExit): + logger.GetLogger().LogOutput("Cleaning up...") + finally: + logger.GetLogger().LogOutput(str(p4gccbs.bs), print_to_console=verbose) + p4gccbs.Cleanup() + + +if __name__ == "__main__": + Main(sys.argv[1:]) diff --git a/binary_search_tool/binary_search_state.py b/binary_search_tool/binary_search_state.py index 1ddd65ce..1b423b5c 100755 --- a/binary_search_tool/binary_search_state.py +++ b/binary_search_tool/binary_search_state.py @@ -1,13 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # 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 division -from __future__ import print_function import argparse import contextlib @@ -30,871 +28,993 @@ from binary_search_tool import pass_mapping from cros_utils import command_executer from cros_utils import logger -GOOD_SET_VAR = 'BISECT_GOOD_SET' -BAD_SET_VAR = 'BISECT_BAD_SET' -STATE_FILE = '%s.state' % sys.argv[0] +GOOD_SET_VAR = "BISECT_GOOD_SET" +BAD_SET_VAR = "BISECT_BAD_SET" + +STATE_FILE = "%s.state" % sys.argv[0] HIDDEN_STATE_FILE = os.path.join( - os.path.dirname(STATE_FILE), '.%s' % os.path.basename(STATE_FILE)) + os.path.dirname(STATE_FILE), ".%s" % os.path.basename(STATE_FILE) +) @contextlib.contextmanager def SetFile(env_var, items): - """Generate set files that can be used by switch/test scripts. - - Generate temporary set file (good/bad) holding contents of good/bad items for - the current binary search iteration. Store the name of each file as an - environment variable so all child processes can access it. - - This function is a contextmanager, meaning it's meant to be used with the - "with" statement in Python. This is so cleanup and setup happens automatically - and cleanly. Execution of the outer "with" statement happens at the "yield" - statement. + """Generate set files that can be used by switch/test scripts. - Args: - env_var: What environment variable to store the file name in. - items: What items are in this set. - """ - with tempfile.NamedTemporaryFile('w', encoding='utf-8') as f: - os.environ[env_var] = f.name - f.write('\n'.join(items)) - f.flush() - yield + Generate temporary set file (good/bad) holding contents of good/bad items for + the current binary search iteration. Store the name of each file as an + environment variable so all child processes can access it. - -class BinarySearchState(object): - """The binary search state class.""" - - def __init__(self, get_initial_items, switch_to_good, switch_to_bad, - test_setup_script, test_script, incremental, prune, pass_bisect, - ir_diff, iterations, prune_iterations, verify, file_args, - verbose): - """BinarySearchState constructor, see Run for full args documentation.""" - self.get_initial_items = get_initial_items - self.switch_to_good = switch_to_good - self.switch_to_bad = switch_to_bad - self.test_setup_script = test_setup_script - self.test_script = test_script - self.incremental = incremental - self.prune = prune - self.pass_bisect = pass_bisect - self.ir_diff = ir_diff - self.iterations = iterations - self.prune_iterations = prune_iterations - self.verify = verify - self.file_args = file_args - self.verbose = verbose - - self.l = logger.GetLogger() - self.ce = command_executer.GetCommandExecuter() - - self.resumed = False - self.prune_cycles = 0 - 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() - self.found_items = set() - self.known_good = set() - - self.start_time = time.time() - - def SwitchToGood(self, item_list): - """Switch given items to "good" set.""" - if self.incremental: - self.l.LogOutput( - 'Incremental set. Wanted to switch %s to good' % str(item_list), - print_to_console=self.verbose) - 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), - print_to_console=self.verbose) - - if not item_list: - return - - self.l.LogOutput( - 'Switching %s to good' % str(item_list), print_to_console=self.verbose) - 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): - """Switch given items to "bad" set.""" - if self.incremental: - self.l.LogOutput( - 'Incremental set. Wanted to switch %s to bad' % str(item_list), - print_to_console=self.verbose) - 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), - print_to_console=self.verbose) - - if not item_list: - return - - self.l.LogOutput( - 'Switching %s to bad' % str(item_list), print_to_console=self.verbose) - 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): - """Pass given items to switch script. + This function is a contextmanager, meaning it's meant to be used with the + "with" statement in Python. This is so cleanup and setup happens automatically + and cleanly. Execution of the outer "with" statement happens at the "yield" + statement. Args: - switch_script: path to switch script - item_list: list of all items to be switched + env_var: What environment variable to store the file name in. + items: What items are in this set. """ - if self.file_args: - with tempfile.NamedTemporaryFile('w', encoding='utf-8') as f: - f.write('\n'.join(item_list)) + with tempfile.NamedTemporaryFile("w", encoding="utf-8") as f: + os.environ[env_var] = f.name + f.write("\n".join(items)) f.flush() - command = '%s %s' % (switch_script, f.name) - ret, _, _ = self.ce.RunCommandWExceptionCleanup( - command, print_to_console=self.verbose) - else: - command = '%s %s' % (switch_script, ' '.join(item_list)) - try: - ret, _, _ = self.ce.RunCommandWExceptionCleanup( - command, print_to_console=self.verbose) - except OSError as e: - if e.errno == errno.E2BIG: - raise RuntimeError('Too many arguments for switch script! Use ' - '--file_args') - assert ret == 0, 'Switch script %s returned %d' % (switch_script, ret) - - def TestScript(self): - """Run test script and return exit code from script.""" - command = self.test_script - ret, _, _ = self.ce.RunCommandWExceptionCleanup(command) - return ret - - def TestSetupScript(self): - """Run test setup script and return exit code from script.""" - if not self.test_setup_script: - return 0 - - command = self.test_setup_script - 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. - - Verify that a "good" set of items produces a "good" result and that a "bad" - set of items produces a "bad" result. To be run directly before running - DoSearch. If verify is False this step is skipped. - """ - if not self.verify: - return - - self.l.LogOutput('VERIFICATION') - self.l.LogOutput('Beginning tests to verify good/bad sets\n') - - self._OutputProgress('Verifying items from GOOD set\n') - with SetFile(GOOD_SET_VAR, self.all_items), SetFile(BAD_SET_VAR, []): - self.l.LogOutput('Resetting all items to good to verify.') - self.SwitchToGood(self.all_items) - status = self.TestSetupScript() - assert status == 0, 'When reset_to_good, test setup should succeed.' - status = self.TestScript() - assert status == 0, 'When reset_to_good, status should be 0.' - - self._OutputProgress('Verifying items from BAD set\n') - with SetFile(GOOD_SET_VAR, []), SetFile(BAD_SET_VAR, self.all_items): - self.l.LogOutput('Resetting all items to bad to verify.') - self.SwitchToBad(self.all_items) - status = self.TestSetupScript() - # The following assumption is not true; a bad image might not - # successfully push onto a device. - # assert status == 0, 'When reset_to_bad, test setup should succeed.' - if status == 0: - status = self.TestScript() - assert status == 1, 'When reset_to_bad, status should be 1.' - - 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.DoBinarySearchBadItems() - self.prune_cycles += 1 - if not terminated: - break - # Prune is set. - prune_index = self.binary_search.current - - # If found item is last item, no new items can be found - 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 - if self.all_items[prune_index] in self.found_items: - self.l.LogOutput( - 'Found item already found before: %s.' % - self.all_items[prune_index], - print_to_console=self.verbose) - self.l.LogOutput('No more bad items remaining. Done searching.') - self.l.LogOutput('Bad items are: %s' % ' '.join(self.found_items)) - 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)) - self.found_items.add(new_all_items[-1]) - - # Everything below newly found bad item is now known to be a good item. - # Take these good items out of the equation to save time on the next - # search. We save these known good items so they are still sent to the - # switch_to_good script. - if prune_index: - self.known_good.update(new_all_items[:prune_index]) - new_all_items = new_all_items[prune_index:] - - self.l.LogOutput( - 'Old list: %s. New list: %s' % (str(self.all_items), - str(new_all_items)), - print_to_console=self.verbose) - - if not self.prune: - self.l.LogOutput('Not continuning further, --prune is not set') - break - # FIXME: Do we need to Convert the currently good items to bad - self.PopulateItemsUsingList(new_all_items) - - # If pass level bisecting is set, generate a script which contains command - # line options to rebuild bad item. - if self.pass_bisect: - status = self.GenerateBadCommandScript(self.found_items) - if status == 0: - 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 DoBinarySearchBadItems(self): - """Perform single iteration of binary search.""" - # If in resume mode don't reset search_cycles - if not self.resumed: - self.search_cycles = 0 - else: - self.resumed = False - - terminated = False - while self.search_cycles < self.iterations and not terminated: - self.SaveState() - self.OutputIterationProgressBadItem() + yield - self.search_cycles += 1 - [bad_items, good_items] = self.GetNextItems() - with SetFile(GOOD_SET_VAR, good_items), SetFile(BAD_SET_VAR, bad_items): - # TODO: bad_items should come first. - self.SwitchToGood(good_items) - self.SwitchToBad(bad_items) - status = self.TestSetupScript() - if status == 0: - status = self.TestScript() - terminated = self.binary_search.SetStatus(status) +class BinarySearchState(object): + """The binary search state class.""" - 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 terminated + def __init__( + self, + get_initial_items, + switch_to_good, + switch_to_bad, + test_setup_script, + test_script, + incremental, + prune, + pass_bisect, + ir_diff, + iterations, + prune_iterations, + verify, + file_args, + verbose, + ): + """BinarySearchState constructor, see Run for full args documentation.""" + self.get_initial_items = get_initial_items + self.switch_to_good = switch_to_good + self.switch_to_bad = switch_to_bad + self.test_setup_script = test_setup_script + self.test_script = test_script + self.incremental = incremental + self.prune = prune + self.pass_bisect = pass_bisect + self.ir_diff = ir_diff + self.iterations = iterations + self.prune_iterations = prune_iterations + self.verify = verify + self.file_args = file_args + self.verbose = verbose + + self.l = logger.GetLogger() + self.ce = command_executer.GetCommandExecuter() + + self.resumed = False + self.prune_cycles = 0 + 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() + self.found_items = set() + self.known_good = set() + + self.start_time = time.time() + + def SwitchToGood(self, item_list): + """Switch given items to "good" set.""" + if self.incremental: + self.l.LogOutput( + "Incremental set. Wanted to switch %s to good" % str(item_list), + print_to_console=self.verbose, + ) + 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), + print_to_console=self.verbose, + ) + + if not item_list: + return - 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) + self.l.LogOutput( + "Switching %s to good" % str(item_list), + print_to_console=self.verbose, + ) + 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): + """Switch given items to "bad" set.""" + if self.incremental: + self.l.LogOutput( + "Incremental set. Wanted to switch %s to bad" % str(item_list), + print_to_console=self.verbose, + ) + 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), + print_to_console=self.verbose, + ) + + if not item_list: + return - for desc in pass_mapping.pass_name: - if desc in pass_info: - return pass_mapping.pass_name[desc] + self.l.LogOutput( + "Switching %s to bad" % str(item_list), + print_to_console=self.verbose, + ) + 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): + """Pass given items to switch script. + + Args: + switch_script: path to switch script + item_list: list of all items to be switched + """ + if self.file_args: + with tempfile.NamedTemporaryFile("w", encoding="utf-8") as f: + f.write("\n".join(item_list)) + f.flush() + command = "%s %s" % (switch_script, f.name) + ret, _, _ = self.ce.RunCommandWExceptionCleanup( + command, print_to_console=self.verbose + ) + else: + command = "%s %s" % (switch_script, " ".join(item_list)) + try: + ret, _, _ = self.ce.RunCommandWExceptionCleanup( + command, print_to_console=self.verbose + ) + except OSError as e: + if e.errno == errno.E2BIG: + raise RuntimeError( + "Too many arguments for switch script! Use " + "--file_args" + ) + assert ret == 0, "Switch script %s returned %d" % (switch_script, ret) + + def TestScript(self): + """Run test script and return exit code from script.""" + command = self.test_script + ret, _, _ = self.ce.RunCommandWExceptionCleanup(command) + return ret + + def TestSetupScript(self): + """Run test setup script and return exit code from script.""" + if not self.test_setup_script: + return 0 + + command = self.test_setup_script + 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. + + Verify that a "good" set of items produces a "good" result and that a "bad" + set of items produces a "bad" result. To be run directly before running + DoSearch. If verify is False this step is skipped. + """ + if not self.verify: + return + + self.l.LogOutput("VERIFICATION") + self.l.LogOutput("Beginning tests to verify good/bad sets\n") + + self._OutputProgress("Verifying items from GOOD set\n") + with SetFile(GOOD_SET_VAR, self.all_items), SetFile(BAD_SET_VAR, []): + self.l.LogOutput("Resetting all items to good to verify.") + self.SwitchToGood(self.all_items) + status = self.TestSetupScript() + assert status == 0, "When reset_to_good, test setup should succeed." + status = self.TestScript() + assert status == 0, "When reset_to_good, status should be 0." + + self._OutputProgress("Verifying items from BAD set\n") + with SetFile(GOOD_SET_VAR, []), SetFile(BAD_SET_VAR, self.all_items): + self.l.LogOutput("Resetting all items to bad to verify.") + self.SwitchToBad(self.all_items) + status = self.TestSetupScript() + # The following assumption is not true; a bad image might not + # successfully push onto a device. + # assert status == 0, 'When reset_to_bad, test setup should succeed.' + if status == 0: + status = self.TestScript() + assert status == 1, "When reset_to_bad, status should be 1." + + 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.DoBinarySearchBadItems() + self.prune_cycles += 1 + if not terminated: + break + # Prune is set. + prune_index = self.binary_search.current + + # If found item is last item, no new items can be found + 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 + if self.all_items[prune_index] in self.found_items: + self.l.LogOutput( + "Found item already found before: %s." + % self.all_items[prune_index], + print_to_console=self.verbose, + ) + self.l.LogOutput("No more bad items remaining. Done searching.") + self.l.LogOutput( + "Bad items are: %s" % " ".join(self.found_items) + ) + 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)) + self.found_items.add(new_all_items[-1]) + + # Everything below newly found bad item is now known to be a good item. + # Take these good items out of the equation to save time on the next + # search. We save these known good items so they are still sent to the + # switch_to_good script. + if prune_index: + self.known_good.update(new_all_items[:prune_index]) + new_all_items = new_all_items[prune_index:] + + self.l.LogOutput( + "Old list: %s. New list: %s" + % (str(self.all_items), str(new_all_items)), + print_to_console=self.verbose, + ) + + if not self.prune: + self.l.LogOutput("Not continuning further, --prune is not set") + break + # FIXME: Do we need to Convert the currently good items to bad + self.PopulateItemsUsingList(new_all_items) + + # If pass level bisecting is set, generate a script which contains command + # line options to rebuild bad item. + if self.pass_bisect: + status = self.GenerateBadCommandScript(self.found_items) + if status == 0: + 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 DoBinarySearchBadItems(self): + """Perform single iteration of binary search.""" + # If in resume mode don't reset search_cycles + if not self.resumed: + self.search_cycles = 0 + else: + self.resumed = False + + terminated = False + while self.search_cycles < self.iterations and not terminated: + self.SaveState() + self.OutputIterationProgressBadItem() + + self.search_cycles += 1 + [bad_items, good_items] = self.GetNextItems() + + with SetFile(GOOD_SET_VAR, good_items), SetFile( + BAD_SET_VAR, bad_items + ): + # TODO: bad_items should come first. + self.SwitchToGood(good_items) + self.SwitchToBad(bad_items) + status = self.TestSetupScript() + if status == 0: + 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 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, generate_ir=False): + """Rebuild bad item with pass level bisect limit + + Run command line script generated by GenerateBadCommandScript(), with + pass level limit flags. + + Returns: + 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) + if generate_ir: + os.environ["LIMIT_FLAGS"] += " -S -emit-llvm" + 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 not in (-1, pass_num): + 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, generate_ir=False + ): + """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. + generate_ir: Whether to generate IR comparison. + + Returns: + 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" + ) + if generate_ir: + os.environ["LIMIT_FLAGS"] += " -S -emit-llvm" + 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: + # Print pass level IR diff only if transformation level bisection does + # not work. + if self.ir_diff: + self.PrintIRDiff(pass_limit) + 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 PrintIRDiff(self, pass_index, pass_name=None, trans_index=-1): + bad_item = list(self.found_items)[0] + self.l.LogOutput( + "IR difference before and after bad pass/transformation:", + print_to_console=self.verbose, + ) + + if trans_index == -1: + # Pass level IR diff + self.BuildWithPassLimit(pass_index, self.ir_diff) + good_ir = os.path.join(tempfile.tempdir, "good.s") + shutil.copyfile(bad_item, good_ir) + pass_index += 1 + self.BuildWithPassLimit(pass_index, self.ir_diff) + else: + # Transformation level IR diff + self.BuildWithTransformLimit( + trans_index, pass_name, pass_index, self.ir_diff + ) + good_ir = os.path.join(tempfile.tempdir, "good.s") + shutil.copyfile(bad_item, good_ir) + trans_index += 1 + self.BuildWithTransformLimit( + trans_index, pass_name, pass_index, self.ir_diff + ) + + bad_ir = os.path.join(tempfile.tempdir, "bad.s") + shutil.copyfile(bad_item, bad_ir) + + command = "diff %s %s" % (good_ir, bad_ir) + _, _, _ = self.ce.RunCommandWOutput( + command, print_to_console=self.verbose + ) + + 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 + ) + + if self.ir_diff: + self.PrintIRDiff(pass_index, pass_name, trans_index) + + 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. + + Returns: + 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. + + This method is mainly required for enumerating the initial list of items + from the get_initial_items script. + + Args: + command: path to executable that will enumerate items. + """ + ce = command_executer.GetCommandExecuter() + _, out, _ = ce.RunCommandWExceptionCleanup( + command, return_output=True, print_to_console=self.verbose + ) + all_items = out.split() + self.PopulateItemsUsingList(all_items) + + def PopulateItemsUsingList(self, all_items): + """Update all_items and binary searching logic from list. + + Args: + all_items: new list of all_items + """ + self.all_items = all_items + self.binary_search = binary_search_perforce.BinarySearcher( + logger_to_set=self.l + ) + self.binary_search.SetSortedList(self.all_items) + + def SaveState(self): + """Save state to STATE_FILE. + + SaveState will create a new unique, hidden state file to hold data from + object. Then atomically overwrite the STATE_FILE symlink to point to the + new data. + + Raises: + OSError if STATE_FILE already exists but is not a symlink. + """ + ce, l = self.ce, self.l + self.ce, self.l, self.binary_search.logger = None, None, None + old_state = None + + _, path = tempfile.mkstemp(prefix=HIDDEN_STATE_FILE, dir=".") + with open(path, "wb") as f: + pickle.dump(self, f) + + if os.path.exists(STATE_FILE): + if os.path.islink(STATE_FILE): + old_state = os.readlink(STATE_FILE) + else: + raise OSError( + ( + "%s already exists and is not a symlink!\n" + "State file saved to %s" % (STATE_FILE, path) + ) + ) + + # Create new link and atomically overwrite old link + temp_link = "%s.link" % HIDDEN_STATE_FILE + os.symlink(path, temp_link) + os.rename(temp_link, STATE_FILE) + + if old_state: + os.remove(old_state) + + self.ce, self.l, self.binary_search.logger = ce, l, l + + @classmethod + def LoadState(cls): + """Create BinarySearchState object from STATE_FILE.""" + if not os.path.isfile(STATE_FILE): + return None + try: + with open(STATE_FILE, "rb") as f: + bss = pickle.load(f) + bss.l = logger.GetLogger() + bss.ce = command_executer.GetCommandExecuter() + bss.binary_search.logger = bss.l + bss.start_time = time.time() + + # Set resumed to be True so we can enter DoBinarySearch without the + # method resetting our current search_cycles to 0. + bss.resumed = True + + # Set currently_good_items and currently_bad_items to empty so that the + # first iteration after resuming will always be non-incremental. This + # is just in case the environment changes, the user makes manual + # changes, or a previous switch_script corrupted the environment. + bss.currently_good_items = set() + bss.currently_bad_items = set() + + binary_search_perforce.verbose = bss.verbose + return bss + except Exception: + return None + + def RemoveState(self): + """Remove STATE_FILE and its symlinked data from file system.""" + if os.path.exists(STATE_FILE): + if os.path.islink(STATE_FILE): + real_file = os.readlink(STATE_FILE) + os.remove(real_file) + os.remove(STATE_FILE) + + def GetNextItems(self): + """Get next items for binary search based on result of the last test run.""" + border_item = self.binary_search.GetNext() + index = self.all_items.index(border_item) + + next_bad_items = self.all_items[: index + 1] + next_good_items = self.all_items[index + 1 :] + list(self.known_good) + + return [next_bad_items, next_good_items] + + def ElapsedTimeString(self): + """Return h m s format of elapsed time since execution has started.""" + diff = int(time.time() - self.start_time) + seconds = diff % 60 + minutes = (diff // 60) % 60 + hours = diff // (60 * 60) + + seconds = str(seconds).rjust(2) + minutes = str(minutes).rjust(2) + hours = str(hours).rjust(2) + + return "%sh %sm %ss" % (hours, minutes, seconds) + + def _OutputProgress(self, progress_text): + """Output current progress of binary search to console and logs. + + Args: + progress_text: The progress to display to the user. + """ + progress = ( + "\n***** PROGRESS (elapsed time: %s) *****\n" + "%s" + "************************************************" + ) + progress = progress % (self.ElapsedTimeString(), progress_text) + self.l.LogOutput(progress) + + 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), + ) + 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): + 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.binary_search) + return ret - # If pass not found, return None - return None - def BuildWithPassLimit(self, limit, generate_ir=False): - """Rebuild bad item with pass level bisect limit +class MockBinarySearchState(BinarySearchState): + """Mock class for BinarySearchState.""" + + def __init__(self, **kwargs): + # Initialize all arguments to None + default_kwargs = { + "get_initial_items": 'echo "1"', + "switch_to_good": None, + "switch_to_bad": None, + "test_setup_script": None, + "test_script": None, + "incremental": True, + "prune": False, + "pass_bisect": None, + "ir_diff": False, + "iterations": 50, + "prune_iterations": 100, + "verify": True, + "file_args": False, + "verbose": False, + } + default_kwargs.update(kwargs) + super(MockBinarySearchState, self).__init__(**default_kwargs) - Run command line script generated by GenerateBadCommandScript(), with - pass level limit flags. - Returns: - 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) - if generate_ir: - os.environ['LIMIT_FLAGS'] += ' -S -emit-llvm' - 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 not in (-1, pass_num): - 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, - generate_ir=False): - """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. +def _CanonicalizeScript(script_name): + """Return canonical path to script. 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. - generate_ir: Whether to generate IR comparison. + script_name: Relative or absolute path to script Returns: - Total number of transformations if limit set to -1, else return 0. + Canonicalized script path """ - 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' - if generate_ir: - os.environ['LIMIT_FLAGS'] += ' -S -emit-llvm' - 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: - # Print pass level IR diff only if transformation level bisection does - # not work. - if self.ir_diff: - self.PrintIRDiff(pass_limit) - 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 PrintIRDiff(self, pass_index, pass_name=None, trans_index=-1): - bad_item = list(self.found_items)[0] - self.l.LogOutput( - 'IR difference before and after bad pass/transformation:', - print_to_console=self.verbose) - - if trans_index == -1: - # Pass level IR diff - self.BuildWithPassLimit(pass_index, self.ir_diff) - good_ir = os.path.join(tempfile.tempdir, 'good.s') - shutil.copyfile(bad_item, good_ir) - pass_index += 1 - self.BuildWithPassLimit(pass_index, self.ir_diff) - else: - # Transformation level IR diff - self.BuildWithTransformLimit(trans_index, pass_name, pass_index, - self.ir_diff) - good_ir = os.path.join(tempfile.tempdir, 'good.s') - shutil.copyfile(bad_item, good_ir) - trans_index += 1 - self.BuildWithTransformLimit(trans_index, pass_name, pass_index, - self.ir_diff) - - bad_ir = os.path.join(tempfile.tempdir, 'bad.s') - shutil.copyfile(bad_item, bad_ir) - - command = 'diff %s %s' % (good_ir, bad_ir) - _, _, _ = self.ce.RunCommandWOutput(command, print_to_console=self.verbose) - - 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) - - if self.ir_diff: - self.PrintIRDiff(pass_index, pass_name, trans_index) - - 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 + script_name = os.path.expanduser(script_name) + if not script_name.startswith("/"): + return os.path.join(".", script_name) + + +def Run( + get_initial_items, + switch_to_good, + switch_to_bad, + test_script, + test_setup_script=None, + iterations=50, + prune=False, + pass_bisect=None, + ir_diff=False, + noincremental=False, + file_args=False, + verify=True, + prune_iterations=100, + verbose=False, + resume=False, +): + """Run binary search tool. + + Equivalent to running through terminal. 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. + 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 + switch_to_bad: Script that will take items as input and switch them to bad + set + test_script: Script that will determine if the current combination of good + and bad items make a "good" or "bad" result. + test_setup_script: Script to do necessary setup (building, compilation, + 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. 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. + ir_diff: Whether to print IR differences before and after bad + pass/transformation to verbose output. Defaults to False, only works when + pass_bisect is enabled. + 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. + 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. Returns: - index: Index of problematic pass/transformation. - pass_name: Works for pass level bisection, returns DebugCounter name for - bad pass. + 0 for success, error otherwise """ - # If in resume mode don't reset search_cycles - if not self.resumed: - self.search_cycles = 0 + # Notice that all the argument checks are in the Run() function rather than + # in the Main() function. It is not common to do so but some wrappers are + # going to call Run() directly and bypass checks in Main() function. + if resume: + logger.GetLogger().LogOutput("Resuming from %s" % STATE_FILE) + bss = BinarySearchState.LoadState() + if not bss: + logger.GetLogger().LogOutput( + "%s is not a valid binary_search_tool state file, cannot resume!" + % STATE_FILE + ) + return 1 + logger.GetLogger().LogOutput( + "Note: resuming from previous state, " + "ignoring given options and loading saved " + "options instead." + ) 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. - - This method is mainly required for enumerating the initial list of items - from the get_initial_items script. - - Args: - command: path to executable that will enumerate items. - """ - ce = command_executer.GetCommandExecuter() - _, out, _ = ce.RunCommandWExceptionCleanup( - command, return_output=True, print_to_console=self.verbose) - all_items = out.split() - self.PopulateItemsUsingList(all_items) - - def PopulateItemsUsingList(self, all_items): - """Update all_items and binary searching logic from list. - - Args: - all_items: new list of all_items - """ - self.all_items = all_items - self.binary_search = binary_search_perforce.BinarySearcher( - logger_to_set=self.l) - self.binary_search.SetSortedList(self.all_items) - - def SaveState(self): - """Save state to STATE_FILE. - - SaveState will create a new unique, hidden state file to hold data from - object. Then atomically overwrite the STATE_FILE symlink to point to the - new data. - - Raises: - OSError if STATE_FILE already exists but is not a symlink. - """ - ce, l = self.ce, self.l - self.ce, self.l, self.binary_search.logger = None, None, None - old_state = None - - _, path = tempfile.mkstemp(prefix=HIDDEN_STATE_FILE, dir='.') - with open(path, 'wb') as f: - pickle.dump(self, f) - - if os.path.exists(STATE_FILE): - if os.path.islink(STATE_FILE): - old_state = os.readlink(STATE_FILE) - else: - raise OSError(('%s already exists and is not a symlink!\n' - 'State file saved to %s' % (STATE_FILE, path))) - - # Create new link and atomically overwrite old link - temp_link = '%s.link' % HIDDEN_STATE_FILE - os.symlink(path, temp_link) - os.rename(temp_link, STATE_FILE) - - if old_state: - os.remove(old_state) - - self.ce, self.l, self.binary_search.logger = ce, l, l - - @classmethod - def LoadState(cls): - """Create BinarySearchState object from STATE_FILE.""" - if not os.path.isfile(STATE_FILE): - return None - try: - with open(STATE_FILE, 'rb') as f: - bss = pickle.load(f) - bss.l = logger.GetLogger() - bss.ce = command_executer.GetCommandExecuter() - bss.binary_search.logger = bss.l - bss.start_time = time.time() - - # Set resumed to be True so we can enter DoBinarySearch without the - # method resetting our current search_cycles to 0. - bss.resumed = True - - # Set currently_good_items and currently_bad_items to empty so that the - # first iteration after resuming will always be non-incremental. This - # is just in case the environment changes, the user makes manual - # changes, or a previous switch_script corrupted the environment. - bss.currently_good_items = set() - bss.currently_bad_items = set() - - binary_search_perforce.verbose = bss.verbose - return bss - except Exception: - return None - - def RemoveState(self): - """Remove STATE_FILE and its symlinked data from file system.""" - if os.path.exists(STATE_FILE): - if os.path.islink(STATE_FILE): - real_file = os.readlink(STATE_FILE) - os.remove(real_file) - os.remove(STATE_FILE) - - def GetNextItems(self): - """Get next items for binary search based on result of the last test run.""" - border_item = self.binary_search.GetNext() - index = self.all_items.index(border_item) - - next_bad_items = self.all_items[:index + 1] - next_good_items = self.all_items[index + 1:] + list(self.known_good) - - return [next_bad_items, next_good_items] - - def ElapsedTimeString(self): - """Return h m s format of elapsed time since execution has started.""" - diff = int(time.time() - self.start_time) - seconds = diff % 60 - minutes = (diff // 60) % 60 - hours = diff // (60 * 60) - - seconds = str(seconds).rjust(2) - minutes = str(minutes).rjust(2) - hours = str(hours).rjust(2) - - return '%sh %sm %ss' % (hours, minutes, seconds) - - def _OutputProgress(self, progress_text): - """Output current progress of binary search to console and logs. - - Args: - progress_text: The progress to display to the user. - """ - progress = ('\n***** PROGRESS (elapsed time: %s) *****\n' - '%s' - '************************************************') - progress = progress % (self.ElapsedTimeString(), progress_text) - self.l.LogOutput(progress) - - 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)) - 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): - 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.binary_search) - return ret - - -class MockBinarySearchState(BinarySearchState): - """Mock class for BinarySearchState.""" - - def __init__(self, **kwargs): - # Initialize all arguments to None - default_kwargs = { - 'get_initial_items': 'echo "1"', - 'switch_to_good': None, - 'switch_to_bad': None, - 'test_setup_script': None, - 'test_script': None, - 'incremental': True, - 'prune': False, - 'pass_bisect': None, - 'ir_diff': False, - 'iterations': 50, - 'prune_iterations': 100, - 'verify': True, - 'file_args': False, - 'verbose': False - } - default_kwargs.update(kwargs) - super(MockBinarySearchState, self).__init__(**default_kwargs) - - -def _CanonicalizeScript(script_name): - """Return canonical path to script. - - Args: - script_name: Relative or absolute path to script - - Returns: - Canonicalized script path - """ - script_name = os.path.expanduser(script_name) - if not script_name.startswith('/'): - return os.path.join('.', script_name) - - -def Run(get_initial_items, - switch_to_good, - switch_to_bad, - test_script, - test_setup_script=None, - iterations=50, - prune=False, - pass_bisect=None, - ir_diff=False, - noincremental=False, - file_args=False, - verify=True, - prune_iterations=100, - verbose=False, - resume=False): - """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 - switch_to_bad: Script that will take items as input and switch them to bad - set - test_script: Script that will determine if the current combination of good - and bad items make a "good" or "bad" result. - test_setup_script: Script to do necessary setup (building, compilation, - 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. 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. - ir_diff: Whether to print IR differences before and after bad - pass/transformation to verbose output. Defaults to False, only works when - pass_bisect is enabled. - 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. - 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. - - Returns: - 0 for success, error otherwise - """ - # Notice that all the argument checks are in the Run() function rather than - # in the Main() function. It is not common to do so but some wrappers are - # going to call Run() directly and bypass checks in Main() function. - if resume: - logger.GetLogger().LogOutput('Resuming from %s' % STATE_FILE) - bss = BinarySearchState.LoadState() - if not bss: - logger.GetLogger().LogOutput( - '%s is not a valid binary_search_tool state file, cannot resume!' % - STATE_FILE) - return 1 - logger.GetLogger().LogOutput('Note: resuming from previous state, ' - '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): - logger.GetLogger().LogOutput('The following options are required: ' - '[-i, -g, -b, -t] | [-r]') - return 1 - if pass_bisect and prune: - logger.GetLogger().LogOutput('"--pass_bisect" only works when ' - '"--prune" is set to be False.') - return 1 - if not pass_bisect and ir_diff: - logger.GetLogger().LogOutput('"--ir_diff" only works when ' - '"--pass_bisect" is enabled.') - - switch_to_good = _CanonicalizeScript(switch_to_good) - switch_to_bad = _CanonicalizeScript(switch_to_bad) - if test_setup_script: - test_setup_script = _CanonicalizeScript(test_setup_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 + if pass_bisect and prune: + logger.GetLogger().LogOutput( + '"--pass_bisect" only works when ' + '"--prune" is set to be False.' + ) + return 1 + if not pass_bisect and ir_diff: + logger.GetLogger().LogOutput( + '"--ir_diff" only works when ' '"--pass_bisect" is enabled.' + ) + + switch_to_good = _CanonicalizeScript(switch_to_good) + switch_to_bad = _CanonicalizeScript(switch_to_bad) + if test_setup_script: + test_setup_script = _CanonicalizeScript(test_setup_script) + if pass_bisect: + pass_bisect = _CanonicalizeScript(pass_bisect) + test_script = _CanonicalizeScript(test_script) + get_initial_items = _CanonicalizeScript(get_initial_items) + incremental = not noincremental + + binary_search_perforce.verbose = verbose + + bss = BinarySearchState( + get_initial_items, + switch_to_good, + switch_to_bad, + test_setup_script, + test_script, + incremental, + prune, + pass_bisect, + ir_diff, + iterations, + prune_iterations, + verify, + file_args, + verbose, + ) + bss.DoVerify() + + bss.DoSearchBadItems() if pass_bisect: - pass_bisect = _CanonicalizeScript(pass_bisect) - test_script = _CanonicalizeScript(test_script) - get_initial_items = _CanonicalizeScript(get_initial_items) - incremental = not noincremental - - binary_search_perforce.verbose = verbose - - bss = BinarySearchState(get_initial_items, switch_to_good, switch_to_bad, - test_setup_script, test_script, incremental, prune, - pass_bisect, ir_diff, iterations, prune_iterations, - verify, file_args, verbose) - bss.DoVerify() - - bss.DoSearchBadItems() - if pass_bisect: - bss.DoSearchBadPass() - bss.RemoveState() - logger.GetLogger().LogOutput( - 'Total execution time: %s' % bss.ElapsedTimeString()) + bss.DoSearchBadPass() + bss.RemoveState() + logger.GetLogger().LogOutput( + "Total execution time: %s" % bss.ElapsedTimeString() + ) - return 0 + return 0 def Main(argv): - """The main function.""" - # Common initializations + """The main function.""" + # Common initializations - parser = argparse.ArgumentParser() - common.BuildArgParser(parser) - logger.GetLogger().LogOutput(' '.join(argv)) - options = parser.parse_args(argv) + parser = argparse.ArgumentParser() + common.BuildArgParser(parser) + logger.GetLogger().LogOutput(" ".join(argv)) + options = parser.parse_args(argv) - # Get dictionary of all options - args = vars(options) - return Run(**args) + # Get dictionary of all options + args = vars(options) + return Run(**args) -if __name__ == '__main__': - sys.exit(Main(sys.argv[1:])) +if __name__ == "__main__": + sys.exit(Main(sys.argv[1:])) diff --git a/binary_search_tool/bisect_driver.py b/binary_search_tool/bisect_driver.py index ac37ad9f..8feb1a37 100644 --- a/binary_search_tool/bisect_driver.py +++ b/binary_search_tool/bisect_driver.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # @@ -19,400 +19,413 @@ Design doc: https://docs.google.com/document/d/1yDgaUIa2O5w6dc3sSTe1ry-1ehKajTGJGQCbyn0fcEM """ -from __future__ import print_function import contextlib import fcntl import os import shutil -import subprocess import stat +import subprocess import sys -VALID_MODES = ('POPULATE_GOOD', 'POPULATE_BAD', 'TRIAGE') -GOOD_CACHE = 'good' -BAD_CACHE = 'bad' -LIST_FILE = os.path.join(GOOD_CACHE, '_LIST') -CONTINUE_ON_MISSING = os.environ.get('BISECT_CONTINUE_ON_MISSING', None) == '1' -CONTINUE_ON_REDUNDANCY = os.environ.get('BISECT_CONTINUE_ON_REDUNDANCY', - None) == '1' -WRAPPER_SAFE_MODE = os.environ.get('BISECT_WRAPPER_SAFE_MODE', None) == '1' +VALID_MODES = ("POPULATE_GOOD", "POPULATE_BAD", "TRIAGE") +GOOD_CACHE = "good" +BAD_CACHE = "bad" +LIST_FILE = os.path.join(GOOD_CACHE, "_LIST") + +CONTINUE_ON_MISSING = os.environ.get("BISECT_CONTINUE_ON_MISSING", None) == "1" +CONTINUE_ON_REDUNDANCY = ( + os.environ.get("BISECT_CONTINUE_ON_REDUNDANCY", None) == "1" +) +WRAPPER_SAFE_MODE = os.environ.get("BISECT_WRAPPER_SAFE_MODE", None) == "1" class Error(Exception): - """The general compiler wrapper error class.""" + """The general compiler wrapper error class.""" @contextlib.contextmanager def lock_file(path, mode): - """Lock file and block if other process has lock on file. - - Acquire exclusive lock for file. Only blocks other processes if they attempt - to also acquire lock through this method. If only reading (modes 'r' and 'rb') - then the lock is shared (i.e. many reads can happen concurrently, but only one - process may write at a time). - - This function is a contextmanager, meaning it's meant to be used with the - "with" statement in Python. This is so cleanup and setup happens automatically - and cleanly. Execution of the outer "with" statement happens at the "yield" - statement. Execution resumes after the yield when the outer "with" statement - ends. - - Args: - path: path to file being locked - mode: mode to open file with ('w', 'r', etc.) - """ - with open(path, mode) as f: - # Apply FD_CLOEXEC argument to fd. This ensures that the file descriptor - # won't be leaked to any child processes. - current_args = fcntl.fcntl(f.fileno(), fcntl.F_GETFD) - fcntl.fcntl(f.fileno(), fcntl.F_SETFD, current_args | fcntl.FD_CLOEXEC) - - # Reads can share the lock as no race conditions exist. If write is needed, - # give writing process exclusive access to the file. - if f.mode == 'r' or f.mode == 'rb': - lock_type = fcntl.LOCK_SH - else: - lock_type = fcntl.LOCK_EX - - try: - fcntl.lockf(f, lock_type) - yield f - f.flush() - finally: - fcntl.lockf(f, fcntl.LOCK_UN) + """Lock file and block if other process has lock on file. + + Acquire exclusive lock for file. Only blocks other processes if they attempt + to also acquire lock through this method. If only reading (modes 'r' and 'rb') + then the lock is shared (i.e. many reads can happen concurrently, but only one + process may write at a time). + + This function is a contextmanager, meaning it's meant to be used with the + "with" statement in Python. This is so cleanup and setup happens automatically + and cleanly. Execution of the outer "with" statement happens at the "yield" + statement. Execution resumes after the yield when the outer "with" statement + ends. + + Args: + path: path to file being locked + mode: mode to open file with ('w', 'r', etc.) + """ + with open(path, mode) as f: + # Apply FD_CLOEXEC argument to fd. This ensures that the file descriptor + # won't be leaked to any child processes. + current_args = fcntl.fcntl(f.fileno(), fcntl.F_GETFD) + fcntl.fcntl(f.fileno(), fcntl.F_SETFD, current_args | fcntl.FD_CLOEXEC) + + # Reads can share the lock as no race conditions exist. If write is needed, + # give writing process exclusive access to the file. + if f.mode == "r" or f.mode == "rb": + lock_type = fcntl.LOCK_SH + else: + lock_type = fcntl.LOCK_EX + + try: + fcntl.lockf(f, lock_type) + yield f + f.flush() + finally: + fcntl.lockf(f, fcntl.LOCK_UN) def log_to_file(path, execargs, link_from=None, link_to=None): - """Common logging function. + """Common logging function. - Log current working directory, current execargs, and a from-to relationship - between files. - """ - with lock_file(path, 'a') as log: - log.write('cd: %s; %s\n' % (os.getcwd(), ' '.join(execargs))) - if link_from and link_to: - log.write('%s -> %s\n' % (link_from, link_to)) + Log current working directory, current execargs, and a from-to relationship + between files. + """ + with lock_file(path, "a") as log: + log.write("cd: %s; %s\n" % (os.getcwd(), " ".join(execargs))) + if link_from and link_to: + log.write("%s -> %s\n" % (link_from, link_to)) def exec_and_return(execargs): - """Execute process and return. + """Execute process and return. - Execute according to execargs and return immediately. Don't inspect - stderr or stdout. - """ - return subprocess.call(execargs) + Execute according to execargs and return immediately. Don't inspect + stderr or stdout. + """ + return subprocess.call(execargs) def which_cache(obj_file): - """Determine which cache an object belongs to. - - The binary search tool creates two files for each search iteration listing - the full set of bad objects and full set of good objects. We use this to - determine where an object file should be linked from (good or bad). - """ - bad_set_file = os.environ.get('BISECT_BAD_SET') - if in_object_list(obj_file, bad_set_file): - return BAD_CACHE - else: - return GOOD_CACHE + """Determine which cache an object belongs to. + + The binary search tool creates two files for each search iteration listing + the full set of bad objects and full set of good objects. We use this to + determine where an object file should be linked from (good or bad). + """ + bad_set_file = os.environ.get("BISECT_BAD_SET") + if in_object_list(obj_file, bad_set_file): + return BAD_CACHE + else: + return GOOD_CACHE def makedirs(path): - """Try to create directories in path.""" - try: - os.makedirs(path) - except os.error: - if not os.path.isdir(path): - raise + """Try to create directories in path.""" + try: + os.makedirs(path) + except os.error: + if not os.path.isdir(path): + raise def get_obj_path(execargs): - """Get the object path for the object file in the list of arguments. - - Returns: - Absolute object path from execution args (-o argument). If no object being - outputted, then return empty string. -o argument is checked only if -c is - also present. - """ - try: - i = execargs.index('-o') - _ = execargs.index('-c') - except ValueError: - return '' - - obj_path = execargs[i + 1] - # Ignore args that do not create a file. - if obj_path in ( - '-', - '/dev/null', - ): - return '' - # Ignore files ending in .tmp. - if obj_path.endswith(('.tmp',)): - return '' - # Ignore configuration files generated by Automake/Autoconf/CMake etc. - if (obj_path.endswith('conftest.o') or - obj_path.endswith('CMakeFiles/test.o') or - obj_path.find('CMakeTmp') != -1 or - os.path.abspath(obj_path).find('CMakeTmp') != -1): - return '' - - return os.path.abspath(obj_path) + """Get the object path for the object file in the list of arguments. + + Returns: + Absolute object path from execution args (-o argument). If no object being + outputted, then return empty string. -o argument is checked only if -c is + also present. + """ + try: + i = execargs.index("-o") + _ = execargs.index("-c") + except ValueError: + return "" + + obj_path = execargs[i + 1] + # Ignore args that do not create a file. + if obj_path in ( + "-", + "/dev/null", + ): + return "" + # Ignore files ending in .tmp. + if obj_path.endswith((".tmp",)): + return "" + # Ignore configuration files generated by Automake/Autoconf/CMake etc. + if ( + obj_path.endswith("conftest.o") + or obj_path.endswith("CMakeFiles/test.o") + or obj_path.find("CMakeTmp") != -1 + or os.path.abspath(obj_path).find("CMakeTmp") != -1 + ): + return "" + + return os.path.abspath(obj_path) def get_dep_path(execargs): - """Get the dep file path for the dep file in the list of arguments. + """Get the dep file path for the dep file in the list of arguments. - Returns: - Absolute path of dependency file path from execution args (-o argument). If - no dependency being outputted then return empty string. - """ - if '-MD' not in execargs and '-MMD' not in execargs: - return '' + Returns: + Absolute path of dependency file path from execution args (-o argument). If + no dependency being outputted then return empty string. + """ + if "-MD" not in execargs and "-MMD" not in execargs: + return "" - # If -MF is given this is the path of the dependency file. Otherwise the - # dependency file is the value of -o but with a .d extension - if '-MF' in execargs: - i = execargs.index('-MF') - dep_path = execargs[i + 1] - return os.path.abspath(dep_path) + # If -MF is given this is the path of the dependency file. Otherwise the + # dependency file is the value of -o but with a .d extension + if "-MF" in execargs: + i = execargs.index("-MF") + dep_path = execargs[i + 1] + return os.path.abspath(dep_path) - full_obj_path = get_obj_path(execargs) - if not full_obj_path: - return '' + full_obj_path = get_obj_path(execargs) + if not full_obj_path: + return "" - return full_obj_path[:-2] + '.d' + return full_obj_path[:-2] + ".d" def get_dwo_path(execargs): - """Get the dwo file path for the dwo file in the list of arguments. + """Get the dwo file path for the dwo file in the list of arguments. - Returns: - Absolute dwo file path from execution args (-gsplit-dwarf argument) If no - dwo file being outputted then return empty string. - """ - if '-gsplit-dwarf' not in execargs: - return '' + Returns: + Absolute dwo file path from execution args (-gsplit-dwarf argument) If no + dwo file being outputted then return empty string. + """ + if "-gsplit-dwarf" not in execargs: + return "" - full_obj_path = get_obj_path(execargs) - if not full_obj_path: - return '' + full_obj_path = get_obj_path(execargs) + if not full_obj_path: + return "" - return full_obj_path[:-2] + '.dwo' + return full_obj_path[:-2] + ".dwo" def in_object_list(obj_name, list_filename): - """Check if object file name exist in file with object list.""" - if not obj_name: - return False + """Check if object file name exist in file with object list.""" + if not obj_name: + return False - with lock_file(list_filename, 'r') as list_file: - for line in list_file: - if line.strip() == obj_name: - return True + with lock_file(list_filename, "r") as list_file: + for line in list_file: + if line.strip() == obj_name: + return True - return False + return False def get_side_effects(execargs): - """Determine side effects generated by compiler + """Determine side effects generated by compiler - Returns: - List of paths of objects that the compiler generates as side effects. - """ - side_effects = [] + Returns: + List of paths of objects that the compiler generates as side effects. + """ + side_effects = [] - # Cache dependency files - full_dep_path = get_dep_path(execargs) - if full_dep_path: - side_effects.append(full_dep_path) + # Cache dependency files + full_dep_path = get_dep_path(execargs) + if full_dep_path: + side_effects.append(full_dep_path) - # Cache dwo files - full_dwo_path = get_dwo_path(execargs) - if full_dwo_path: - side_effects.append(full_dwo_path) + # Cache dwo files + full_dwo_path = get_dwo_path(execargs) + if full_dwo_path: + side_effects.append(full_dwo_path) - return side_effects + return side_effects def cache_file(execargs, bisect_dir, cache, abs_file_path): - """Cache compiler output file (.o/.d/.dwo). - - Args: - execargs: compiler execution arguments. - bisect_dir: The directory where bisection caches live. - cache: Which cache the file will be cached to (GOOD/BAD). - abs_file_path: Absolute path to file being cached. - - Returns: - True if caching was successful, False otherwise. - """ - # os.path.join fails with absolute paths, use + instead - bisect_path = os.path.join(bisect_dir, cache) + abs_file_path - bisect_path_dir = os.path.dirname(bisect_path) - makedirs(bisect_path_dir) - pop_log = os.path.join(bisect_dir, cache, '_POPULATE_LOG') - log_to_file(pop_log, execargs, abs_file_path, bisect_path) - - try: - if os.path.exists(abs_file_path): - if os.path.exists(bisect_path): - # File exists - population_dir = os.path.join(bisect_dir, cache) - with lock_file(os.path.join(population_dir, '_DUPS'), - 'a') as dup_object_list: - dup_object_list.write('%s\n' % abs_file_path) - if CONTINUE_ON_REDUNDANCY: - return True - raise Exception( - 'Trying to cache file %s multiple times. To avoid the error, set ' \ - 'BISECT_CONTINUE_ON_REDUNDANCY to 1. For reference, the list of ' \ - 'such files will be written to %s' % (abs_file_path, os.path.join( - population_dir, '_DUPS'))) - - shutil.copy2(abs_file_path, bisect_path) - # Set cache object to be read-only so later compilations can't - # accidentally overwrite it. - os.chmod(bisect_path, 0o444) - return True - else: - # File not found (happens when compilation fails but error code is still - # 0) - return False - except Exception: - print('Could not cache file %s' % abs_file_path, file=sys.stderr) - raise + """Cache compiler output file (.o/.d/.dwo). + + Args: + execargs: compiler execution arguments. + bisect_dir: The directory where bisection caches live. + cache: Which cache the file will be cached to (GOOD/BAD). + abs_file_path: Absolute path to file being cached. + + Returns: + True if caching was successful, False otherwise. + """ + # os.path.join fails with absolute paths, use + instead + bisect_path = os.path.join(bisect_dir, cache) + abs_file_path + bisect_path_dir = os.path.dirname(bisect_path) + makedirs(bisect_path_dir) + pop_log = os.path.join(bisect_dir, cache, "_POPULATE_LOG") + log_to_file(pop_log, execargs, abs_file_path, bisect_path) + + try: + if os.path.exists(abs_file_path): + if os.path.exists(bisect_path): + # File exists + population_dir = os.path.join(bisect_dir, cache) + with lock_file( + os.path.join(population_dir, "_DUPS"), "a" + ) as dup_object_list: + dup_object_list.write("%s\n" % abs_file_path) + if CONTINUE_ON_REDUNDANCY: + return True + raise Exception( + "Trying to cache file %s multiple times. To avoid the error, set " + "BISECT_CONTINUE_ON_REDUNDANCY to 1. For reference, the list of " + "such files will be written to %s" + % (abs_file_path, os.path.join(population_dir, "_DUPS")) + ) + + shutil.copy2(abs_file_path, bisect_path) + # Set cache object to be read-only so later compilations can't + # accidentally overwrite it. + os.chmod(bisect_path, 0o444) + return True + else: + # File not found (happens when compilation fails but error code is still + # 0) + return False + except Exception: + print("Could not cache file %s" % abs_file_path, file=sys.stderr) + raise def restore_file(bisect_dir, cache, abs_file_path): - """Restore file from cache (.o/.d/.dwo). - - Args: - bisect_dir: The directory where bisection caches live. - cache: Which cache the file will be restored from (GOOD/BAD). - abs_file_path: Absolute path to file being restored. - """ - # os.path.join fails with absolute paths, use + instead - cached_path = os.path.join(bisect_dir, cache) + abs_file_path - if os.path.exists(cached_path): - if os.path.exists(abs_file_path): - os.remove(abs_file_path) - shutil.copy2(cached_path, abs_file_path) - # Add write permission to the restored object files as some packages - # (such as kernels) may need write permission to delete files. - os.chmod(abs_file_path, os.stat(abs_file_path).st_mode | stat.S_IWUSR) - else: - raise Error(('%s is missing from %s cache! Unsure how to proceed. Make ' - 'will now crash.' % (cache, cached_path))) + """Restore file from cache (.o/.d/.dwo). + + Args: + bisect_dir: The directory where bisection caches live. + cache: Which cache the file will be restored from (GOOD/BAD). + abs_file_path: Absolute path to file being restored. + """ + # os.path.join fails with absolute paths, use + instead + cached_path = os.path.join(bisect_dir, cache) + abs_file_path + if os.path.exists(cached_path): + if os.path.exists(abs_file_path): + os.remove(abs_file_path) + shutil.copy2(cached_path, abs_file_path) + # Add write permission to the restored object files as some packages + # (such as kernels) may need write permission to delete files. + os.chmod(abs_file_path, os.stat(abs_file_path).st_mode | stat.S_IWUSR) + else: + raise Error( + ( + "%s is missing from %s cache! Unsure how to proceed. Make " + "will now crash." % (cache, cached_path) + ) + ) def bisect_populate(execargs, bisect_dir, population_name): - """Add necessary information to the bisect cache for the given execution. - - Extract the necessary information for bisection from the compiler - execution arguments and put it into the bisection cache. This - includes copying the created object file, adding the object - file path to the cache list and keeping a log of the execution. - - Args: - execargs: compiler execution arguments. - bisect_dir: bisection directory. - population_name: name of the cache being populated (good/bad). - """ - retval = exec_and_return(execargs) - if retval: - return retval - - full_obj_path = get_obj_path(execargs) - # This is not a normal compiler call because it doesn't have a -o argument, - # or the -o argument has an unusable output file. - # It's likely that this compiler call was actually made to invoke the linker, - # or as part of a configuratoin test. In this case we want to simply call the - # compiler and return. - if not full_obj_path: - return retval - - # Return if not able to cache the object file - if not cache_file(execargs, bisect_dir, population_name, full_obj_path): - return retval + """Add necessary information to the bisect cache for the given execution. + + Extract the necessary information for bisection from the compiler + execution arguments and put it into the bisection cache. This + includes copying the created object file, adding the object + file path to the cache list and keeping a log of the execution. + + Args: + execargs: compiler execution arguments. + bisect_dir: bisection directory. + population_name: name of the cache being populated (good/bad). + """ + retval = exec_and_return(execargs) + if retval: + return retval - population_dir = os.path.join(bisect_dir, population_name) - with lock_file(os.path.join(population_dir, '_LIST'), 'a') as object_list: - object_list.write('%s\n' % full_obj_path) + full_obj_path = get_obj_path(execargs) + # This is not a normal compiler call because it doesn't have a -o argument, + # or the -o argument has an unusable output file. + # It's likely that this compiler call was actually made to invoke the linker, + # or as part of a configuratoin test. In this case we want to simply call the + # compiler and return. + if not full_obj_path: + return retval - for side_effect in get_side_effects(execargs): - _ = cache_file(execargs, bisect_dir, population_name, side_effect) + # Return if not able to cache the object file + if not cache_file(execargs, bisect_dir, population_name, full_obj_path): + return retval - return retval + population_dir = os.path.join(bisect_dir, population_name) + with lock_file(os.path.join(population_dir, "_LIST"), "a") as object_list: + object_list.write("%s\n" % full_obj_path) + for side_effect in get_side_effects(execargs): + _ = cache_file(execargs, bisect_dir, population_name, side_effect) -def bisect_triage(execargs, bisect_dir): - """Use object object file from appropriate cache (good/bad). - - Given a populated bisection directory, use the object file saved - into one of the caches (good/bad) according to what is specified - in the good/bad sets. The good/bad sets are generated by the - high level binary search tool. Additionally restore any possible - side effects of compiler. - - Args: - execargs: compiler execution arguments. - bisect_dir: populated bisection directory. - """ - full_obj_path = get_obj_path(execargs) - obj_list = os.path.join(bisect_dir, LIST_FILE) - - # If the output isn't an object file just call compiler - if not full_obj_path: - return exec_and_return(execargs) - - # If this isn't a bisected object just call compiler - # This shouldn't happen! - if not in_object_list(full_obj_path, obj_list): - if CONTINUE_ON_MISSING: - log_file = os.path.join(bisect_dir, '_MISSING_CACHED_OBJ_LOG') - log_to_file(log_file, execargs, '? compiler', full_obj_path) - return exec_and_return(execargs) - else: - raise Error(('%s is missing from cache! To ignore export ' - 'BISECT_CONTINUE_ON_MISSING=1. See documentation for more ' - 'details on this option.' % full_obj_path)) - - cache = which_cache(full_obj_path) - - # If using safe WRAPPER_SAFE_MODE option call compiler and overwrite the - # result from the good/bad cache. This option is safe and covers all compiler - # side effects, but is very slow! - if WRAPPER_SAFE_MODE: - retval = exec_and_return(execargs) - if retval: - return retval - os.remove(full_obj_path) - restore_file(bisect_dir, cache, full_obj_path) return retval - # Generate compiler side effects. Trick Make into thinking compiler was - # actually executed. - for side_effect in get_side_effects(execargs): - restore_file(bisect_dir, cache, side_effect) - # If generated object file happened to be pruned/cleaned by Make then link it - # over from cache again. - if not os.path.exists(full_obj_path): - restore_file(bisect_dir, cache, full_obj_path) - - return 0 +def bisect_triage(execargs, bisect_dir): + """Use object object file from appropriate cache (good/bad). + + Given a populated bisection directory, use the object file saved + into one of the caches (good/bad) according to what is specified + in the good/bad sets. The good/bad sets are generated by the + high level binary search tool. Additionally restore any possible + side effects of compiler. + + Args: + execargs: compiler execution arguments. + bisect_dir: populated bisection directory. + """ + full_obj_path = get_obj_path(execargs) + obj_list = os.path.join(bisect_dir, LIST_FILE) + + # If the output isn't an object file just call compiler + if not full_obj_path: + return exec_and_return(execargs) + + # If this isn't a bisected object just call compiler + # This shouldn't happen! + if not in_object_list(full_obj_path, obj_list): + if CONTINUE_ON_MISSING: + log_file = os.path.join(bisect_dir, "_MISSING_CACHED_OBJ_LOG") + log_to_file(log_file, execargs, "? compiler", full_obj_path) + return exec_and_return(execargs) + else: + raise Error( + ( + "%s is missing from cache! To ignore export " + "BISECT_CONTINUE_ON_MISSING=1. See documentation for more " + "details on this option." % full_obj_path + ) + ) + + cache = which_cache(full_obj_path) + + # If using safe WRAPPER_SAFE_MODE option call compiler and overwrite the + # result from the good/bad cache. This option is safe and covers all compiler + # side effects, but is very slow! + if WRAPPER_SAFE_MODE: + retval = exec_and_return(execargs) + if retval: + return retval + os.remove(full_obj_path) + restore_file(bisect_dir, cache, full_obj_path) + return retval + + # Generate compiler side effects. Trick Make into thinking compiler was + # actually executed. + for side_effect in get_side_effects(execargs): + restore_file(bisect_dir, cache, side_effect) + + # If generated object file happened to be pruned/cleaned by Make then link it + # over from cache again. + if not os.path.exists(full_obj_path): + restore_file(bisect_dir, cache, full_obj_path) + + return 0 def bisect_driver(bisect_stage, bisect_dir, execargs): - """Call appropriate bisection stage according to value in bisect_stage.""" - if bisect_stage == 'POPULATE_GOOD': - return bisect_populate(execargs, bisect_dir, GOOD_CACHE) - elif bisect_stage == 'POPULATE_BAD': - return bisect_populate(execargs, bisect_dir, BAD_CACHE) - elif bisect_stage == 'TRIAGE': - return bisect_triage(execargs, bisect_dir) - else: - raise ValueError('wrong value for BISECT_STAGE: %s' % bisect_stage) + """Call appropriate bisection stage according to value in bisect_stage.""" + if bisect_stage == "POPULATE_GOOD": + return bisect_populate(execargs, bisect_dir, GOOD_CACHE) + elif bisect_stage == "POPULATE_BAD": + return bisect_populate(execargs, bisect_dir, BAD_CACHE) + elif bisect_stage == "TRIAGE": + return bisect_triage(execargs, bisect_dir) + else: + raise ValueError("wrong value for BISECT_STAGE: %s" % bisect_stage) diff --git a/binary_search_tool/common.py b/binary_search_tool/common.py index 85cd478b..f6165847 100644 --- a/binary_search_tool/common.py +++ b/binary_search_tool/common.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -21,276 +21,303 @@ ArgumentDict inherits OrderedDict in order to preserve the order the args are created so the help text is made properly. """ -from __future__ import print_function import collections import os import sys + # Programatically 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]))) + 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]))) + wdir = os.getcwd() + utils_pythonpath = os.path.abspath( + "{0}/{1}/..".format(wdir, os.path.dirname(sys.argv[0])) + ) sys.path.append(utils_pythonpath) class ArgumentDict(collections.OrderedDict): - """Wrapper around OrderedDict, represents CLI arguments for program. - - AddArgument enforces the following layout: - { - ['-n', '--iterations'] : { - 'dest': 'iterations', - 'type': int, - 'help': 'Number of iterations to try in the search.', - 'default': 50 - } - [arg_name1, arg_name2, ...] : { - arg_option1 : arg_option_val1, - ... - }, - ... - } - """ - _POSSIBLE_OPTIONS = [ - 'action', 'nargs', 'const', 'default', 'type', 'choices', 'required', - 'help', 'metavar', 'dest' - ] - - def AddArgument(self, *args, **kwargs): - """Add argument to ArgsDict, has same signature as argparse.add_argument - - Emulates the the argparse.add_argument method so the internal OrderedDict - can be safely and easily populated. Each call to this method will have a 1-1 - corresponding call to argparse.add_argument once BuildArgParser is called. - - Args: - *args: The names for the argument (-V, --verbose, etc.) - **kwargs: The options for the argument, corresponds to the args of - argparse.add_argument - - Returns: - None - - Raises: - TypeError: if args is empty or if option in kwargs is not a valid - option for argparse.add_argument. + """Wrapper around OrderedDict, represents CLI arguments for program. + + AddArgument enforces the following layout: + { + ['-n', '--iterations'] : { + 'dest': 'iterations', + 'type': int, + 'help': 'Number of iterations to try in the search.', + 'default': 50 + } + [arg_name1, arg_name2, ...] : { + arg_option1 : arg_option_val1, + ... + }, + ... + } """ - if not args: - raise TypeError('Argument needs at least one name') - - for key in kwargs: - if key not in self._POSSIBLE_OPTIONS: - raise TypeError('Invalid option "%s" for argument %s' % (key, args[0])) - self[args] = kwargs + _POSSIBLE_OPTIONS = [ + "action", + "nargs", + "const", + "default", + "type", + "choices", + "required", + "help", + "metavar", + "dest", + ] + + def AddArgument(self, *args, **kwargs): + """Add argument to ArgsDict, has same signature as argparse.add_argument + + Emulates the the argparse.add_argument method so the internal OrderedDict + can be safely and easily populated. Each call to this method will have a 1-1 + corresponding call to argparse.add_argument once BuildArgParser is called. + + Args: + *args: The names for the argument (-V, --verbose, etc.) + **kwargs: The options for the argument, corresponds to the args of + argparse.add_argument + + Returns: + None + + Raises: + TypeError: if args is empty or if option in kwargs is not a valid + option for argparse.add_argument. + """ + if not args: + raise TypeError("Argument needs at least one name") + + for key in kwargs: + if key not in self._POSSIBLE_OPTIONS: + raise TypeError( + 'Invalid option "%s" for argument %s' % (key, args[0]) + ) + + self[args] = kwargs _ArgsDict = ArgumentDict() def GetArgsDict(): - """_ArgsDict singleton method""" - if not _ArgsDict: - _BuildArgsDict(_ArgsDict) - return _ArgsDict + """_ArgsDict singleton method""" + if not _ArgsDict: + _BuildArgsDict(_ArgsDict) + return _ArgsDict def BuildArgParser(parser, override=False): - """Add all arguments from singleton ArgsDict to parser. + """Add all arguments from singleton ArgsDict to parser. - Will take argparse parser and add all arguments in ArgsDict. Will ignore - the default and required options if override is set to True. + Will take argparse parser and add all arguments in ArgsDict. Will ignore + the default and required options if override is set to True. - Args: - parser: type argparse.ArgumentParser, will call add_argument for every item - in _ArgsDict - override: True if being called from run_bisect.py. Used to say that default - and required options are to be ignored + Args: + parser: type argparse.ArgumentParser, will call add_argument for every item + in _ArgsDict + override: True if being called from run_bisect.py. Used to say that default + and required options are to be ignored - Returns: - None - """ - ArgsDict = GetArgsDict() + Returns: + None + """ + ArgsDict = GetArgsDict() - # Have no defaults when overriding - for arg_names, arg_options in ArgsDict.items(): - if override: - arg_options = arg_options.copy() - arg_options.pop('default', None) - arg_options.pop('required', None) + # Have no defaults when overriding + for arg_names, arg_options in ArgsDict.items(): + if override: + arg_options = arg_options.copy() + arg_options.pop("default", None) + arg_options.pop("required", None) - parser.add_argument(*arg_names, **arg_options) + parser.add_argument(*arg_names, **arg_options) def StrToBool(str_in): - if str_in.lower() in ['true', 't', '1']: - return True - if str_in.lower() in ['false', 'f', '0']: - return False + if str_in.lower() in ["true", "t", "1"]: + return True + if str_in.lower() in ["false", "f", "0"]: + return False - raise AttributeError('%s is not a valid boolean string' % str_in) + raise AttributeError("%s is not a valid boolean string" % str_in) def _BuildArgsDict(args): - """Populate ArgumentDict with all arguments""" - args.AddArgument( - '-n', - '--iterations', - dest='iterations', - type=int, - help='Number of iterations to try in the search.', - default=50) - args.AddArgument( - '-i', - '--get_initial_items', - dest='get_initial_items', - help='Script to run to get the initial objects. ' - 'If your script requires user input ' - 'the --verbose option must be used') - args.AddArgument( - '-g', - '--switch_to_good', - dest='switch_to_good', - help='Script to run to switch to good. ' - 'If your switch script requires user input ' - 'the --verbose option must be used') - args.AddArgument( - '-b', - '--switch_to_bad', - dest='switch_to_bad', - help='Script to run to switch to bad. ' - 'If your switch script requires user input ' - 'the --verbose option must be used') - args.AddArgument( - '-I', - '--test_setup_script', - dest='test_setup_script', - help='Optional script to perform building, flashing, ' - 'and other setup before the test script runs.') - args.AddArgument( - '-t', - '--test_script', - dest='test_script', - help='Script to run to test the ' - 'output after packages are built.') - # No input (evals to False), - # --prune (evals to True), - # --prune=False, - # --prune=True - args.AddArgument( - '-p', - '--prune', - dest='prune', - nargs='?', - const=True, - default=False, - type=StrToBool, - metavar='bool', - help='If True, continue until all bad items are found. ' - 'Defaults to False.') - args.AddArgument( - '-P', - '--pass_bisect', - dest='pass_bisect', - default=None, - help='Script to generate another script for pass level bisect, ' - '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.') - # No input (evals to False), - # --ir_diff (evals to True), - # --ir_diff=False, - # --ir_diff=True - args.AddArgument( - '-d', - '--ir_diff', - dest='ir_diff', - nargs='?', - const=True, - default=False, - type=StrToBool, - metavar='bool', - help='Whether to print IR differences before and after bad ' - 'pass/transformation to verbose output. Defaults to False, ' - 'only works when pass_bisect is enabled.') - # No input (evals to False), - # --noincremental (evals to True), - # --noincremental=False, - # --noincremental=True - args.AddArgument( - '-c', - '--noincremental', - dest='noincremental', - nargs='?', - const=True, - default=False, - type=StrToBool, - metavar='bool', - help="If True, don't propagate good/bad changes " - 'incrementally. Defaults to False.') - # No input (evals to False), - # --file_args (evals to True), - # --file_args=False, - # --file_args=True - args.AddArgument( - '-f', - '--file_args', - dest='file_args', - nargs='?', - const=True, - default=False, - type=StrToBool, - metavar='bool', - help='Whether to use a file to pass arguments to scripts. ' - 'Defaults to False.') - # No input (evals to True), - # --verify (evals to True), - # --verify=False, - # --verify=True - args.AddArgument( - '--verify', - dest='verify', - nargs='?', - const=True, - default=True, - type=StrToBool, - metavar='bool', - help='Whether to run verify iterations before searching. ' - 'Defaults to True.') - args.AddArgument( - '-N', - '--prune_iterations', - dest='prune_iterations', - type=int, - help='Number of prune iterations to try in the search.', - default=100) - # No input (evals to False), - # --verbose (evals to True), - # --verbose=False, - # --verbose=True - args.AddArgument( - '-V', - '--verbose', - dest='verbose', - nargs='?', - const=True, - default=False, - type=StrToBool, - metavar='bool', - help='If True, print full output to console.') - args.AddArgument( - '-r', - '--resume', - dest='resume', - action='store_true', - help='Resume bisection tool execution from state file.' - 'Useful if the last bisection was terminated ' - 'before it could properly finish.') + """Populate ArgumentDict with all arguments""" + args.AddArgument( + "-n", + "--iterations", + dest="iterations", + type=int, + help="Number of iterations to try in the search.", + default=50, + ) + args.AddArgument( + "-i", + "--get_initial_items", + dest="get_initial_items", + help="Script to run to get the initial objects. " + "If your script requires user input " + "the --verbose option must be used", + ) + args.AddArgument( + "-g", + "--switch_to_good", + dest="switch_to_good", + help="Script to run to switch to good. " + "If your switch script requires user input " + "the --verbose option must be used", + ) + args.AddArgument( + "-b", + "--switch_to_bad", + dest="switch_to_bad", + help="Script to run to switch to bad. " + "If your switch script requires user input " + "the --verbose option must be used", + ) + args.AddArgument( + "-I", + "--test_setup_script", + dest="test_setup_script", + help="Optional script to perform building, flashing, " + "and other setup before the test script runs.", + ) + args.AddArgument( + "-t", + "--test_script", + dest="test_script", + help="Script to run to test the " "output after packages are built.", + ) + # No input (evals to False), + # --prune (evals to True), + # --prune=False, + # --prune=True + args.AddArgument( + "-p", + "--prune", + dest="prune", + nargs="?", + const=True, + default=False, + type=StrToBool, + metavar="bool", + help="If True, continue until all bad items are found. " + "Defaults to False.", + ) + args.AddArgument( + "-P", + "--pass_bisect", + dest="pass_bisect", + default=None, + help="Script to generate another script for pass level bisect, " + "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.", + ) + # No input (evals to False), + # --ir_diff (evals to True), + # --ir_diff=False, + # --ir_diff=True + args.AddArgument( + "-d", + "--ir_diff", + dest="ir_diff", + nargs="?", + const=True, + default=False, + type=StrToBool, + metavar="bool", + help="Whether to print IR differences before and after bad " + "pass/transformation to verbose output. Defaults to False, " + "only works when pass_bisect is enabled.", + ) + # No input (evals to False), + # --noincremental (evals to True), + # --noincremental=False, + # --noincremental=True + args.AddArgument( + "-c", + "--noincremental", + dest="noincremental", + nargs="?", + const=True, + default=False, + type=StrToBool, + metavar="bool", + help="If True, don't propagate good/bad changes " + "incrementally. Defaults to False.", + ) + # No input (evals to False), + # --file_args (evals to True), + # --file_args=False, + # --file_args=True + args.AddArgument( + "-f", + "--file_args", + dest="file_args", + nargs="?", + const=True, + default=False, + type=StrToBool, + metavar="bool", + help="Whether to use a file to pass arguments to scripts. " + "Defaults to False.", + ) + # No input (evals to True), + # --verify (evals to True), + # --verify=False, + # --verify=True + args.AddArgument( + "--verify", + dest="verify", + nargs="?", + const=True, + default=True, + type=StrToBool, + metavar="bool", + help="Whether to run verify iterations before searching. " + "Defaults to True.", + ) + args.AddArgument( + "-N", + "--prune_iterations", + dest="prune_iterations", + type=int, + help="Number of prune iterations to try in the search.", + default=100, + ) + # No input (evals to False), + # --verbose (evals to True), + # --verbose=False, + # --verbose=True + args.AddArgument( + "-V", + "--verbose", + dest="verbose", + nargs="?", + const=True, + default=False, + type=StrToBool, + metavar="bool", + help="If True, print full output to console.", + ) + args.AddArgument( + "-r", + "--resume", + dest="resume", + action="store_true", + help="Resume bisection tool execution from state file." + "Useful if the last bisection was terminated " + "before it could properly finish.", + ) diff --git a/binary_search_tool/common/boot_test.sh b/binary_search_tool/common/boot_test.sh index 8f6d9a7d..384712b7 100755 --- a/binary_search_tool/common/boot_test.sh +++ b/binary_search_tool/common/boot_test.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script pings the chromebook to determine if it has successfully booted. # diff --git a/binary_search_tool/common/hash_test.sh b/binary_search_tool/common/hash_test.sh index 5450988e..338ee026 100755 --- a/binary_search_tool/common/hash_test.sh +++ b/binary_search_tool/common/hash_test.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script is intended to be used by binary_search_state.py. It is to # be used for testing/development of the binary search triage tool diff --git a/binary_search_tool/common/interactive_test.sh b/binary_search_tool/common/interactive_test.sh index 8773dd12..05d47b7f 100755 --- a/binary_search_tool/common/interactive_test.sh +++ b/binary_search_tool/common/interactive_test.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script pings the chromebook to determine if it successfully booted. # It then asks the user if the image is good or not, allowing the user to diff --git a/binary_search_tool/common/interactive_test_noping.sh b/binary_search_tool/common/interactive_test_noping.sh index bb01b950..d4e77d7c 100755 --- a/binary_search_tool/common/interactive_test_noping.sh +++ b/binary_search_tool/common/interactive_test_noping.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script asks the user if the image is good or not, allowing the user to # conduct whatever tests the user wishes, and waiting for a response. diff --git a/binary_search_tool/common/test_setup.sh b/binary_search_tool/common/test_setup.sh index 3ea73272..06452346 100755 --- a/binary_search_tool/common/test_setup.sh +++ b/binary_search_tool/common/test_setup.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2021 The Chromium OS Authors. All rights reserved. +# Copyright 2021 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # diff --git a/binary_search_tool/compiler_wrapper.py b/binary_search_tool/compiler_wrapper.py index 0fd92c67..c32826b0 100755 --- a/binary_search_tool/compiler_wrapper.py +++ b/binary_search_tool/compiler_wrapper.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -20,7 +20,6 @@ Design doc: https://docs.google.com/document/d/1yDgaUIa2O5w6dc3sSTe1ry-1ehKajTGJGQCbyn0fcEM """ -from __future__ import print_function import os import shlex @@ -28,41 +27,42 @@ import sys from binary_search_tool import bisect_driver -WRAPPED = '%s.real' % sys.argv[0] -BISECT_STAGE = os.environ.get('BISECT_STAGE') -DEFAULT_BISECT_DIR = os.path.expanduser('~/ANDROID_BISECT') -BISECT_DIR = os.environ.get('BISECT_DIR') or DEFAULT_BISECT_DIR + +WRAPPED = "%s.real" % sys.argv[0] +BISECT_STAGE = os.environ.get("BISECT_STAGE") +DEFAULT_BISECT_DIR = os.path.expanduser("~/ANDROID_BISECT") +BISECT_DIR = os.environ.get("BISECT_DIR") or DEFAULT_BISECT_DIR def ProcessArgFile(arg_file): - args = [] - # Read in entire file at once and parse as if in shell - with open(arg_file, 'r', encoding='utf-8') as f: - args.extend(shlex.split(f.read())) + args = [] + # Read in entire file at once and parse as if in shell + with open(arg_file, "r", encoding="utf-8") as f: + args.extend(shlex.split(f.read())) - return args + return args def Main(_): - if not os.path.islink(sys.argv[0]): - print("Compiler wrapper can't be called directly!") - return 1 + if not os.path.islink(sys.argv[0]): + print("Compiler wrapper can't be called directly!") + return 1 - execargs = [WRAPPED] + sys.argv[1:] + execargs = [WRAPPED] + sys.argv[1:] - if BISECT_STAGE not in bisect_driver.VALID_MODES or '-o' not in execargs: - os.execv(WRAPPED, [WRAPPED] + sys.argv[1:]) + if BISECT_STAGE not in bisect_driver.VALID_MODES or "-o" not in execargs: + os.execv(WRAPPED, [WRAPPED] + sys.argv[1:]) - # Handle @file argument syntax with compiler - for idx, _ in enumerate(execargs): - # @file can be nested in other @file arguments, use While to re-evaluate - # the first argument of the embedded file. - while execargs[idx][0] == '@': - args_in_file = ProcessArgFile(execargs[idx][1:]) - execargs = execargs[0:idx] + args_in_file + execargs[idx + 1:] + # Handle @file argument syntax with compiler + for idx, _ in enumerate(execargs): + # @file can be nested in other @file arguments, use While to re-evaluate + # the first argument of the embedded file. + while execargs[idx][0] == "@": + args_in_file = ProcessArgFile(execargs[idx][1:]) + execargs = execargs[0:idx] + args_in_file + execargs[idx + 1 :] - bisect_driver.bisect_driver(BISECT_STAGE, BISECT_DIR, execargs) + bisect_driver.bisect_driver(BISECT_STAGE, BISECT_DIR, execargs) -if __name__ == '__main__': - sys.exit(Main(sys.argv[1:])) +if __name__ == "__main__": + sys.exit(Main(sys.argv[1:])) diff --git a/binary_search_tool/cros_pkg/create_cleanup_script.py b/binary_search_tool/cros_pkg/create_cleanup_script.py index 62ee38f1..abfea5eb 100755 --- a/binary_search_tool/cros_pkg/create_cleanup_script.py +++ b/binary_search_tool/cros_pkg/create_cleanup_script.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -13,20 +13,19 @@ undo the changes made by setup.sh, returning everything to its original state. """ -from __future__ import print_function import argparse import sys def Usage(parser, msg): - print('ERROR: ' + msg) - parser.print_help() - sys.exit(1) + print("ERROR: " + msg) + parser.print_help() + sys.exit(1) def Main(argv): - """Generate a script to undo changes done by setup.sh + """Generate a script to undo changes done by setup.sh The script setup.sh makes a change that needs to be undone, namely it creates a soft link making /build/${board} point @@ -40,80 +39,91 @@ def Main(argv): This function takes arguments that tell it exactly what setup.sh actually did, then generates a script to undo those exact changes. - """ - - parser = argparse.ArgumentParser() - parser.add_argument( - '--board', - dest='board', - required=True, - help='Chromeos board for packages/image.') - - parser.add_argument( - '--old_tree_missing', - dest='tree_existed', - action='store_false', - help='Did /build/${BOARD} exist.', - default=True) - - parser.add_argument( - '--renamed_tree', - dest='renamed_tree', - action='store_true', - help='Was /build/${BOARD} saved & renamed.', - default=False) - - parser.add_argument( - '--old_link', - dest='old_link', - help=('The original build tree soft link.')) - - options = parser.parse_args(argv[1:]) - - if options.old_link or options.renamed_tree: - if not options.tree_existed: - Usage( - parser, 'If --tree_existed is False, cannot have ' - '--renamed_tree or --old_link') - - if options.old_link and options.renamed_tree: - Usage(parser, '--old_link and --renamed_tree are incompatible options.') - - if options.tree_existed: - if not options.old_link and not options.renamed_tree: - Usage( - parser, 'If --tree_existed is True, then must have either ' - '--old_link or --renamed_tree') - - out_filename = 'cros_pkg/' + options.board + '_cleanup.sh' - - with open(out_filename, 'w', encoding='utf-8') as out_file: - out_file.write('#!/bin/bash\n\n') - # First, remove the 'new' soft link. - out_file.write('sudo rm /build/%s\n' % options.board) + """ + + parser = argparse.ArgumentParser() + parser.add_argument( + "--board", + dest="board", + required=True, + help="Chromeos board for packages/image.", + ) + + parser.add_argument( + "--old_tree_missing", + dest="tree_existed", + action="store_false", + help="Did /build/${BOARD} exist.", + default=True, + ) + + parser.add_argument( + "--renamed_tree", + dest="renamed_tree", + action="store_true", + help="Was /build/${BOARD} saved & renamed.", + default=False, + ) + + parser.add_argument( + "--old_link", + dest="old_link", + help=("The original build tree soft link."), + ) + + options = parser.parse_args(argv[1:]) + + if options.old_link or options.renamed_tree: + if not options.tree_existed: + Usage( + parser, + "If --tree_existed is False, cannot have " + "--renamed_tree or --old_link", + ) + + if options.old_link and options.renamed_tree: + Usage(parser, "--old_link and --renamed_tree are incompatible options.") + if options.tree_existed: - if options.renamed_tree: - # Old build tree existed and was a real tree, so it got - # renamed. Move the renamed tree back to the original tree. - out_file.write('sudo mv /build/%s.save /build/%s\n' % (options.board, - options.board)) - else: - # Old tree existed and was already a soft link. Re-create the - # original soft link. - original_link = options.old_link - if original_link[0] == "'": - original_link = original_link[1:] - if original_link[-1] == "'": - original_link = original_link[:-1] - out_file.write( - 'sudo ln -s %s /build/%s\n' % (original_link, options.board)) - out_file.write('\n') - # Remove common.sh file - out_file.write('rm common/common.sh\n') - - return 0 - - -if __name__ == '__main__': - retval = Main(sys.argv) - sys.exit(retval) + if not options.old_link and not options.renamed_tree: + Usage( + parser, + "If --tree_existed is True, then must have either " + "--old_link or --renamed_tree", + ) + + out_filename = "cros_pkg/" + options.board + "_cleanup.sh" + + with open(out_filename, "w", encoding="utf-8") as out_file: + out_file.write("#!/bin/bash\n\n") + # First, remove the 'new' soft link. + out_file.write("sudo rm /build/%s\n" % options.board) + if options.tree_existed: + if options.renamed_tree: + # Old build tree existed and was a real tree, so it got + # renamed. Move the renamed tree back to the original tree. + out_file.write( + "sudo mv /build/%s.save /build/%s\n" + % (options.board, options.board) + ) + else: + # Old tree existed and was already a soft link. Re-create the + # original soft link. + original_link = options.old_link + if original_link[0] == "'": + original_link = original_link[1:] + if original_link[-1] == "'": + original_link = original_link[:-1] + out_file.write( + "sudo ln -s %s /build/%s\n" % (original_link, options.board) + ) + out_file.write("\n") + # Remove common.sh file + out_file.write("rm common/common.sh\n") + + return 0 + + +if __name__ == "__main__": + retval = Main(sys.argv) + sys.exit(retval) diff --git a/binary_search_tool/cros_pkg/get_initial_items.sh b/binary_search_tool/cros_pkg/get_initial_items.sh index 49ca3d18..bc0fd2e6 100755 --- a/binary_search_tool/cros_pkg/get_initial_items.sh +++ b/binary_search_tool/cros_pkg/get_initial_items.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2015 Google Inc. All Rights Reserved. +# Copyright 2015 Google LLC # # This script is intended to be used by binary_search_state.py, as # part of the binary search triage on ChromeOS packages. This script @@ -13,4 +13,3 @@ source common/common.sh cd ${GOOD_BUILD}/packages find . -name "*.tbz2" - diff --git a/binary_search_tool/cros_pkg/setup.sh b/binary_search_tool/cros_pkg/setup.sh index ae31fa82..30a3a423 100755 --- a/binary_search_tool/cros_pkg/setup.sh +++ b/binary_search_tool/cros_pkg/setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2015 Google Inc. All Rights Reserved. +# Copyright 2015 Google LLC # # This script is part of the ChromeOS package binary search triage process. # It should be the first script called by the user, after the user has set up diff --git a/binary_search_tool/cros_pkg/switch_to_bad.sh b/binary_search_tool/cros_pkg/switch_to_bad.sh index 126425f4..b4156a0e 100755 --- a/binary_search_tool/cros_pkg/switch_to_bad.sh +++ b/binary_search_tool/cros_pkg/switch_to_bad.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2015 Google Inc. All Rights Reserved. +# Copyright 2015 Google LLC # # This script is intended to be used by binary_search_state.py, as # part of the binary search triage on ChromeOS packages. This script diff --git a/binary_search_tool/cros_pkg/switch_to_good.sh b/binary_search_tool/cros_pkg/switch_to_good.sh index a9095e99..5f7c2d77 100755 --- a/binary_search_tool/cros_pkg/switch_to_good.sh +++ b/binary_search_tool/cros_pkg/switch_to_good.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2015 Google Inc. All Rights Reserved. +# Copyright 2015 Google LLC # # This script is intended to be used by binary_search_state.py, as # part of the binary search triage on ChromeOS packages. This script diff --git a/binary_search_tool/cros_pkg/test_setup_usb.sh b/binary_search_tool/cros_pkg/test_setup_usb.sh index fec66f8e..54d0baa1 100755 --- a/binary_search_tool/cros_pkg/test_setup_usb.sh +++ b/binary_search_tool/cros_pkg/test_setup_usb.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This is a generic ChromeOS package/image test setup script. It is meant to # be used for the package bisection tool, in particular when there is a booting diff --git a/binary_search_tool/ndk/DO_BISECTION.sh b/binary_search_tool/ndk/DO_BISECTION.sh index 298d5747..e6eed765 100755 --- a/binary_search_tool/ndk/DO_BISECTION.sh +++ b/binary_search_tool/ndk/DO_BISECTION.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This is an example script to show users the steps for bisecting an NDK # application for Android. Our example is the Teapot app that comes bundled with diff --git a/binary_search_tool/ndk/boot_test.sh b/binary_search_tool/ndk/boot_test.sh index b8c34aa5..0b66ddfa 100755 --- a/binary_search_tool/ndk/boot_test.sh +++ b/binary_search_tool/ndk/boot_test.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script checks the android device to determine if the app is currently # running. For our specific test case we will be checking if the Teapot app diff --git a/binary_search_tool/ndk/get_initial_items.sh b/binary_search_tool/ndk/get_initial_items.sh index bc2d05cd..5dd3396d 100755 --- a/binary_search_tool/ndk/get_initial_items.sh +++ b/binary_search_tool/ndk/get_initial_items.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script is intended to be used by binary_search_state.py, as # part of the binary search triage on the Android NDK apps. This script @@ -9,4 +9,3 @@ # cat ${BISECT_DIR}/good/_LIST - diff --git a/binary_search_tool/ndk/switch_to_good.sh b/binary_search_tool/ndk/switch_to_good.sh index cb8d5fd9..c98de67c 100755 --- a/binary_search_tool/ndk/switch_to_good.sh +++ b/binary_search_tool/ndk/switch_to_good.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script is intended to be used by binary_search_state.py, as # part of the binary search triage on Android NDK apps. This script simply @@ -43,4 +43,3 @@ fi cat $OBJ_LIST_FILE | xargs rm exit 0 - diff --git a/binary_search_tool/ndk/test_setup.sh b/binary_search_tool/ndk/test_setup.sh index 477bcb21..8f3ce04e 100755 --- a/binary_search_tool/ndk/test_setup.sh +++ b/binary_search_tool/ndk/test_setup.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This is the setup script for generating and installing the ndk app. # diff --git a/binary_search_tool/pass_mapping.py b/binary_search_tool/pass_mapping.py index 2678fd6d..33c023a9 100644 --- a/binary_search_tool/pass_mapping.py +++ b/binary_search_tool/pass_mapping.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -15,20 +15,12 @@ pass_name = { # 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', + "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/run_bisect.py b/binary_search_tool/run_bisect.py index 249b9cf5..f54e00e1 100755 --- a/binary_search_tool/run_bisect.py +++ b/binary_search_tool/run_bisect.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """The unified package/object bisecting tool.""" -from __future__ import print_function import abc import argparse @@ -17,293 +16,336 @@ import sys from binary_search_tool import binary_search_state from binary_search_tool import common - from cros_utils import command_executer from cros_utils import logger class Bisector(object, metaclass=abc.ABCMeta): - """The abstract base class for Bisectors.""" - - def __init__(self, options, overrides=None): - """Constructor for Bisector abstract base class - - Args: - options: positional arguments for specific mode (board, remote, etc.) - overrides: optional dict of overrides for argument defaults - """ - self.options = options - self.overrides = overrides - if not overrides: - self.overrides = {} - self.logger = logger.GetLogger() - self.ce = command_executer.GetCommandExecuter() - - def _PrettyPrintArgs(self, args, overrides): - """Output arguments in a nice, human readable format - - Will print and log all arguments for the bisecting tool and make note of - which arguments have been overridden. - - Example output: - ./run_bisect.py package daisy 172.17.211.184 -I "" -t cros_pkg/my_test.sh - Performing ChromeOS Package bisection - Method Config: - board : daisy - remote : 172.17.211.184 - - Bisection Config: (* = overridden) - get_initial_items : cros_pkg/get_initial_items.sh - switch_to_good : cros_pkg/switch_to_good.sh - switch_to_bad : cros_pkg/switch_to_bad.sh - * test_setup_script : - * test_script : cros_pkg/my_test.sh - prune : True - noincremental : False - file_args : True - - Args: - args: The args to be given to binary_search_state.Run. This represents - how the bisection tool will run (with overridden arguments already - added in). - overrides: The dict of overriden arguments provided by the user. This is - provided so the user can be told which arguments were - overriden and with what value. - """ - # Output method config (board, remote, etc.) - options = vars(self.options) - out = '\nPerforming %s bisection\n' % self.method_name - out += 'Method Config:\n' - max_key_len = max([len(str(x)) for x in options.keys()]) - for key in sorted(options): - val = options[key] - key_str = str(key).rjust(max_key_len) - val_str = str(val) - out += ' %s : %s\n' % (key_str, val_str) - - # Output bisection config (scripts, prune, etc.) - out += '\nBisection Config: (* = overridden)\n' - max_key_len = max([len(str(x)) for x in args.keys()]) - # Print args in common._ArgsDict order - args_order = [x['dest'] for x in common.GetArgsDict().values()] - for key in sorted(args, key=args_order.index): - val = args[key] - key_str = str(key).rjust(max_key_len) - val_str = str(val) - changed_str = '*' if key in overrides else ' ' - - out += ' %s %s : %s\n' % (changed_str, key_str, val_str) - - out += '\n' - self.logger.LogOutput(out) - - def ArgOverride(self, args, overrides, pretty_print=True): - """Override arguments based on given overrides and provide nice output - - Args: - args: dict of arguments to be passed to binary_search_state.Run (runs - dict.update, causing args to be mutated). - overrides: dict of arguments to update args with - pretty_print: if True print out args/overrides to user in pretty format - """ - args.update(overrides) - if pretty_print: - self._PrettyPrintArgs(args, overrides) - - @abc.abstractmethod - def PreRun(self): - pass - - @abc.abstractmethod - def Run(self): - pass - - @abc.abstractmethod - def PostRun(self): - pass + """The abstract base class for Bisectors.""" + + def __init__(self, options, overrides=None): + """Constructor for Bisector abstract base class + + Args: + options: positional arguments for specific mode (board, remote, etc.) + overrides: optional dict of overrides for argument defaults + """ + self.options = options + self.overrides = overrides + if not overrides: + self.overrides = {} + self.logger = logger.GetLogger() + self.ce = command_executer.GetCommandExecuter() + + def _PrettyPrintArgs(self, args, overrides): + """Output arguments in a nice, human readable format + + Will print and log all arguments for the bisecting tool and make note of + which arguments have been overridden. + + Example output: + ./run_bisect.py package daisy 172.17.211.184 -I "" -t cros_pkg/my_test.sh + Performing ChromeOS Package bisection + Method Config: + board : daisy + remote : 172.17.211.184 + + Bisection Config: (* = overridden) + get_initial_items : cros_pkg/get_initial_items.sh + switch_to_good : cros_pkg/switch_to_good.sh + switch_to_bad : cros_pkg/switch_to_bad.sh + * test_setup_script : + * test_script : cros_pkg/my_test.sh + prune : True + noincremental : False + file_args : True + + Args: + args: The args to be given to binary_search_state.Run. This represents + how the bisection tool will run (with overridden arguments already + added in). + overrides: The dict of overriden arguments provided by the user. This is + provided so the user can be told which arguments were + overriden and with what value. + """ + # Output method config (board, remote, etc.) + options = vars(self.options) + out = "\nPerforming %s bisection\n" % self.method_name + out += "Method Config:\n" + max_key_len = max([len(str(x)) for x in options.keys()]) + for key in sorted(options): + val = options[key] + key_str = str(key).rjust(max_key_len) + val_str = str(val) + out += " %s : %s\n" % (key_str, val_str) + + # Output bisection config (scripts, prune, etc.) + out += "\nBisection Config: (* = overridden)\n" + max_key_len = max([len(str(x)) for x in args.keys()]) + # Print args in common._ArgsDict order + args_order = [x["dest"] for x in common.GetArgsDict().values()] + for key in sorted(args, key=args_order.index): + val = args[key] + key_str = str(key).rjust(max_key_len) + val_str = str(val) + changed_str = "*" if key in overrides else " " + + out += " %s %s : %s\n" % (changed_str, key_str, val_str) + + out += "\n" + self.logger.LogOutput(out) + + def ArgOverride(self, args, overrides, pretty_print=True): + """Override arguments based on given overrides and provide nice output + + Args: + args: dict of arguments to be passed to binary_search_state.Run (runs + dict.update, causing args to be mutated). + overrides: dict of arguments to update args with + pretty_print: if True print out args/overrides to user in pretty format + """ + args.update(overrides) + if pretty_print: + self._PrettyPrintArgs(args, overrides) + + @abc.abstractmethod + def PreRun(self): + pass + + @abc.abstractmethod + def Run(self): + pass + + @abc.abstractmethod + def PostRun(self): + pass class BisectPackage(Bisector): - """The class for package bisection steps.""" - - cros_pkg_setup = 'cros_pkg/setup.sh' - cros_pkg_cleanup = 'cros_pkg/%s_cleanup.sh' - - def __init__(self, options, overrides): - super(BisectPackage, self).__init__(options, overrides) - self.method_name = 'ChromeOS Package' - self.default_kwargs = { - 'get_initial_items': 'cros_pkg/get_initial_items.sh', - 'switch_to_good': 'cros_pkg/switch_to_good.sh', - 'switch_to_bad': 'cros_pkg/switch_to_bad.sh', - 'test_setup_script': 'cros_pkg/test_setup.sh', - 'test_script': 'cros_pkg/interactive_test.sh', - 'noincremental': False, - 'prune': True, - 'file_args': True - } - self.setup_cmd = ' '.join( - (self.cros_pkg_setup, self.options.board, self.options.remote)) - self.ArgOverride(self.default_kwargs, self.overrides) - - def PreRun(self): - ret, _, _ = self.ce.RunCommandWExceptionCleanup( - self.setup_cmd, print_to_console=True) - if ret: - self.logger.LogError('Package bisector setup failed w/ error %d' % ret) - return 1 - return 0 - - def Run(self): - return binary_search_state.Run(**self.default_kwargs) - - def PostRun(self): - cmd = self.cros_pkg_cleanup % self.options.board - ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True) - if ret: - self.logger.LogError('Package bisector cleanup failed w/ error %d' % ret) - return 1 - - self.logger.LogOutput(('Cleanup successful! To restore the bisection ' - 'environment run the following:\n' - ' cd %s; %s') % (os.getcwd(), self.setup_cmd)) - return 0 + """The class for package bisection steps.""" + + cros_pkg_setup = "cros_pkg/setup.sh" + cros_pkg_cleanup = "cros_pkg/%s_cleanup.sh" + + def __init__(self, options, overrides): + super(BisectPackage, self).__init__(options, overrides) + self.method_name = "ChromeOS Package" + self.default_kwargs = { + "get_initial_items": "cros_pkg/get_initial_items.sh", + "switch_to_good": "cros_pkg/switch_to_good.sh", + "switch_to_bad": "cros_pkg/switch_to_bad.sh", + "test_setup_script": "cros_pkg/test_setup.sh", + "test_script": "cros_pkg/interactive_test.sh", + "noincremental": False, + "prune": True, + "file_args": True, + } + self.setup_cmd = " ".join( + (self.cros_pkg_setup, self.options.board, self.options.remote) + ) + self.ArgOverride(self.default_kwargs, self.overrides) + + def PreRun(self): + ret, _, _ = self.ce.RunCommandWExceptionCleanup( + self.setup_cmd, print_to_console=True + ) + if ret: + self.logger.LogError( + "Package bisector setup failed w/ error %d" % ret + ) + return 1 + return 0 + + def Run(self): + return binary_search_state.Run(**self.default_kwargs) + + def PostRun(self): + cmd = self.cros_pkg_cleanup % self.options.board + ret, _, _ = self.ce.RunCommandWExceptionCleanup( + cmd, print_to_console=True + ) + if ret: + self.logger.LogError( + "Package bisector cleanup failed w/ error %d" % ret + ) + return 1 + + self.logger.LogOutput( + ( + "Cleanup successful! To restore the bisection " + "environment run the following:\n" + " cd %s; %s" + ) + % (os.getcwd(), self.setup_cmd) + ) + return 0 class BisectObject(Bisector): - """The class for object bisection steps.""" - - sysroot_wrapper_setup = 'sysroot_wrapper/setup.sh' - sysroot_wrapper_cleanup = 'sysroot_wrapper/cleanup.sh' - - def __init__(self, options, overrides): - super(BisectObject, self).__init__(options, overrides) - self.method_name = 'ChromeOS Object' - self.default_kwargs = { - 'get_initial_items': 'sysroot_wrapper/get_initial_items.sh', - 'switch_to_good': 'sysroot_wrapper/switch_to_good.sh', - 'switch_to_bad': 'sysroot_wrapper/switch_to_bad.sh', - 'test_setup_script': 'sysroot_wrapper/test_setup.sh', - 'test_script': 'sysroot_wrapper/interactive_test.sh', - 'noincremental': False, - 'prune': True, - 'file_args': True - } - self.options = options - if options.dir: - os.environ['BISECT_DIR'] = options.dir - self.options.dir = os.environ.get('BISECT_DIR', '/tmp/sysroot_bisect') - self.setup_cmd = ' '.join( - (self.sysroot_wrapper_setup, self.options.board, self.options.remote, - self.options.package, str(self.options.reboot).lower(), - shlex.quote(self.options.use_flags))) - - self.ArgOverride(self.default_kwargs, overrides) - - def PreRun(self): - ret, _, _ = self.ce.RunCommandWExceptionCleanup( - self.setup_cmd, print_to_console=True) - if ret: - self.logger.LogError('Object bisector setup failed w/ error %d' % ret) - return 1 - - os.environ['BISECT_STAGE'] = 'TRIAGE' - return 0 - - def Run(self): - return binary_search_state.Run(**self.default_kwargs) - - def PostRun(self): - cmd = self.sysroot_wrapper_cleanup - ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True) - if ret: - self.logger.LogError('Object bisector cleanup failed w/ error %d' % ret) - return 1 - self.logger.LogOutput(('Cleanup successful! To restore the bisection ' - 'environment run the following:\n' - ' cd %s; %s') % (os.getcwd(), self.setup_cmd)) - return 0 + """The class for object bisection steps.""" + + sysroot_wrapper_setup = "sysroot_wrapper/setup.sh" + sysroot_wrapper_cleanup = "sysroot_wrapper/cleanup.sh" + + def __init__(self, options, overrides): + super(BisectObject, self).__init__(options, overrides) + self.method_name = "ChromeOS Object" + self.default_kwargs = { + "get_initial_items": "sysroot_wrapper/get_initial_items.sh", + "switch_to_good": "sysroot_wrapper/switch_to_good.sh", + "switch_to_bad": "sysroot_wrapper/switch_to_bad.sh", + "test_setup_script": "sysroot_wrapper/test_setup.sh", + "test_script": "sysroot_wrapper/interactive_test.sh", + "noincremental": False, + "prune": True, + "file_args": True, + } + self.options = options + if options.dir: + os.environ["BISECT_DIR"] = options.dir + self.options.dir = os.environ.get("BISECT_DIR", "/tmp/sysroot_bisect") + self.setup_cmd = " ".join( + ( + self.sysroot_wrapper_setup, + self.options.board, + self.options.remote, + self.options.package, + str(self.options.reboot).lower(), + shlex.quote(self.options.use_flags), + ) + ) + + self.ArgOverride(self.default_kwargs, overrides) + + def PreRun(self): + ret, _, _ = self.ce.RunCommandWExceptionCleanup( + self.setup_cmd, print_to_console=True + ) + if ret: + self.logger.LogError( + "Object bisector setup failed w/ error %d" % ret + ) + return 1 + + os.environ["BISECT_STAGE"] = "TRIAGE" + return 0 + + def Run(self): + return binary_search_state.Run(**self.default_kwargs) + + def PostRun(self): + cmd = self.sysroot_wrapper_cleanup + ret, _, _ = self.ce.RunCommandWExceptionCleanup( + cmd, print_to_console=True + ) + if ret: + self.logger.LogError( + "Object bisector cleanup failed w/ error %d" % ret + ) + return 1 + self.logger.LogOutput( + ( + "Cleanup successful! To restore the bisection " + "environment run the following:\n" + " cd %s; %s" + ) + % (os.getcwd(), self.setup_cmd) + ) + return 0 class BisectAndroid(Bisector): - """The class for Android bisection steps.""" - - android_setup = 'android/setup.sh' - android_cleanup = 'android/cleanup.sh' - default_dir = os.path.expanduser('~/ANDROID_BISECT') - - def __init__(self, options, overrides): - super(BisectAndroid, self).__init__(options, overrides) - self.method_name = 'Android' - self.default_kwargs = { - 'get_initial_items': 'android/get_initial_items.sh', - 'switch_to_good': 'android/switch_to_good.sh', - 'switch_to_bad': 'android/switch_to_bad.sh', - 'test_setup_script': 'android/test_setup.sh', - 'test_script': 'android/interactive_test.sh', - 'prune': True, - 'file_args': True, - 'noincremental': False, - } - self.options = options - if options.dir: - os.environ['BISECT_DIR'] = options.dir - self.options.dir = os.environ.get('BISECT_DIR', self.default_dir) - - num_jobs = "NUM_JOBS='%s'" % self.options.num_jobs - device_id = '' - if self.options.device_id: - device_id = "ANDROID_SERIAL='%s'" % self.options.device_id - - self.setup_cmd = ' '.join( - (num_jobs, device_id, self.android_setup, self.options.android_src)) - - self.ArgOverride(self.default_kwargs, overrides) - - def PreRun(self): - ret, _, _ = self.ce.RunCommandWExceptionCleanup( - self.setup_cmd, print_to_console=True) - if ret: - self.logger.LogError('Android bisector setup failed w/ error %d' % ret) - return 1 - - os.environ['BISECT_STAGE'] = 'TRIAGE' - return 0 - - def Run(self): - return binary_search_state.Run(**self.default_kwargs) - - def PostRun(self): - cmd = self.android_cleanup - ret, _, _ = self.ce.RunCommandWExceptionCleanup(cmd, print_to_console=True) - if ret: - self.logger.LogError('Android bisector cleanup failed w/ error %d' % ret) - return 1 - self.logger.LogOutput(('Cleanup successful! To restore the bisection ' - 'environment run the following:\n' - ' cd %s; %s') % (os.getcwd(), self.setup_cmd)) - return 0 + """The class for Android bisection steps.""" + + android_setup = "android/setup.sh" + android_cleanup = "android/cleanup.sh" + default_dir = os.path.expanduser("~/ANDROID_BISECT") + + def __init__(self, options, overrides): + super(BisectAndroid, self).__init__(options, overrides) + self.method_name = "Android" + self.default_kwargs = { + "get_initial_items": "android/get_initial_items.sh", + "switch_to_good": "android/switch_to_good.sh", + "switch_to_bad": "android/switch_to_bad.sh", + "test_setup_script": "android/test_setup.sh", + "test_script": "android/interactive_test.sh", + "prune": True, + "file_args": True, + "noincremental": False, + } + self.options = options + if options.dir: + os.environ["BISECT_DIR"] = options.dir + self.options.dir = os.environ.get("BISECT_DIR", self.default_dir) + + num_jobs = "NUM_JOBS='%s'" % self.options.num_jobs + device_id = "" + if self.options.device_id: + device_id = "ANDROID_SERIAL='%s'" % self.options.device_id + + self.setup_cmd = " ".join( + (num_jobs, device_id, self.android_setup, self.options.android_src) + ) + + self.ArgOverride(self.default_kwargs, overrides) + + def PreRun(self): + ret, _, _ = self.ce.RunCommandWExceptionCleanup( + self.setup_cmd, print_to_console=True + ) + if ret: + self.logger.LogError( + "Android bisector setup failed w/ error %d" % ret + ) + return 1 + + os.environ["BISECT_STAGE"] = "TRIAGE" + return 0 + + def Run(self): + return binary_search_state.Run(**self.default_kwargs) + + def PostRun(self): + cmd = self.android_cleanup + ret, _, _ = self.ce.RunCommandWExceptionCleanup( + cmd, print_to_console=True + ) + if ret: + self.logger.LogError( + "Android bisector cleanup failed w/ error %d" % ret + ) + return 1 + self.logger.LogOutput( + ( + "Cleanup successful! To restore the bisection " + "environment run the following:\n" + " cd %s; %s" + ) + % (os.getcwd(), self.setup_cmd) + ) + return 0 def Run(bisector): - log = logger.GetLogger() + log = logger.GetLogger() - log.LogOutput('Setting up Bisection tool') - ret = bisector.PreRun() - if ret: - return ret + log.LogOutput("Setting up Bisection tool") + ret = bisector.PreRun() + if ret: + return ret - log.LogOutput('Running Bisection tool') - ret = bisector.Run() - if ret: - return ret + log.LogOutput("Running Bisection tool") + ret = bisector.Run() + if ret: + return ret - log.LogOutput('Cleaning up Bisection tool') - ret = bisector.PostRun() - if ret: - return ret + log.LogOutput("Cleaning up Bisection tool") + ret = bisector.PostRun() + if ret: + return ret - return 0 + return 0 _HELP_EPILOG = """ @@ -318,92 +360,113 @@ See below for full override argument reference: def Main(argv): - override_parser = argparse.ArgumentParser( - add_help=False, - argument_default=argparse.SUPPRESS, - usage='run_bisect.py {mode} [options]') - common.BuildArgParser(override_parser, override=True) - - epilog = _HELP_EPILOG + override_parser.format_help() - parser = argparse.ArgumentParser( - epilog=epilog, formatter_class=RawTextHelpFormatter) - subparsers = parser.add_subparsers( - title='Bisect mode', - description=('Which bisection method to ' - 'use. Each method has ' - 'specific setup and ' - 'arguments. Please consult ' - 'the README for more ' - 'information.')) - - parser_package = subparsers.add_parser('package') - parser_package.add_argument('board', help='Board to target') - parser_package.add_argument('remote', help='Remote machine to test on') - parser_package.set_defaults(handler=BisectPackage) - - parser_object = subparsers.add_parser('object') - parser_object.add_argument('board', help='Board to target') - parser_object.add_argument('remote', help='Remote machine to test on') - parser_object.add_argument('package', help='Package to emerge and test') - parser_object.add_argument( - '--use_flags', - required=False, - default='', - help='Use flags passed to emerge') - parser_object.add_argument( - '--noreboot', - action='store_false', - dest='reboot', - help='Do not reboot after updating the package (default: False)') - parser_object.add_argument( - '--dir', - help=('Bisection directory to use, sets ' - '$BISECT_DIR if provided. Defaults to ' - 'current value of $BISECT_DIR (or ' - '/tmp/sysroot_bisect if $BISECT_DIR is ' - 'empty).')) - parser_object.set_defaults(handler=BisectObject) - - parser_android = subparsers.add_parser('android') - parser_android.add_argument('android_src', help='Path to android source tree') - parser_android.add_argument( - '--dir', - help=('Bisection directory to use, sets ' - '$BISECT_DIR if provided. Defaults to ' - 'current value of $BISECT_DIR (or ' - '~/ANDROID_BISECT/ if $BISECT_DIR is ' - 'empty).')) - parser_android.add_argument( - '-j', - '--num_jobs', - type=int, - default=1, - help=('Number of jobs that make and various ' - 'scripts for bisector can spawn. Setting ' - 'this value too high can freeze up your ' - 'machine!')) - parser_android.add_argument( - '--device_id', - default='', - help=('Device id for device used for testing. ' - 'Use this if you have multiple Android ' - 'devices plugged into your machine.')) - parser_android.set_defaults(handler=BisectAndroid) - - options, remaining = parser.parse_known_args(argv) - if remaining: - overrides = override_parser.parse_args(remaining) - overrides = vars(overrides) - else: - overrides = {} - - subcmd = options.handler - del options.handler - - bisector = subcmd(options, overrides) - return Run(bisector) - - -if __name__ == '__main__': - os.chdir(os.path.dirname(__file__)) - sys.exit(Main(sys.argv[1:])) + override_parser = argparse.ArgumentParser( + add_help=False, + argument_default=argparse.SUPPRESS, + usage="run_bisect.py {mode} [options]", + ) + common.BuildArgParser(override_parser, override=True) + + epilog = _HELP_EPILOG + override_parser.format_help() + parser = argparse.ArgumentParser( + epilog=epilog, formatter_class=RawTextHelpFormatter + ) + subparsers = parser.add_subparsers( + title="Bisect mode", + description=( + "Which bisection method to " + "use. Each method has " + "specific setup and " + "arguments. Please consult " + "the README for more " + "information." + ), + ) + + parser_package = subparsers.add_parser("package") + parser_package.add_argument("board", help="Board to target") + parser_package.add_argument("remote", help="Remote machine to test on") + parser_package.set_defaults(handler=BisectPackage) + + parser_object = subparsers.add_parser("object") + parser_object.add_argument("board", help="Board to target") + parser_object.add_argument("remote", help="Remote machine to test on") + parser_object.add_argument("package", help="Package to emerge and test") + parser_object.add_argument( + "--use_flags", + required=False, + default="", + help="Use flags passed to emerge", + ) + parser_object.add_argument( + "--noreboot", + action="store_false", + dest="reboot", + help="Do not reboot after updating the package (default: False)", + ) + parser_object.add_argument( + "--dir", + help=( + "Bisection directory to use, sets " + "$BISECT_DIR if provided. Defaults to " + "current value of $BISECT_DIR (or " + "/tmp/sysroot_bisect if $BISECT_DIR is " + "empty)." + ), + ) + parser_object.set_defaults(handler=BisectObject) + + parser_android = subparsers.add_parser("android") + parser_android.add_argument( + "android_src", help="Path to android source tree" + ) + parser_android.add_argument( + "--dir", + help=( + "Bisection directory to use, sets " + "$BISECT_DIR if provided. Defaults to " + "current value of $BISECT_DIR (or " + "~/ANDROID_BISECT/ if $BISECT_DIR is " + "empty)." + ), + ) + parser_android.add_argument( + "-j", + "--num_jobs", + type=int, + default=1, + help=( + "Number of jobs that make and various " + "scripts for bisector can spawn. Setting " + "this value too high can freeze up your " + "machine!" + ), + ) + parser_android.add_argument( + "--device_id", + default="", + help=( + "Device id for device used for testing. " + "Use this if you have multiple Android " + "devices plugged into your machine." + ), + ) + parser_android.set_defaults(handler=BisectAndroid) + + options, remaining = parser.parse_known_args(argv) + if remaining: + overrides = override_parser.parse_args(remaining) + overrides = vars(overrides) + else: + overrides = {} + + subcmd = options.handler + del options.handler + + bisector = subcmd(options, overrides) + return Run(bisector) + + +if __name__ == "__main__": + os.chdir(os.path.dirname(__file__)) + sys.exit(Main(sys.argv[1:])) diff --git a/binary_search_tool/run_bisect_tests.py b/binary_search_tool/run_bisect_tests.py index 9172d678..ca7077d3 100755 --- a/binary_search_tool/run_bisect_tests.py +++ b/binary_search_tool/run_bisect_tests.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Run full bisection test.""" -from __future__ import print_function import argparse import os @@ -14,81 +13,88 @@ import sys from cros_utils import command_executer -TEST_DIR = 'full_bisect_test' -DEFAULT_BISECT_DIR = '/tmp/sysroot_bisect' +TEST_DIR = "full_bisect_test" +DEFAULT_BISECT_DIR = "/tmp/sysroot_bisect" -def populate_good_files(top_dir, ce, bisect_dir=DEFAULT_BISECT_DIR): - # 'make clean' - work_dir = os.path.join(top_dir, TEST_DIR, 'work') - cmd = 'rm -f %s/*.o' % work_dir - status = ce.RunCommand(cmd) - if status != 0: - print('Error trying to clean out work directory: %s' % cmd) - return status - # set up the 'good' source files - script = os.path.join(top_dir, TEST_DIR, 'make_sources_good.sh') - status = ce.RunCommand(script) - if status != 0: - print('Error setting up "good" source files: %s' % script) +def populate_good_files(top_dir, ce, bisect_dir=DEFAULT_BISECT_DIR): + # 'make clean' + work_dir = os.path.join(top_dir, TEST_DIR, "work") + cmd = "rm -f %s/*.o" % work_dir + status = ce.RunCommand(cmd) + if status != 0: + print("Error trying to clean out work directory: %s" % cmd) + return status + + # set up the 'good' source files + script = os.path.join(top_dir, TEST_DIR, "make_sources_good.sh") + status = ce.RunCommand(script) + if status != 0: + print('Error setting up "good" source files: %s' % script) + return status + + export_bisect = "export BISECT_DIR=%s; " % bisect_dir + # build the good source files + script_path = os.path.join(top_dir, TEST_DIR) + if os.path.exists("/usr/bin/x86_64-cros-linux-gnu-gcc"): + build_script = "chromeos_build.sh" + else: + build_script = "build.sh" + cmd = "%s export BISECT_STAGE=POPULATE_GOOD; pushd %s; ./%s; popd" % ( + export_bisect, + script_path, + build_script, + ) + status = ce.RunCommand(cmd) return status - export_bisect = 'export BISECT_DIR=%s; ' % bisect_dir - # build the good source files - script_path = os.path.join(top_dir, TEST_DIR) - if os.path.exists('/usr/bin/x86_64-cros-linux-gnu-gcc'): - build_script = 'chromeos_build.sh' - else: - build_script = 'build.sh' - cmd = ('%s export BISECT_STAGE=POPULATE_GOOD; pushd %s; ./%s; popd' % - (export_bisect, script_path, build_script)) - status = ce.RunCommand(cmd) - return status - def populate_bad_files(top_dir, ce, bisect_dir=DEFAULT_BISECT_DIR): - # 'make clean' - work_dir = os.path.join(top_dir, TEST_DIR, 'work') - cmd = 'rm -f %s/*.o' % work_dir - status = ce.RunCommand(cmd) - if status != 0: - print('Error trying to clean out work directory: %s' % cmd) + # 'make clean' + work_dir = os.path.join(top_dir, TEST_DIR, "work") + cmd = "rm -f %s/*.o" % work_dir + status = ce.RunCommand(cmd) + if status != 0: + print("Error trying to clean out work directory: %s" % cmd) + return status + + # set up the 'bad' source files + script = os.path.join(top_dir, TEST_DIR, "make_sources_bad.sh") + status = ce.RunCommand(script) + if status != 0: + print('Error setting up "bad" source files: %s' % script) + return status + + export_bisect = "export BISECT_DIR=%s; " % bisect_dir + # build the bad source files + script_path = os.path.join(top_dir, TEST_DIR) + if os.path.exists("/usr/bin/x86_64-cros-linux-gnu-gcc"): + build_script = "chromeos_build.sh" + else: + build_script = "build.sh" + cmd = "%s export BISECT_STAGE=POPULATE_BAD; pushd %s; ./%s ; popd" % ( + export_bisect, + script_path, + build_script, + ) + status = ce.RunCommand(cmd) return status - # set up the 'bad' source files - script = os.path.join(top_dir, TEST_DIR, 'make_sources_bad.sh') - status = ce.RunCommand(script) - if status != 0: - print('Error setting up "bad" source files: %s' % script) - return status - - export_bisect = 'export BISECT_DIR=%s; ' % bisect_dir - # build the bad source files - script_path = os.path.join(top_dir, TEST_DIR) - if os.path.exists('/usr/bin/x86_64-cros-linux-gnu-gcc'): - build_script = 'chromeos_build.sh' - else: - build_script = 'build.sh' - cmd = ('%s export BISECT_STAGE=POPULATE_BAD; pushd %s; ./%s ; popd' % - (export_bisect, script_path, build_script)) - status = ce.RunCommand(cmd) - return status - def run_main_bisection_test(top_dir, ce): - test_script = os.path.join(top_dir, TEST_DIR, 'main-bisect-test.sh') - status = ce.RunCommand(test_script) - return status + test_script = os.path.join(top_dir, TEST_DIR, "main-bisect-test.sh") + status = ce.RunCommand(test_script) + return status def verify_compiler_and_wrapper(): - # We don't need to do any special setup if running inside a ChromeOS - # chroot. - if os.path.exists('/usr/bin/x86_64-cros-linux-gnu-gcc'): - return True + # We don't need to do any special setup if running inside a ChromeOS + # chroot. + if os.path.exists("/usr/bin/x86_64-cros-linux-gnu-gcc"): + return True - message = """ + message = """ *** IMPORTANT --- READ THIS CAREFULLY!! *** This test uses the command 'gcc' to compile the good/bad versions of the @@ -100,78 +106,93 @@ thing". Is your compiler wrapper properly set up? [Y/n] """ - print(message) - inp = sys.stdin.readline() - inp = inp.strip() - inp = inp.lower() - return not inp or inp == 'y' or inp == 'yes' + print(message) + inp = sys.stdin.readline() + inp = inp.strip() + inp = inp.lower() + return not inp or inp == "y" or inp == "yes" def Main(argv): - parser = argparse.ArgumentParser() - parser.add_argument( - '--dir', - dest='directory', - help='Bisection work tree, where good & bad object ' - 'files go. Default is /tmp/sysroot_bisect') - - options = parser.parse_args(argv) - - # Make sure the compiler wrapper & soft links are properly set up. - wrapper_is_setup = verify_compiler_and_wrapper() - if not wrapper_is_setup: - print('Exiting now. Please re-run after you have set up the compiler ' - 'wrapper.') - return 0 - - # Make sure we're in the correct directory for running this test. - cwd = os.getcwd() - if not os.path.exists(os.path.join(cwd, 'full_bisect_test')): - print('Error: Wrong directory. This script must be run from the top level' - ' of the binary_search_tool tree (under toolchain_utils).') - return 1 - - ce = command_executer.GetCommandExecuter() - bisect_dir = options.directory - if not bisect_dir: - bisect_dir = DEFAULT_BISECT_DIR - - # Make sure BISECT_DIR is clean - if os.path.exists(bisect_dir): - cmd = 'rm -Rf %s/*' % bisect_dir - retv = ce.RunCommand(cmd) + parser = argparse.ArgumentParser() + parser.add_argument( + "--dir", + dest="directory", + help="Bisection work tree, where good & bad object " + "files go. Default is /tmp/sysroot_bisect", + ) + + options = parser.parse_args(argv) + + # Make sure the compiler wrapper & soft links are properly set up. + wrapper_is_setup = verify_compiler_and_wrapper() + if not wrapper_is_setup: + print( + "Exiting now. Please re-run after you have set up the compiler " + "wrapper." + ) + return 0 + + # Make sure we're in the correct directory for running this test. + cwd = os.getcwd() + if not os.path.exists(os.path.join(cwd, "full_bisect_test")): + print( + "Error: Wrong directory. This script must be run from the top level" + " of the binary_search_tool tree (under toolchain_utils)." + ) + return 1 + + ce = command_executer.GetCommandExecuter() + bisect_dir = options.directory + if not bisect_dir: + bisect_dir = DEFAULT_BISECT_DIR + + # Make sure BISECT_DIR is clean + if os.path.exists(bisect_dir): + cmd = "rm -Rf %s/*" % bisect_dir + retv = ce.RunCommand(cmd) + if retv != 0: + return retv + + retv = populate_good_files(cwd, ce, bisect_dir) if retv != 0: - return retv - - retv = populate_good_files(cwd, ce, bisect_dir) - if retv != 0: - return retv + return retv - retv = populate_bad_files(cwd, ce, bisect_dir) - if retv != 0: + retv = populate_bad_files(cwd, ce, bisect_dir) + if retv != 0: + return retv + + # Set up good/bad work soft links + cmd = "rm -f %s/%s/good-objects; ln -s %s/good %s/%s/good-objects" % ( + cwd, + TEST_DIR, + bisect_dir, + cwd, + TEST_DIR, + ) + + status = ce.RunCommand(cmd) + if status != 0: + print("Error executing: %s; exiting now." % cmd) + return status + + cmd = "rm -f %s/%s/bad-objects; ln -s %s/bad %s/%s/bad-objects" % ( + cwd, + TEST_DIR, + bisect_dir, + cwd, + TEST_DIR, + ) + + status = ce.RunCommand(cmd) + if status != 0: + print("Error executing: %s; exiting now." % cmd) + return status + + retv = run_main_bisection_test(cwd, ce) return retv - # Set up good/bad work soft links - cmd = ('rm -f %s/%s/good-objects; ln -s %s/good %s/%s/good-objects' % - (cwd, TEST_DIR, bisect_dir, cwd, TEST_DIR)) - - status = ce.RunCommand(cmd) - if status != 0: - print('Error executing: %s; exiting now.' % cmd) - return status - - cmd = ('rm -f %s/%s/bad-objects; ln -s %s/bad %s/%s/bad-objects' % - (cwd, TEST_DIR, bisect_dir, cwd, TEST_DIR)) - - status = ce.RunCommand(cmd) - if status != 0: - print('Error executing: %s; exiting now.' % cmd) - return status - - retv = run_main_bisection_test(cwd, ce) - return retv - -if __name__ == '__main__': - retval = Main(sys.argv[1:]) - sys.exit(retval) +if __name__ == "__main__": + retval = Main(sys.argv[1:]) + sys.exit(retval) diff --git a/binary_search_tool/sysroot_wrapper/cleanup.sh b/binary_search_tool/sysroot_wrapper/cleanup.sh index 5066d638..b3ae2dd9 100755 --- a/binary_search_tool/sysroot_wrapper/cleanup.sh +++ b/binary_search_tool/sysroot_wrapper/cleanup.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2016 Google Inc. All Rights Reserved. +# Copyright 2016 Google LLC # # This script is part of the ChromeOS object binary search triage process. # It should be the last script called by the user, after the user has diff --git a/binary_search_tool/sysroot_wrapper/interactive_test_host.sh b/binary_search_tool/sysroot_wrapper/interactive_test_host.sh index 58adffc0..bd84936c 100755 --- a/binary_search_tool/sysroot_wrapper/interactive_test_host.sh +++ b/binary_search_tool/sysroot_wrapper/interactive_test_host.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2017 Google Inc. All Rights Reserved. +# Copyright 2017 Google LLC # # This script is intended to be used by binary_search_state.py, as # part of the binary search triage on ChromeOS package and object files for a diff --git a/binary_search_tool/sysroot_wrapper/setup.sh b/binary_search_tool/sysroot_wrapper/setup.sh index 6b9b48f1..f9ecb0ea 100755 --- a/binary_search_tool/sysroot_wrapper/setup.sh +++ b/binary_search_tool/sysroot_wrapper/setup.sh @@ -1,6 +1,6 @@ #!/bin/bash -u # -# Copyright 2021 The Chromium OS Authors. All rights reserved. +# Copyright 2021 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # diff --git a/binary_search_tool/sysroot_wrapper/test_setup_host.sh b/binary_search_tool/sysroot_wrapper/test_setup_host.sh index b5169eee..e61bc367 100755 --- a/binary_search_tool/sysroot_wrapper/test_setup_host.sh +++ b/binary_search_tool/sysroot_wrapper/test_setup_host.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Copyright 2017 Google Inc. All Rights Reserved. +# Copyright 2017 Google LLC # # This is a generic ChromeOS package/image test setup script. It is meant to # be used for either the object file or package bisection tools. This script diff --git a/binary_search_tool/sysroot_wrapper/testing_test.py b/binary_search_tool/sysroot_wrapper/testing_test.py index b5ceec1f..af884be9 100755 --- a/binary_search_tool/sysroot_wrapper/testing_test.py +++ b/binary_search_tool/sysroot_wrapper/testing_test.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -11,32 +11,35 @@ chromeos-chrome built for a daisy board, if you are using another package you will need to change the base_path accordingly. """ -from __future__ import print_function import subprocess import sys import os -base_path = ('/var/cache/chromeos-chrome/chrome-src-internal/src/out_daisy/' - 'Release/obj/') +base_path = ( + "/var/cache/chromeos-chrome/chrome-src-internal/src/out_daisy/" + "Release/obj/" +) bad_files = [ - os.path.join(base_path, 'base/base.cpu.o'), - os.path.join(base_path, 'base/base.version.o'), - os.path.join(base_path, 'apps/apps.launcher.o') + os.path.join(base_path, "base/base.cpu.o"), + os.path.join(base_path, "base/base.version.o"), + os.path.join(base_path, "apps/apps.launcher.o"), ] -bisect_dir = os.environ.get('BISECT_DIR', '/tmp/sysroot_bisect') +bisect_dir = os.environ.get("BISECT_DIR", "/tmp/sysroot_bisect") def Main(_): - for test_file in bad_files: - test_file = test_file.strip() - cmd = ['grep', test_file, os.path.join(bisect_dir, 'BAD_SET')] - ret = subprocess.call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if not ret: - return 1 - return 0 - - -if __name__ == '__main__': - sys.exit(Main(sys.argv[1:])) + for test_file in bad_files: + test_file = test_file.strip() + cmd = ["grep", test_file, os.path.join(bisect_dir, "BAD_SET")] + ret = subprocess.call( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + if not ret: + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(Main(sys.argv[1:])) diff --git a/binary_search_tool/test/__init__.py b/binary_search_tool/test/__init__.py index 76500def..6e3ade4a 100644 --- a/binary_search_tool/test/__init__.py +++ b/binary_search_tool/test/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/binary_search_tool/test/binary_search_tool_test.py b/binary_search_tool/test/binary_search_tool_test.py index 6f5b514e..a79c9a1d 100755 --- a/binary_search_tool/test/binary_search_tool_test.py +++ b/binary_search_tool/test/binary_search_tool_test.py @@ -1,15 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # 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 division -from __future__ import print_function -__author__ = 'shenhan@google.com (Han Shen)' +__author__ = "shenhan@google.com (Han Shen)" import os import random @@ -25,545 +23,597 @@ from binary_search_tool.test import gen_obj def GenObj(): - obj_num = random.randint(100, 1000) - bad_obj_num = random.randint(obj_num // 100, obj_num // 20) - if bad_obj_num == 0: - bad_obj_num = 1 - gen_obj.Main(['--obj_num', str(obj_num), '--bad_obj_num', str(bad_obj_num)]) + obj_num = random.randint(100, 1000) + bad_obj_num = random.randint(obj_num // 100, obj_num // 20) + if bad_obj_num == 0: + bad_obj_num = 1 + gen_obj.Main(["--obj_num", str(obj_num), "--bad_obj_num", str(bad_obj_num)]) def CleanObj(): - os.remove(common.OBJECTS_FILE) - os.remove(common.WORKING_SET_FILE) - print('Deleted "{0}" and "{1}"'.format(common.OBJECTS_FILE, - common.WORKING_SET_FILE)) + os.remove(common.OBJECTS_FILE) + os.remove(common.WORKING_SET_FILE) + print( + 'Deleted "{0}" and "{1}"'.format( + common.OBJECTS_FILE, common.WORKING_SET_FILE + ) + ) class BisectTest(unittest.TestCase): - """Tests for run_bisect.py""" - - def setUp(self): - with open('./is_setup', 'w', encoding='utf-8'): - pass - - try: - os.remove(binary_search_state.STATE_FILE) - except OSError: - pass - - def tearDown(self): - try: - os.remove('./is_setup') - os.remove(os.readlink(binary_search_state.STATE_FILE)) - os.remove(binary_search_state.STATE_FILE) - except OSError: - pass - - class FullBisector(run_bisect.Bisector): - """Test bisector to test run_bisect.py with""" - - def __init__(self, options, overrides): - super(BisectTest.FullBisector, self).__init__(options, overrides) - - def PreRun(self): - GenObj() - return 0 - - def Run(self): - return binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good.py', - switch_to_bad='./switch_to_bad.py', - test_script='./is_good.py', - prune=True, - file_args=True) - - def PostRun(self): - CleanObj() - return 0 - - def test_full_bisector(self): - ret = run_bisect.Run(self.FullBisector({}, {})) - self.assertEqual(ret, 0) - self.assertFalse(os.path.exists(common.OBJECTS_FILE)) - self.assertFalse(os.path.exists(common.WORKING_SET_FILE)) - - def check_output(self): - _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( - ('grep "Bad items are: " logs/binary_search_tool_test.py.out | ' - 'tail -n1')) - ls = out.splitlines() - self.assertEqual(len(ls), 1) - line = ls[0] - - _, _, bad_ones = line.partition('Bad items are: ') - bad_ones = bad_ones.split() - expected_result = common.ReadObjectsFile() - - # Reconstruct objects file from bad_ones and compare - actual_result = [0] * len(expected_result) - for bad_obj in bad_ones: - actual_result[int(bad_obj)] = 1 - - self.assertEqual(actual_result, expected_result) + """Tests for run_bisect.py""" + + def setUp(self): + with open("./is_setup", "w", encoding="utf-8"): + pass + + try: + os.remove(binary_search_state.STATE_FILE) + except OSError: + pass + + def tearDown(self): + try: + os.remove("./is_setup") + os.remove(os.readlink(binary_search_state.STATE_FILE)) + os.remove(binary_search_state.STATE_FILE) + except OSError: + pass + + class FullBisector(run_bisect.Bisector): + """Test bisector to test run_bisect.py with""" + + def __init__(self, options, overrides): + super(BisectTest.FullBisector, self).__init__(options, overrides) + + def PreRun(self): + GenObj() + return 0 + + def Run(self): + return binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good.py", + switch_to_bad="./switch_to_bad.py", + test_script="./is_good.py", + prune=True, + file_args=True, + ) + + def PostRun(self): + CleanObj() + return 0 + + def test_full_bisector(self): + ret = run_bisect.Run(self.FullBisector({}, {})) + self.assertEqual(ret, 0) + self.assertFalse(os.path.exists(common.OBJECTS_FILE)) + self.assertFalse(os.path.exists(common.WORKING_SET_FILE)) + + def check_output(self): + _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( + ( + 'grep "Bad items are: " logs/binary_search_tool_test.py.out | ' + "tail -n1" + ) + ) + ls = out.splitlines() + self.assertEqual(len(ls), 1) + line = ls[0] + + _, _, bad_ones = line.partition("Bad items are: ") + bad_ones = bad_ones.split() + expected_result = common.ReadObjectsFile() + + # Reconstruct objects file from bad_ones and compare + actual_result = [0] * len(expected_result) + for bad_obj in bad_ones: + actual_result[int(bad_obj)] = 1 + + self.assertEqual(actual_result, expected_result) class BisectingUtilsTest(unittest.TestCase): - """Tests for bisecting tool.""" - - def setUp(self): - """Generate [100-1000] object files, and 1-5% of which are bad ones.""" - GenObj() - - with open('./is_setup', 'w', encoding='utf-8'): - pass - - try: - os.remove(binary_search_state.STATE_FILE) - except OSError: - pass - - def tearDown(self): - """Cleanup temp files.""" - CleanObj() - - try: - os.remove(os.readlink(binary_search_state.STATE_FILE)) - except OSError: - pass - - cleanup_list = [ - './is_setup', binary_search_state.STATE_FILE, 'noinc_prune_bad', - 'noinc_prune_good', './cmd_script.sh' - ] - for f in cleanup_list: - if os.path.exists(f): - os.remove(f) - - def runTest(self): - ret = binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good.py', - switch_to_bad='./switch_to_bad.py', - test_script='./is_good.py', - prune=True, - file_args=True) - self.assertEqual(ret, 0) - self.check_output() - - def test_arg_parse(self): - args = [ - '--get_initial_items', './gen_init_list.py', '--switch_to_good', - './switch_to_good.py', '--switch_to_bad', './switch_to_bad.py', - '--test_script', './is_good.py', '--prune', '--file_args' - ] - ret = binary_search_state.Main(args) - self.assertEqual(ret, 0) - self.check_output() - - def test_test_setup_script(self): - os.remove('./is_setup') - with self.assertRaises(AssertionError): - ret = binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good.py', - switch_to_bad='./switch_to_bad.py', - test_script='./is_good.py', - prune=True, - file_args=True) - - ret = binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good.py', - switch_to_bad='./switch_to_bad.py', - test_script='./is_good.py', - test_setup_script='./test_setup.py', - prune=True, - file_args=True) - self.assertEqual(ret, 0) - self.check_output() - - def test_bad_test_setup_script(self): - with self.assertRaises(AssertionError): - binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good.py', - switch_to_bad='./switch_to_bad.py', - test_script='./is_good.py', - test_setup_script='./test_setup_bad.py', - prune=True, - file_args=True) - - def test_bad_save_state(self): - state_file = binary_search_state.STATE_FILE - hidden_state_file = os.path.basename(binary_search_state.HIDDEN_STATE_FILE) - - with open(state_file, 'w', encoding='utf-8') as f: - f.write('test123') - - bss = binary_search_state.MockBinarySearchState() - with self.assertRaises(OSError): - bss.SaveState() - - with open(state_file, 'r', encoding='utf-8') as f: - self.assertEqual(f.read(), 'test123') - - os.remove(state_file) - - # Cleanup generated save state that has no symlink - files = os.listdir(os.getcwd()) - save_states = [x for x in files if x.startswith(hidden_state_file)] - _ = [os.remove(x) for x in save_states] - - def test_save_state(self): - state_file = binary_search_state.STATE_FILE - - bss = binary_search_state.MockBinarySearchState() - bss.SaveState() - self.assertTrue(os.path.exists(state_file)) - first_state = os.readlink(state_file) - - bss.SaveState() - second_state = os.readlink(state_file) - self.assertTrue(os.path.exists(state_file)) - self.assertTrue(second_state != first_state) - self.assertFalse(os.path.exists(first_state)) - - bss.RemoveState() - self.assertFalse(os.path.islink(state_file)) - self.assertFalse(os.path.exists(second_state)) - - def test_load_state(self): - test_items = [1, 2, 3, 4, 5] - - bss = binary_search_state.MockBinarySearchState() - bss.all_items = test_items - bss.currently_good_items = set([1, 2, 3]) - bss.currently_bad_items = set([4, 5]) - bss.SaveState() - - bss = None - - bss2 = binary_search_state.MockBinarySearchState.LoadState() - self.assertEqual(bss2.all_items, test_items) - self.assertEqual(bss2.currently_good_items, set([])) - self.assertEqual(bss2.currently_bad_items, set([])) - - def test_tmp_cleanup(self): - bss = binary_search_state.MockBinarySearchState( - get_initial_items='echo "0\n1\n2\n3"', - switch_to_good='./switch_tmp.py', - file_args=True) - bss.SwitchToGood(['0', '1', '2', '3']) - - tmp_file = None - with open('tmp_file', 'r', encoding='utf-8') as f: - tmp_file = f.read() - os.remove('tmp_file') - - self.assertFalse(os.path.exists(tmp_file)) - ws = common.ReadWorkingSet() - for i in range(3): - self.assertEqual(ws[i], 42) - - def test_verify_fail(self): - bss = binary_search_state.MockBinarySearchState( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_bad.py', - switch_to_bad='./switch_to_good.py', - test_script='./is_good.py', - prune=True, - file_args=True, - verify=True) - with self.assertRaises(AssertionError): - bss.DoVerify() - - def test_early_terminate(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', - test_script='./is_good.py', - prune=True, - file_args=True, - iterations=1) - bss.DoSearchBadItems() - self.assertFalse(bss.found_items) - - def test_no_prune(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', - test_script='./is_good.py', - test_setup_script='./test_setup.py', - prune=False, - file_args=True) - bss.DoSearchBadItems() - self.assertEqual(len(bss.found_items), 1) - - bad_objs = common.ReadObjectsFile() - found_obj = int(bss.found_items.pop()) - self.assertEqual(bad_objs[found_obj], 1) - - def test_set_file(self): - binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good_set_file.py', - switch_to_bad='./switch_to_bad_set_file.py', - test_script='./is_good.py', - prune=True, - file_args=True, - verify=True) - self.check_output() - - def test_noincremental_prune(self): - ret = binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good_noinc_prune.py', - switch_to_bad='./switch_to_bad_noinc_prune.py', - test_script='./is_good_noinc_prune.py', - test_setup_script='./test_setup.py', - prune=True, - noincremental=True, - file_args=True, - verify=False) - self.assertEqual(ret, 0) - self.check_output() - - def check_output(self): - _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( - ('grep "Bad items are: " logs/binary_search_tool_test.py.out | ' - 'tail -n1')) - ls = out.splitlines() - self.assertEqual(len(ls), 1) - line = ls[0] - - _, _, bad_ones = line.partition('Bad items are: ') - bad_ones = bad_ones.split() - expected_result = common.ReadObjectsFile() - - # Reconstruct objects file from bad_ones and compare - actual_result = [0] * len(expected_result) - for bad_obj in bad_ones: - actual_result[int(bad_obj)] = 1 - - self.assertEqual(actual_result, expected_result) + """Tests for bisecting tool.""" + + def setUp(self): + """Generate [100-1000] object files, and 1-5% of which are bad ones.""" + GenObj() + + with open("./is_setup", "w", encoding="utf-8"): + pass + + try: + os.remove(binary_search_state.STATE_FILE) + except OSError: + pass + + def tearDown(self): + """Cleanup temp files.""" + CleanObj() + + try: + os.remove(os.readlink(binary_search_state.STATE_FILE)) + except OSError: + pass + + cleanup_list = [ + "./is_setup", + binary_search_state.STATE_FILE, + "noinc_prune_bad", + "noinc_prune_good", + "./cmd_script.sh", + ] + for f in cleanup_list: + if os.path.exists(f): + os.remove(f) + + def runTest(self): + ret = binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good.py", + switch_to_bad="./switch_to_bad.py", + test_script="./is_good.py", + prune=True, + file_args=True, + ) + self.assertEqual(ret, 0) + self.check_output() + + def test_arg_parse(self): + args = [ + "--get_initial_items", + "./gen_init_list.py", + "--switch_to_good", + "./switch_to_good.py", + "--switch_to_bad", + "./switch_to_bad.py", + "--test_script", + "./is_good.py", + "--prune", + "--file_args", + ] + ret = binary_search_state.Main(args) + self.assertEqual(ret, 0) + self.check_output() + + def test_test_setup_script(self): + os.remove("./is_setup") + with self.assertRaises(AssertionError): + ret = binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good.py", + switch_to_bad="./switch_to_bad.py", + test_script="./is_good.py", + prune=True, + file_args=True, + ) + + ret = binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good.py", + switch_to_bad="./switch_to_bad.py", + test_script="./is_good.py", + test_setup_script="./test_setup.py", + prune=True, + file_args=True, + ) + self.assertEqual(ret, 0) + self.check_output() + + def test_bad_test_setup_script(self): + with self.assertRaises(AssertionError): + binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good.py", + switch_to_bad="./switch_to_bad.py", + test_script="./is_good.py", + test_setup_script="./test_setup_bad.py", + prune=True, + file_args=True, + ) + + def test_bad_save_state(self): + state_file = binary_search_state.STATE_FILE + hidden_state_file = os.path.basename( + binary_search_state.HIDDEN_STATE_FILE + ) + + with open(state_file, "w", encoding="utf-8") as f: + f.write("test123") + + bss = binary_search_state.MockBinarySearchState() + with self.assertRaises(OSError): + bss.SaveState() + + with open(state_file, "r", encoding="utf-8") as f: + self.assertEqual(f.read(), "test123") + + os.remove(state_file) + + # Cleanup generated save state that has no symlink + files = os.listdir(os.getcwd()) + save_states = [x for x in files if x.startswith(hidden_state_file)] + _ = [os.remove(x) for x in save_states] + + def test_save_state(self): + state_file = binary_search_state.STATE_FILE + + bss = binary_search_state.MockBinarySearchState() + bss.SaveState() + self.assertTrue(os.path.exists(state_file)) + first_state = os.readlink(state_file) + + bss.SaveState() + second_state = os.readlink(state_file) + self.assertTrue(os.path.exists(state_file)) + self.assertTrue(second_state != first_state) + self.assertFalse(os.path.exists(first_state)) + + bss.RemoveState() + self.assertFalse(os.path.islink(state_file)) + self.assertFalse(os.path.exists(second_state)) + + def test_load_state(self): + test_items = [1, 2, 3, 4, 5] + + bss = binary_search_state.MockBinarySearchState() + bss.all_items = test_items + bss.currently_good_items = set([1, 2, 3]) + bss.currently_bad_items = set([4, 5]) + bss.SaveState() + + bss = None + + bss2 = binary_search_state.MockBinarySearchState.LoadState() + self.assertEqual(bss2.all_items, test_items) + self.assertEqual(bss2.currently_good_items, set([])) + self.assertEqual(bss2.currently_bad_items, set([])) + + def test_tmp_cleanup(self): + bss = binary_search_state.MockBinarySearchState( + get_initial_items='echo "0\n1\n2\n3"', + switch_to_good="./switch_tmp.py", + file_args=True, + ) + bss.SwitchToGood(["0", "1", "2", "3"]) + + tmp_file = None + with open("tmp_file", "r", encoding="utf-8") as f: + tmp_file = f.read() + os.remove("tmp_file") + + self.assertFalse(os.path.exists(tmp_file)) + ws = common.ReadWorkingSet() + for i in range(3): + self.assertEqual(ws[i], 42) + + def test_verify_fail(self): + bss = binary_search_state.MockBinarySearchState( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_bad.py", + switch_to_bad="./switch_to_good.py", + test_script="./is_good.py", + prune=True, + file_args=True, + verify=True, + ) + with self.assertRaises(AssertionError): + bss.DoVerify() + + def test_early_terminate(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", + test_script="./is_good.py", + prune=True, + file_args=True, + iterations=1, + ) + bss.DoSearchBadItems() + self.assertFalse(bss.found_items) + + def test_no_prune(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", + test_script="./is_good.py", + test_setup_script="./test_setup.py", + prune=False, + file_args=True, + ) + bss.DoSearchBadItems() + self.assertEqual(len(bss.found_items), 1) + + bad_objs = common.ReadObjectsFile() + found_obj = int(bss.found_items.pop()) + self.assertEqual(bad_objs[found_obj], 1) + + def test_set_file(self): + binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good_set_file.py", + switch_to_bad="./switch_to_bad_set_file.py", + test_script="./is_good.py", + prune=True, + file_args=True, + verify=True, + ) + self.check_output() + + def test_noincremental_prune(self): + ret = binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good_noinc_prune.py", + switch_to_bad="./switch_to_bad_noinc_prune.py", + test_script="./is_good_noinc_prune.py", + test_setup_script="./test_setup.py", + prune=True, + noincremental=True, + file_args=True, + verify=False, + ) + self.assertEqual(ret, 0) + self.check_output() + + def check_output(self): + _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( + ( + 'grep "Bad items are: " logs/binary_search_tool_test.py.out | ' + "tail -n1" + ) + ) + ls = out.splitlines() + self.assertEqual(len(ls), 1) + line = ls[0] + + _, _, bad_ones = line.partition("Bad items are: ") + bad_ones = bad_ones.split() + expected_result = common.ReadObjectsFile() + + # Reconstruct objects file from bad_ones and compare + actual_result = [0] * len(expected_result) + for bad_obj in bad_ones: + actual_result[int(bad_obj)] = 1 + + self.assertEqual(actual_result, expected_result) class BisectingUtilsPassTest(BisectingUtilsTest): - """Tests for bisecting tool at pass/transformation level.""" - - def check_pass_output(self, pass_name, pass_num, trans_num): - _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( - ('grep "Bad pass: " logs/binary_search_tool_test.py.out | ' - 'tail -n1')) - ls = out.splitlines() - self.assertEqual(len(ls), 1) - line = ls[0] - _, _, bad_info = line.partition('Bad pass: ') - actual_info = pass_name + ' at number ' + str(pass_num) - self.assertEqual(actual_info, bad_info) - - _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( - ('grep "Bad transformation number: ' - '" logs/binary_search_tool_test.py.out | ' - 'tail -n1')) - ls = out.splitlines() - self.assertEqual(len(ls), 1) - line = ls[0] - _, _, bad_info = line.partition('Bad transformation number: ') - actual_info = str(trans_num) - self.assertEqual(actual_info, bad_info) - - def test_with_prune(self): - ret = binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good.py', - switch_to_bad='./switch_to_bad.py', - test_script='./is_good.py', - pass_bisect='./generate_cmd.py', - prune=True, - file_args=True) - self.assertEqual(ret, 1) - - def test_gen_cmd_script(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', - test_script='./is_good.py', - pass_bisect='./generate_cmd.py', - prune=False, - file_args=True) - bss.DoSearchBadItems() - cmd_script_path = bss.cmd_script - self.assertTrue(os.path.exists(cmd_script_path)) - - def test_no_pass_support(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', - test_script='./is_good.py', - pass_bisect='./generate_cmd.py', - prune=False, - file_args=True) - bss.cmd_script = './cmd_script_no_support.py' - # No support for -opt-bisect-limit - with self.assertRaises(RuntimeError): - bss.BuildWithPassLimit(-1) - - def test_no_transform_support(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', - test_script='./is_good.py', - pass_bisect='./generate_cmd.py', - prune=False, - file_args=True) - bss.cmd_script = './cmd_script_no_support.py' - # No support for -print-debug-counter - with self.assertRaises(RuntimeError): - bss.BuildWithTransformLimit(-1, 'counter_name') - - def test_pass_transform_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', - test_script='./is_good.py', - pass_bisect='./generate_cmd.py', - prune=False, - file_args=True) - pass_num = 4 - trans_num = 19 - bss.cmd_script = './cmd_script.py %d %d' % (pass_num, trans_num) - bss.DoSearchBadPass() - self.check_pass_output('instcombine-visit', pass_num, trans_num) - - def test_result_not_reproduced_pass(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', - test_script='./is_good.py', - pass_bisect='./generate_cmd.py', - prune=False, - file_args=True) - # Fails reproducing at pass level. - pass_num = 0 - trans_num = 19 - bss.cmd_script = './cmd_script.py %d %d' % (pass_num, trans_num) - with self.assertRaises(ValueError): - bss.DoSearchBadPass() - - def test_result_not_reproduced_transform(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', - test_script='./is_good.py', - pass_bisect='./generate_cmd.py', - prune=False, - file_args=True) - # Fails reproducing at transformation level. - pass_num = 4 - trans_num = 0 - bss.cmd_script = './cmd_script.py %d %d' % (pass_num, trans_num) - with self.assertRaises(ValueError): - bss.DoSearchBadPass() + """Tests for bisecting tool at pass/transformation level.""" + + def check_pass_output(self, pass_name, pass_num, trans_num): + _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( + ( + 'grep "Bad pass: " logs/binary_search_tool_test.py.out | ' + "tail -n1" + ) + ) + ls = out.splitlines() + self.assertEqual(len(ls), 1) + line = ls[0] + _, _, bad_info = line.partition("Bad pass: ") + actual_info = pass_name + " at number " + str(pass_num) + self.assertEqual(actual_info, bad_info) + + _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( + ( + 'grep "Bad transformation number: ' + '" logs/binary_search_tool_test.py.out | ' + "tail -n1" + ) + ) + ls = out.splitlines() + self.assertEqual(len(ls), 1) + line = ls[0] + _, _, bad_info = line.partition("Bad transformation number: ") + actual_info = str(trans_num) + self.assertEqual(actual_info, bad_info) + + def test_with_prune(self): + ret = binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good.py", + switch_to_bad="./switch_to_bad.py", + test_script="./is_good.py", + pass_bisect="./generate_cmd.py", + prune=True, + file_args=True, + ) + self.assertEqual(ret, 1) + + def test_gen_cmd_script(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", + test_script="./is_good.py", + pass_bisect="./generate_cmd.py", + prune=False, + file_args=True, + ) + bss.DoSearchBadItems() + cmd_script_path = bss.cmd_script + self.assertTrue(os.path.exists(cmd_script_path)) + + def test_no_pass_support(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", + test_script="./is_good.py", + pass_bisect="./generate_cmd.py", + prune=False, + file_args=True, + ) + bss.cmd_script = "./cmd_script_no_support.py" + # No support for -opt-bisect-limit + with self.assertRaises(RuntimeError): + bss.BuildWithPassLimit(-1) + + def test_no_transform_support(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", + test_script="./is_good.py", + pass_bisect="./generate_cmd.py", + prune=False, + file_args=True, + ) + bss.cmd_script = "./cmd_script_no_support.py" + # No support for -print-debug-counter + with self.assertRaises(RuntimeError): + bss.BuildWithTransformLimit(-1, "counter_name") + + def test_pass_transform_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", + test_script="./is_good.py", + pass_bisect="./generate_cmd.py", + prune=False, + file_args=True, + ) + pass_num = 4 + trans_num = 19 + bss.cmd_script = "./cmd_script.py %d %d" % (pass_num, trans_num) + bss.DoSearchBadPass() + self.check_pass_output("instcombine-visit", pass_num, trans_num) + + def test_result_not_reproduced_pass(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", + test_script="./is_good.py", + pass_bisect="./generate_cmd.py", + prune=False, + file_args=True, + ) + # Fails reproducing at pass level. + pass_num = 0 + trans_num = 19 + bss.cmd_script = "./cmd_script.py %d %d" % (pass_num, trans_num) + with self.assertRaises(ValueError): + bss.DoSearchBadPass() + + def test_result_not_reproduced_transform(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", + test_script="./is_good.py", + pass_bisect="./generate_cmd.py", + prune=False, + file_args=True, + ) + # Fails reproducing at transformation level. + pass_num = 4 + trans_num = 0 + bss.cmd_script = "./cmd_script.py %d %d" % (pass_num, trans_num) + with self.assertRaises(ValueError): + bss.DoSearchBadPass() class BisectStressTest(unittest.TestCase): - """Stress tests for bisecting tool.""" - - def test_every_obj_bad(self): - amt = 25 - gen_obj.Main(['--obj_num', str(amt), '--bad_obj_num', str(amt)]) - ret = binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good.py', - switch_to_bad='./switch_to_bad.py', - test_script='./is_good.py', - prune=True, - file_args=True, - verify=False) - self.assertEqual(ret, 0) - self.check_output() - - def test_every_index_is_bad(self): - amt = 25 - for i in range(amt): - obj_list = ['0'] * amt - obj_list[i] = '1' - obj_list = ','.join(obj_list) - gen_obj.Main(['--obj_list', obj_list]) - ret = binary_search_state.Run( - get_initial_items='./gen_init_list.py', - switch_to_good='./switch_to_good.py', - switch_to_bad='./switch_to_bad.py', - test_setup_script='./test_setup.py', - test_script='./is_good.py', - prune=True, - file_args=True) - self.assertEqual(ret, 0) - self.check_output() - - def check_output(self): - _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( - ('grep "Bad items are: " logs/binary_search_tool_test.py.out | ' - 'tail -n1')) - ls = out.splitlines() - self.assertEqual(len(ls), 1) - line = ls[0] - - _, _, bad_ones = line.partition('Bad items are: ') - bad_ones = bad_ones.split() - expected_result = common.ReadObjectsFile() - - # Reconstruct objects file from bad_ones and compare - actual_result = [0] * len(expected_result) - for bad_obj in bad_ones: - actual_result[int(bad_obj)] = 1 - - self.assertEqual(actual_result, expected_result) + """Stress tests for bisecting tool.""" + + def test_every_obj_bad(self): + amt = 25 + gen_obj.Main(["--obj_num", str(amt), "--bad_obj_num", str(amt)]) + ret = binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good.py", + switch_to_bad="./switch_to_bad.py", + test_script="./is_good.py", + prune=True, + file_args=True, + verify=False, + ) + self.assertEqual(ret, 0) + self.check_output() + + def test_every_index_is_bad(self): + amt = 25 + for i in range(amt): + obj_list = ["0"] * amt + obj_list[i] = "1" + obj_list = ",".join(obj_list) + gen_obj.Main(["--obj_list", obj_list]) + ret = binary_search_state.Run( + get_initial_items="./gen_init_list.py", + switch_to_good="./switch_to_good.py", + switch_to_bad="./switch_to_bad.py", + test_setup_script="./test_setup.py", + test_script="./is_good.py", + prune=True, + file_args=True, + ) + self.assertEqual(ret, 0) + self.check_output() + + def check_output(self): + _, out, _ = command_executer.GetCommandExecuter().RunCommandWOutput( + ( + 'grep "Bad items are: " logs/binary_search_tool_test.py.out | ' + "tail -n1" + ) + ) + ls = out.splitlines() + self.assertEqual(len(ls), 1) + line = ls[0] + + _, _, bad_ones = line.partition("Bad items are: ") + bad_ones = bad_ones.split() + expected_result = common.ReadObjectsFile() + + # Reconstruct objects file from bad_ones and compare + actual_result = [0] * len(expected_result) + for bad_obj in bad_ones: + actual_result[int(bad_obj)] = 1 + + self.assertEqual(actual_result, expected_result) def Main(argv): - num_tests = 2 - if len(argv) > 1: - num_tests = int(argv[1]) - - suite = unittest.TestSuite() - for _ in range(0, num_tests): - suite.addTest(BisectingUtilsTest()) - suite.addTest(BisectingUtilsTest('test_arg_parse')) - suite.addTest(BisectingUtilsTest('test_test_setup_script')) - suite.addTest(BisectingUtilsTest('test_bad_test_setup_script')) - suite.addTest(BisectingUtilsTest('test_bad_save_state')) - suite.addTest(BisectingUtilsTest('test_save_state')) - suite.addTest(BisectingUtilsTest('test_load_state')) - suite.addTest(BisectingUtilsTest('test_tmp_cleanup')) - suite.addTest(BisectingUtilsTest('test_verify_fail')) - suite.addTest(BisectingUtilsTest('test_early_terminate')) - suite.addTest(BisectingUtilsTest('test_no_prune')) - suite.addTest(BisectingUtilsTest('test_set_file')) - suite.addTest(BisectingUtilsTest('test_noincremental_prune')) - suite.addTest(BisectingUtilsPassTest('test_with_prune')) - suite.addTest(BisectingUtilsPassTest('test_gen_cmd_script')) - suite.addTest(BisectingUtilsPassTest('test_no_pass_support')) - suite.addTest(BisectingUtilsPassTest('test_no_transform_support')) - suite.addTest(BisectingUtilsPassTest('test_pass_transform_bisect')) - suite.addTest(BisectingUtilsPassTest('test_result_not_reproduced_pass')) - suite.addTest(BisectingUtilsPassTest('test_result_not_reproduced_transform')) - suite.addTest(BisectTest('test_full_bisector')) - suite.addTest(BisectStressTest('test_every_obj_bad')) - suite.addTest(BisectStressTest('test_every_index_is_bad')) - runner = unittest.TextTestRunner() - runner.run(suite) - - -if __name__ == '__main__': - Main(sys.argv) + num_tests = 2 + if len(argv) > 1: + num_tests = int(argv[1]) + + suite = unittest.TestSuite() + for _ in range(0, num_tests): + suite.addTest(BisectingUtilsTest()) + suite.addTest(BisectingUtilsTest("test_arg_parse")) + suite.addTest(BisectingUtilsTest("test_test_setup_script")) + suite.addTest(BisectingUtilsTest("test_bad_test_setup_script")) + suite.addTest(BisectingUtilsTest("test_bad_save_state")) + suite.addTest(BisectingUtilsTest("test_save_state")) + suite.addTest(BisectingUtilsTest("test_load_state")) + suite.addTest(BisectingUtilsTest("test_tmp_cleanup")) + suite.addTest(BisectingUtilsTest("test_verify_fail")) + suite.addTest(BisectingUtilsTest("test_early_terminate")) + suite.addTest(BisectingUtilsTest("test_no_prune")) + suite.addTest(BisectingUtilsTest("test_set_file")) + suite.addTest(BisectingUtilsTest("test_noincremental_prune")) + suite.addTest(BisectingUtilsPassTest("test_with_prune")) + suite.addTest(BisectingUtilsPassTest("test_gen_cmd_script")) + suite.addTest(BisectingUtilsPassTest("test_no_pass_support")) + suite.addTest(BisectingUtilsPassTest("test_no_transform_support")) + suite.addTest(BisectingUtilsPassTest("test_pass_transform_bisect")) + suite.addTest(BisectingUtilsPassTest("test_result_not_reproduced_pass")) + suite.addTest( + BisectingUtilsPassTest("test_result_not_reproduced_transform") + ) + suite.addTest(BisectTest("test_full_bisector")) + suite.addTest(BisectStressTest("test_every_obj_bad")) + suite.addTest(BisectStressTest("test_every_index_is_bad")) + runner = unittest.TextTestRunner() + runner.run(suite) + + +if __name__ == "__main__": + Main(sys.argv) diff --git a/binary_search_tool/test/cmd_script.py b/binary_search_tool/test/cmd_script.py index bfd56052..b0475c70 100755 --- a/binary_search_tool/test/cmd_script.py +++ b/binary_search_tool/test/cmd_script.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -11,7 +11,6 @@ It assumes that -opt-bisect-limit and -print-debug-counter are supported by the compiler. """ -from __future__ import print_function import os import sys @@ -20,57 +19,62 @@ from binary_search_tool.test import common def Main(argv): - if not os.path.exists('./is_setup'): - return 1 - - if len(argv) != 3: - return 1 - - limit_flags = os.environ['LIMIT_FLAGS'] - opt_bisect_exist = False - debug_counter_exist = False - - for option in limit_flags.split(): - if '-opt-bisect-limit' in option: - opt_bisect_limit = int(option.split('=')[-1]) - opt_bisect_exist = True - if '-debug-counter=' in option: - debug_counter = int(option.split('=')[-1]) - debug_counter_exist = True - - if not opt_bisect_exist: - return 1 - - # Manually set total number and bad number - total_pass = 10 - total_transform = 20 - bad_pass = int(argv[1]) - bad_transform = int(argv[2]) - - if opt_bisect_limit == -1: - opt_bisect_limit = total_pass - - for i in range(1, total_pass + 1): - bisect_str = 'BISECT: %srunning pass (%d) Combine redundant ' \ - 'instructions on function (f1)' \ - % ('NOT ' if i > opt_bisect_limit else '', i) - print(bisect_str, file=sys.stderr) - - if debug_counter_exist: - print('Counters and values:', file=sys.stderr) - print( - 'instcombine-visit : {%d, 0, %d}' % (total_transform, debug_counter), - file=sys.stderr) - - if opt_bisect_limit > bad_pass or \ - (debug_counter_exist and debug_counter > bad_transform): - common.WriteWorkingSet([1]) - else: - common.WriteWorkingSet([0]) - - return 0 - - -if __name__ == '__main__': - retval = Main(sys.argv) - sys.exit(retval) + if not os.path.exists("./is_setup"): + return 1 + + if len(argv) != 3: + return 1 + + limit_flags = os.environ["LIMIT_FLAGS"] + opt_bisect_exist = False + debug_counter_exist = False + + for option in limit_flags.split(): + if "-opt-bisect-limit" in option: + opt_bisect_limit = int(option.split("=")[-1]) + opt_bisect_exist = True + if "-debug-counter=" in option: + debug_counter = int(option.split("=")[-1]) + debug_counter_exist = True + + if not opt_bisect_exist: + return 1 + + # Manually set total number and bad number + total_pass = 10 + total_transform = 20 + bad_pass = int(argv[1]) + bad_transform = int(argv[2]) + + if opt_bisect_limit == -1: + opt_bisect_limit = total_pass + + for i in range(1, total_pass + 1): + bisect_str = ( + "BISECT: %srunning pass (%d) Combine redundant " + "instructions on function (f1)" + % ("NOT " if i > opt_bisect_limit else "", i) + ) + print(bisect_str, file=sys.stderr) + + if debug_counter_exist: + print("Counters and values:", file=sys.stderr) + print( + "instcombine-visit : {%d, 0, %d}" + % (total_transform, debug_counter), + file=sys.stderr, + ) + + if opt_bisect_limit > bad_pass or ( + debug_counter_exist and debug_counter > bad_transform + ): + common.WriteWorkingSet([1]) + else: + common.WriteWorkingSet([0]) + + return 0 + + +if __name__ == "__main__": + retval = Main(sys.argv) + sys.exit(retval) diff --git a/binary_search_tool/test/cmd_script_no_support.py b/binary_search_tool/test/cmd_script_no_support.py index badbedc8..f1c2bcbe 100644 --- a/binary_search_tool/test/cmd_script_no_support.py +++ b/binary_search_tool/test/cmd_script_no_support.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -9,21 +9,21 @@ This script generates a pseudo log when certain bisecting flags are not supported by compiler. """ -from __future__ import print_function import os import sys def Main(): - if not os.path.exists('./is_setup'): - return 1 - print( - 'No support for -opt-bisect-limit or -print-debug-counter.', - file=sys.stderr) - return 0 - - -if __name__ == '__main__': - retval = Main() - sys.exit(retval) + if not os.path.exists("./is_setup"): + return 1 + print( + "No support for -opt-bisect-limit or -print-debug-counter.", + file=sys.stderr, + ) + return 0 + + +if __name__ == "__main__": + retval = Main() + sys.exit(retval) diff --git a/binary_search_tool/test/common.py b/binary_search_tool/test/common.py index cf5300f5..6632a4c7 100755 --- a/binary_search_tool/test/common.py +++ b/binary_search_tool/test/common.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -8,35 +8,35 @@ DEFAULT_OBJECT_NUMBER = 1238 DEFAULT_BAD_OBJECT_NUMBER = 23 -OBJECTS_FILE = 'objects.txt' -WORKING_SET_FILE = 'working_set.txt' +OBJECTS_FILE = "objects.txt" +WORKING_SET_FILE = "working_set.txt" def ReadWorkingSet(): - working_set = [] - with open(WORKING_SET_FILE, 'r', encoding='utf-8') as f: - for l in f: - working_set.append(int(l)) - return working_set + working_set = [] + with open(WORKING_SET_FILE, "r", encoding="utf-8") as f: + for l in f: + working_set.append(int(l)) + return working_set def WriteWorkingSet(working_set): - with open(WORKING_SET_FILE, 'w', encoding='utf-8') as f: - for o in working_set: - f.write('{0}\n'.format(o)) + with open(WORKING_SET_FILE, "w", encoding="utf-8") as f: + for o in working_set: + f.write("{0}\n".format(o)) def ReadObjectsFile(): - objects_file = [] - with open(OBJECTS_FILE, 'r', encoding='utf-8') as f: - for l in f: - objects_file.append(int(l)) - return objects_file + objects_file = [] + with open(OBJECTS_FILE, "r", encoding="utf-8") as f: + for l in f: + objects_file.append(int(l)) + return objects_file def ReadObjectIndex(filename): - object_index = [] - with open(filename, 'r', encoding='utf-8') as f: - for o in f: - object_index.append(int(o)) - return object_index + object_index = [] + with open(filename, "r", encoding="utf-8") as f: + for o in f: + object_index.append(int(o)) + return object_index diff --git a/binary_search_tool/test/gen_init_list.py b/binary_search_tool/test/gen_init_list.py index bc5dd8fe..138e949c 100755 --- a/binary_search_tool/test/gen_init_list.py +++ b/binary_search_tool/test/gen_init_list.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Prints out index for every object file, starting from 0.""" -from __future__ import print_function import sys @@ -15,13 +14,14 @@ from binary_search_tool.test import common def Main(): - ce = command_executer.GetCommandExecuter() - _, l, _ = ce.RunCommandWOutput( - 'cat {0} | wc -l'.format(common.OBJECTS_FILE), print_to_console=False) - for i in range(0, int(l)): - print(i) + ce = command_executer.GetCommandExecuter() + _, l, _ = ce.RunCommandWOutput( + "cat {0} | wc -l".format(common.OBJECTS_FILE), print_to_console=False + ) + for i in range(0, int(l)): + print(i) -if __name__ == '__main__': - Main() - sys.exit(0) +if __name__ == "__main__": + Main() + sys.exit(0) diff --git a/binary_search_tool/test/gen_obj.py b/binary_search_tool/test/gen_obj.py index 4f65c71b..394445f0 100755 --- a/binary_search_tool/test/gen_obj.py +++ b/binary_search_tool/test/gen_obj.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -10,7 +10,6 @@ 1 represents a bad object file. """ -from __future__ import print_function import argparse import os @@ -21,81 +20,91 @@ from binary_search_tool.test import common def Main(argv): - """Generates a list, the value of each element is 0 or 1. - - The number of 1s in the list is specified by bad_obj_num. - The others are all 0s. The total number of 0s and 1s is specified by obj_num. - - Args: - argv: argument from command line - - Returns: - 0 always. - """ - parser = argparse.ArgumentParser() - parser.add_argument( - '-n', - '--obj_num', - dest='obj_num', - default=common.DEFAULT_OBJECT_NUMBER, - help=('Number of total objects.')) - parser.add_argument( - '-b', - '--bad_obj_num', - dest='bad_obj_num', - default=common.DEFAULT_BAD_OBJECT_NUMBER, - help=('Number of bad objects. Must be great than or ' - 'equal to zero and less than total object ' - 'number.')) - parser.add_argument( - '-o', - '--obj_list', - dest='obj_list', - default='', - help=('List of comma seperated objects to generate. ' - 'A 0 means the object is good, a 1 means the ' - 'object is bad.')) - options = parser.parse_args(argv) - - obj_num = int(options.obj_num) - bad_obj_num = int(options.bad_obj_num) - bad_to_gen = int(options.bad_obj_num) - obj_list = options.obj_list - if not obj_list: - obj_list = [] - for i in range(obj_num): - if bad_to_gen > 0 and random.randint(1, obj_num) <= bad_obj_num: - obj_list.append(1) - bad_to_gen -= 1 - else: - obj_list.append(0) - while bad_to_gen > 0: - t = random.randint(0, obj_num - 1) - if obj_list[t] == 0: - obj_list[t] = 1 - bad_to_gen -= 1 - else: - obj_list = obj_list.split(',') - - if os.path.isfile(common.OBJECTS_FILE): - os.remove(common.OBJECTS_FILE) - if os.path.isfile(common.WORKING_SET_FILE): - os.remove(common.WORKING_SET_FILE) - - with open(common.OBJECTS_FILE, 'w', encoding='utf-8') as f: - with open(common.WORKING_SET_FILE, 'w', encoding='utf-8') as w: - for i in obj_list: - f.write('{0}\n'.format(i)) - w.write('{0}\n'.format(i)) - - obj_num = len(obj_list) - bad_obj_num = obj_list.count(1) - print('Generated {0} object files, with {1} bad ones.'.format( - obj_num, bad_obj_num)) - - return 0 - - -if __name__ == '__main__': - retval = Main(sys.argv[1:]) - sys.exit(retval) + """Generates a list, the value of each element is 0 or 1. + + The number of 1s in the list is specified by bad_obj_num. + The others are all 0s. The total number of 0s and 1s is specified by obj_num. + + Args: + argv: argument from command line + + Returns: + 0 always. + """ + parser = argparse.ArgumentParser() + parser.add_argument( + "-n", + "--obj_num", + dest="obj_num", + default=common.DEFAULT_OBJECT_NUMBER, + help=("Number of total objects."), + ) + parser.add_argument( + "-b", + "--bad_obj_num", + dest="bad_obj_num", + default=common.DEFAULT_BAD_OBJECT_NUMBER, + help=( + "Number of bad objects. Must be great than or " + "equal to zero and less than total object " + "number." + ), + ) + parser.add_argument( + "-o", + "--obj_list", + dest="obj_list", + default="", + help=( + "List of comma seperated objects to generate. " + "A 0 means the object is good, a 1 means the " + "object is bad." + ), + ) + options = parser.parse_args(argv) + + obj_num = int(options.obj_num) + bad_obj_num = int(options.bad_obj_num) + bad_to_gen = int(options.bad_obj_num) + obj_list = options.obj_list + if not obj_list: + obj_list = [] + for i in range(obj_num): + if bad_to_gen > 0 and random.randint(1, obj_num) <= bad_obj_num: + obj_list.append(1) + bad_to_gen -= 1 + else: + obj_list.append(0) + while bad_to_gen > 0: + t = random.randint(0, obj_num - 1) + if obj_list[t] == 0: + obj_list[t] = 1 + bad_to_gen -= 1 + else: + obj_list = obj_list.split(",") + + if os.path.isfile(common.OBJECTS_FILE): + os.remove(common.OBJECTS_FILE) + if os.path.isfile(common.WORKING_SET_FILE): + os.remove(common.WORKING_SET_FILE) + + with open(common.OBJECTS_FILE, "w", encoding="utf-8") as f: + with open(common.WORKING_SET_FILE, "w", encoding="utf-8") as w: + for i in obj_list: + f.write("{0}\n".format(i)) + w.write("{0}\n".format(i)) + + obj_num = len(obj_list) + bad_obj_num = obj_list.count(1) + print( + "Generated {0} object files, with {1} bad ones.".format( + obj_num, bad_obj_num + ) + ) + + return 0 + + +if __name__ == "__main__": + retval = Main(sys.argv[1:]) + sys.exit(retval) diff --git a/binary_search_tool/test/generate_cmd.py b/binary_search_tool/test/generate_cmd.py index 51b36b0a..96fa720c 100755 --- a/binary_search_tool/test/generate_cmd.py +++ b/binary_search_tool/test/generate_cmd.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -10,21 +10,20 @@ This is a required argument for pass level bisecting. For unit test, we use this script to verify if cmd_script.sh is generated correctly. """ -from __future__ import print_function import os import sys def Main(): - if not os.path.exists('./is_setup'): - return 1 - file_name = 'cmd_script.sh' - with open(file_name, 'w', encoding='utf-8') as f: - f.write('Generated by generate_cmd.py') - return 0 + if not os.path.exists("./is_setup"): + return 1 + file_name = "cmd_script.sh" + with open(file_name, "w", encoding="utf-8") as f: + f.write("Generated by generate_cmd.py") + return 0 -if __name__ == '__main__': - retval = Main() - sys.exit(retval) +if __name__ == "__main__": + retval = Main() + sys.exit(retval) diff --git a/binary_search_tool/test/is_good.py b/binary_search_tool/test/is_good.py index 662921e8..fd3f908f 100755 --- a/binary_search_tool/test/is_good.py +++ b/binary_search_tool/test/is_good.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Check to see if the working set produces a good executable.""" -from __future__ import print_function import os import sys @@ -15,15 +14,15 @@ from binary_search_tool.test import common def Main(): - if not os.path.exists('./is_setup'): - return 1 - working_set = common.ReadWorkingSet() - for w in working_set: - if w == 1: - return 1 ## False, linking failure - return 0 - - -if __name__ == '__main__': - retval = Main() - sys.exit(retval) + if not os.path.exists("./is_setup"): + return 1 + working_set = common.ReadWorkingSet() + for w in working_set: + if w == 1: + return 1 ## False, linking failure + return 0 + + +if __name__ == "__main__": + retval = Main() + sys.exit(retval) diff --git a/binary_search_tool/test/is_good_noinc_prune.py b/binary_search_tool/test/is_good_noinc_prune.py index c0e42bb1..654fcd25 100755 --- a/binary_search_tool/test/is_good_noinc_prune.py +++ b/binary_search_tool/test/is_good_noinc_prune.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -12,7 +12,6 @@ to the switch scripts is equals to the actual number of items (i.e. checking that noincremental always holds). """ -from __future__ import print_function import os import sys @@ -21,31 +20,31 @@ from binary_search_tool.test import common def Main(): - working_set = common.ReadWorkingSet() + working_set = common.ReadWorkingSet() - with open('noinc_prune_good', 'r', encoding='utf-8') as good_args: - num_good_args = len(good_args.readlines()) + with open("noinc_prune_good", "r", encoding="utf-8") as good_args: + num_good_args = len(good_args.readlines()) - with open('noinc_prune_bad', 'r', encoding='utf-8') as bad_args: - num_bad_args = len(bad_args.readlines()) + with open("noinc_prune_bad", "r", encoding="utf-8") as bad_args: + num_bad_args = len(bad_args.readlines()) - num_args = num_good_args + num_bad_args - if num_args != len(working_set): - print('Only %d args, expected %d' % (num_args, len(working_set))) - print('%d good args, %d bad args' % (num_good_args, num_bad_args)) - return 3 + num_args = num_good_args + num_bad_args + if num_args != len(working_set): + print("Only %d args, expected %d" % (num_args, len(working_set))) + print("%d good args, %d bad args" % (num_good_args, num_bad_args)) + return 3 - os.remove('noinc_prune_bad') - os.remove('noinc_prune_good') + os.remove("noinc_prune_bad") + os.remove("noinc_prune_good") - if not os.path.exists('./is_setup'): - return 1 - for w in working_set: - if w == 1: - return 1 ## False, linking failure - return 0 + if not os.path.exists("./is_setup"): + return 1 + for w in working_set: + if w == 1: + return 1 ## False, linking failure + return 0 -if __name__ == '__main__': - retval = Main() - sys.exit(retval) +if __name__ == "__main__": + retval = Main() + sys.exit(retval) diff --git a/binary_search_tool/test/switch_tmp.py b/binary_search_tool/test/switch_tmp.py index 0f3c4234..acc0393d 100755 --- a/binary_search_tool/test/switch_tmp.py +++ b/binary_search_tool/test/switch_tmp.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -12,7 +12,6 @@ this script) content. Every line in the file is an object index, which will be set to good (mark as 42). """ -from __future__ import print_function import sys @@ -20,20 +19,20 @@ from binary_search_tool.test import common def Main(argv): - working_set = common.ReadWorkingSet() - object_index = common.ReadObjectIndex(argv[1]) + working_set = common.ReadWorkingSet() + object_index = common.ReadObjectIndex(argv[1]) - # Random number so the results can be checked - for oi in object_index: - working_set[int(oi)] = 42 + # Random number so the results can be checked + for oi in object_index: + working_set[int(oi)] = 42 - common.WriteWorkingSet(working_set) - with open('tmp_file', 'w', encoding='utf-8') as f: - f.write(argv[1]) + common.WriteWorkingSet(working_set) + with open("tmp_file", "w", encoding="utf-8") as f: + f.write(argv[1]) - return 0 + return 0 -if __name__ == '__main__': - retval = Main(sys.argv) - sys.exit(retval) +if __name__ == "__main__": + retval = Main(sys.argv) + sys.exit(retval) diff --git a/binary_search_tool/test/switch_to_bad.py b/binary_search_tool/test/switch_to_bad.py index e3553eb6..bc32f3cc 100755 --- a/binary_search_tool/test/switch_to_bad.py +++ b/binary_search_tool/test/switch_to_bad.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Switch part of the objects file in working set to (possible) bad ones.""" -from __future__ import print_function import sys @@ -14,19 +13,19 @@ from binary_search_tool.test import common def Main(argv): - """Switch part of the objects file in working set to (possible) bad ones.""" - working_set = common.ReadWorkingSet() - objects_file = common.ReadObjectsFile() - object_index = common.ReadObjectIndex(argv[1]) + """Switch part of the objects file in working set to (possible) bad ones.""" + working_set = common.ReadWorkingSet() + objects_file = common.ReadObjectsFile() + object_index = common.ReadObjectIndex(argv[1]) - for oi in object_index: - working_set[oi] = objects_file[oi] + for oi in object_index: + working_set[oi] = objects_file[oi] - common.WriteWorkingSet(working_set) + common.WriteWorkingSet(working_set) - return 0 + return 0 -if __name__ == '__main__': - retval = Main(sys.argv) - sys.exit(retval) +if __name__ == "__main__": + retval = Main(sys.argv) + sys.exit(retval) diff --git a/binary_search_tool/test/switch_to_bad_noinc_prune.py b/binary_search_tool/test/switch_to_bad_noinc_prune.py index 81b558e1..e5574f95 100755 --- a/binary_search_tool/test/switch_to_bad_noinc_prune.py +++ b/binary_search_tool/test/switch_to_bad_noinc_prune.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -18,7 +18,6 @@ that noincremental always holds). Warning: This switch script assumes the --file_args option """ -from __future__ import print_function import shutil import sys @@ -27,21 +26,21 @@ from binary_search_tool.test import common def Main(argv): - """Switch part of the objects file in working set to (possible) bad ones.""" - working_set = common.ReadWorkingSet() - objects_file = common.ReadObjectsFile() - object_index = common.ReadObjectIndex(argv[1]) + """Switch part of the objects file in working set to (possible) bad ones.""" + working_set = common.ReadWorkingSet() + objects_file = common.ReadObjectsFile() + object_index = common.ReadObjectIndex(argv[1]) - for oi in object_index: - working_set[oi] = objects_file[oi] + for oi in object_index: + working_set[oi] = objects_file[oi] - shutil.copy(argv[1], './noinc_prune_bad') + shutil.copy(argv[1], "./noinc_prune_bad") - common.WriteWorkingSet(working_set) + common.WriteWorkingSet(working_set) - return 0 + return 0 -if __name__ == '__main__': - retval = Main(sys.argv) - sys.exit(retval) +if __name__ == "__main__": + retval = Main(sys.argv) + sys.exit(retval) diff --git a/binary_search_tool/test/switch_to_bad_set_file.py b/binary_search_tool/test/switch_to_bad_set_file.py index 5b941c62..9d4bee6f 100755 --- a/binary_search_tool/test/switch_to_bad_set_file.py +++ b/binary_search_tool/test/switch_to_bad_set_file.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -10,7 +10,6 @@ This script is meant to be specifically used with the set_file test. This uses the set files generated by binary_search_state to do the switching. """ -from __future__ import print_function import os import sys @@ -19,24 +18,24 @@ from binary_search_tool.test import common def Main(_): - """Switch part of the objects file in working set to (possible) bad ones.""" - working_set = common.ReadWorkingSet() - objects_file = common.ReadObjectsFile() + """Switch part of the objects file in working set to (possible) bad ones.""" + working_set = common.ReadWorkingSet() + objects_file = common.ReadObjectsFile() - if not os.path.exists(os.environ['BISECT_BAD_SET']): - print('Bad set file does not exist!') - return 1 + if not os.path.exists(os.environ["BISECT_BAD_SET"]): + print("Bad set file does not exist!") + return 1 - object_index = common.ReadObjectIndex(os.environ['BISECT_BAD_SET']) + object_index = common.ReadObjectIndex(os.environ["BISECT_BAD_SET"]) - for oi in object_index: - working_set[int(oi)] = objects_file[oi] + for oi in object_index: + working_set[int(oi)] = objects_file[oi] - common.WriteWorkingSet(working_set) + common.WriteWorkingSet(working_set) - return 0 + return 0 -if __name__ == '__main__': - retval = Main(sys.argv) - sys.exit(retval) +if __name__ == "__main__": + retval = Main(sys.argv) + sys.exit(retval) diff --git a/binary_search_tool/test/switch_to_good.py b/binary_search_tool/test/switch_to_good.py index 97479329..61a59a2a 100755 --- a/binary_search_tool/test/switch_to_good.py +++ b/binary_search_tool/test/switch_to_good.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -11,7 +11,6 @@ this script) content. Every line in the file is an object index, which will be set to good (mark as 0). """ -from __future__ import print_function import sys @@ -19,17 +18,17 @@ from binary_search_tool.test import common def Main(argv): - working_set = common.ReadWorkingSet() - object_index = common.ReadObjectIndex(argv[1]) + working_set = common.ReadWorkingSet() + object_index = common.ReadObjectIndex(argv[1]) - for oi in object_index: - working_set[int(oi)] = 0 + for oi in object_index: + working_set[int(oi)] = 0 - common.WriteWorkingSet(working_set) + common.WriteWorkingSet(working_set) - return 0 + return 0 -if __name__ == '__main__': - retval = Main(sys.argv) - sys.exit(retval) +if __name__ == "__main__": + retval = Main(sys.argv) + sys.exit(retval) diff --git a/binary_search_tool/test/switch_to_good_noinc_prune.py b/binary_search_tool/test/switch_to_good_noinc_prune.py index 0b91a0d8..3bda1d78 100755 --- a/binary_search_tool/test/switch_to_good_noinc_prune.py +++ b/binary_search_tool/test/switch_to_good_noinc_prune.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -18,7 +18,6 @@ that noincremental always holds). Warning: This switch script assumes the --file_args option """ -from __future__ import print_function import shutil import sys @@ -27,19 +26,19 @@ from binary_search_tool.test import common def Main(argv): - working_set = common.ReadWorkingSet() - object_index = common.ReadObjectIndex(argv[1]) + working_set = common.ReadWorkingSet() + object_index = common.ReadObjectIndex(argv[1]) - for oi in object_index: - working_set[int(oi)] = 0 + for oi in object_index: + working_set[int(oi)] = 0 - shutil.copy(argv[1], './noinc_prune_good') + shutil.copy(argv[1], "./noinc_prune_good") - common.WriteWorkingSet(working_set) + common.WriteWorkingSet(working_set) - return 0 + return 0 -if __name__ == '__main__': - retval = Main(sys.argv) - sys.exit(retval) +if __name__ == "__main__": + retval = Main(sys.argv) + sys.exit(retval) diff --git a/binary_search_tool/test/switch_to_good_set_file.py b/binary_search_tool/test/switch_to_good_set_file.py index 1cb05e0c..b83cbe3f 100755 --- a/binary_search_tool/test/switch_to_good_set_file.py +++ b/binary_search_tool/test/switch_to_good_set_file.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -14,7 +14,6 @@ This script is meant to be specifically used with the set_file test. This uses the set files generated by binary_search_state to do the switching. """ -from __future__ import print_function import os import sys @@ -23,22 +22,22 @@ from binary_search_tool.test import common def Main(_): - working_set = common.ReadWorkingSet() + working_set = common.ReadWorkingSet() - if not os.path.exists(os.environ['BISECT_GOOD_SET']): - print('Good set file does not exist!') - return 1 + if not os.path.exists(os.environ["BISECT_GOOD_SET"]): + print("Good set file does not exist!") + return 1 - object_index = common.ReadObjectIndex(os.environ['BISECT_GOOD_SET']) + object_index = common.ReadObjectIndex(os.environ["BISECT_GOOD_SET"]) - for oi in object_index: - working_set[int(oi)] = 0 + for oi in object_index: + working_set[int(oi)] = 0 - common.WriteWorkingSet(working_set) + common.WriteWorkingSet(working_set) - return 0 + return 0 -if __name__ == '__main__': - retval = Main(sys.argv) - sys.exit(retval) +if __name__ == "__main__": + retval = Main(sys.argv) + sys.exit(retval) diff --git a/binary_search_tool/test/test_setup.py b/binary_search_tool/test/test_setup.py index ecc8eb97..52486a28 100755 --- a/binary_search_tool/test/test_setup.py +++ b/binary_search_tool/test/test_setup.py @@ -1,24 +1,23 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Emulate running of test setup script, is_good.py should fail without this.""" -from __future__ import print_function import sys def Main(): - # create ./is_setup - with open('./is_setup', 'w', encoding='utf-8'): - pass + # create ./is_setup + with open("./is_setup", "w", encoding="utf-8"): + pass - return 0 + return 0 -if __name__ == '__main__': - retval = Main() - sys.exit(retval) +if __name__ == "__main__": + retval = Main() + sys.exit(retval) diff --git a/binary_search_tool/test/test_setup_bad.py b/binary_search_tool/test/test_setup_bad.py index cbca3c21..518a69fd 100755 --- a/binary_search_tool/test/test_setup_bad.py +++ b/binary_search_tool/test/test_setup_bad.py @@ -1,20 +1,19 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Emulate test setup that fails (i.e. failed flash to device)""" -from __future__ import print_function import sys def Main(): - return 1 ## False, flashing failure + return 1 ## False, flashing failure -if __name__ == '__main__': - retval = Main() - sys.exit(retval) +if __name__ == "__main__": + retval = Main() + sys.exit(retval) |