diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-11-13 02:20:05 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-11-13 02:20:05 +0000 |
commit | cec5bc676dfc0ade1b32f3873e93a3bd2c30e390 (patch) | |
tree | 77dc031614745bb406dbd90cea9a082a1b5cdd54 /binary_search_tool/bisect_driver.py | |
parent | 773bb66b7b5fab0b5f14d66455274040063e14bf (diff) | |
parent | 517424dcc11380511bc34f4a081f119104ff9e80 (diff) | |
download | toolchain-utils-android14-d1-s4-release.tar.gz |
Snap for 9286410 from 517424dcc11380511bc34f4a081f119104ff9e80 to udc-d1-releaseandroid-14.0.0_r9android-14.0.0_r8android-14.0.0_r7android-14.0.0_r6android-14.0.0_r5android-14.0.0_r4android-14.0.0_r3android-14.0.0_r12android-14.0.0_r11android-14.0.0_r10android14-d1-s7-releaseandroid14-d1-s6-releaseandroid14-d1-s5-releaseandroid14-d1-s4-releaseandroid14-d1-s3-releaseandroid14-d1-s2-releaseandroid14-d1-s1-releaseandroid14-d1-release
Change-Id: I0dfd9fe9a88cd3daf076b0fb77b69e3138c08e6c
Diffstat (limited to 'binary_search_tool/bisect_driver.py')
-rw-r--r-- | binary_search_tool/bisect_driver.py | 653 |
1 files changed, 333 insertions, 320 deletions
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) |