Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python2 | |
| 2 import argparse | |
| 3 import os | |
| 4 import signal | |
| 5 import subprocess | |
| 6 | |
| 7 class Runner(object): | |
| 8 def __init__(self, input_cmd, timeout, comma_join = False): | |
| 9 self._input_cmd = input_cmd | |
| 10 self._timeout = timeout | |
| 11 self._num_tries = 0 | |
| 12 self._comma_join = comma_join | |
| 13 | |
| 14 def Run(self, included_ranges): | |
| 15 def timeout_handler(signum, frame): | |
| 16 raise RuntimeError("Timeout") | |
| 17 | |
| 18 self._num_tries += 1 | |
| 19 cmd = self._input_cmd | |
| 20 for i in included_ranges: | |
| 21 if isinstance(i, int): | |
|
John
2016/07/20 22:23:05
I would always pass a range. If you want a single
| |
| 22 range_str = str(i) | |
| 23 else: | |
| 24 range_str = "{start}:{end}".format(start=i[0], end=i[1]) | |
| 25 if self._comma_join: | |
| 26 cmd += "," + range_str | |
| 27 else: | |
| 28 cmd += " -i " + range_str | |
| 29 print cmd | |
| 30 p = subprocess.Popen(cmd, shell = True, cwd = None, | |
| 31 stdout = subprocess.PIPE, stderr = subprocess.PIPE, env = None) | |
| 32 if self._timeout != -1: | |
| 33 signal.signal(signal.SIGALRM, timeout_handler) | |
| 34 signal.alarm(self._timeout) | |
| 35 | |
| 36 try: | |
| 37 _, _ = p.communicate() | |
| 38 if self._timeout != -1: | |
| 39 signal.alarm(0) | |
| 40 except: | |
| 41 try: | |
| 42 os.kill(p.pid, signal.SIGKILL) | |
| 43 except OSError: | |
| 44 pass | |
| 45 print "Timeout" | |
| 46 return -9 | |
| 47 print "Return Code: " + str(p.returncode) | |
| 48 return p.returncode | |
| 49 | |
| 50 def flatten(tree): | |
| 51 if isinstance(tree, list): | |
| 52 result = [] | |
| 53 for node in tree: | |
| 54 result.extend(flatten(node)) | |
| 55 return result | |
| 56 else: | |
| 57 return [tree] # leaf | |
| 58 | |
| 59 def find_crashes(runner, current_interval, include_ranges, find_all): | |
| 60 if current_interval[0] == current_interval[1]: | |
| 61 return [] | |
| 62 mid = (current_interval[0] + current_interval[1])/2 | |
| 63 | |
| 64 first_half = (current_interval[0], mid) | |
| 65 second_half = (mid, current_interval[1]) | |
| 66 | |
| 67 exit_code_2 = 0 | |
| 68 | |
| 69 exit_code_1 = runner.Run([first_half] + include_ranges) | |
| 70 if find_all or exit_code_1 == 0: | |
| 71 exit_code_2 = runner.Run([second_half] + include_ranges) | |
| 72 | |
| 73 if exit_code_1 == 0 and exit_code_2 == 0: | |
| 74 # Whole range fails but both halves pass | |
| 75 # So, some conjunction of functions cause a failure, but none individually. | |
| 76 partial_result = flatten(find_crashes(runner, first_half, [second_half] | |
|
John
2016/07/20 22:23:05
you **REALLY** shouldn't need to flatten your resu
| |
| 77 + include_ranges, find_all)) | |
| 78 # Heavy list concatenation, but this is insignificant compared to the | |
| 79 # process run times | |
| 80 partial_result.extend(flatten(find_crashes(runner, second_half, | |
| 81 partial_result + include_ranges, find_all))) | |
| 82 return [partial_result] | |
|
John
2016/07/20 22:23:05
why do you return partial result wrapped in a list
| |
| 83 else: | |
| 84 result = [] | |
| 85 if exit_code_1 != 0: | |
|
John
2016/07/20 22:23:05
I don't see you using "find_all" here, so you migh
| |
| 86 if first_half[1] == first_half[0] + 1: | |
| 87 result.append(first_half[0]) | |
| 88 else: | |
| 89 result.extend(find_crashes(runner, first_half, | |
| 90 include_ranges, find_all)) | |
| 91 if exit_code_2 != 0: | |
| 92 if second_half[1] == second_half[0] + 1: | |
| 93 result.append(second_half[0]) | |
| 94 else: | |
| 95 result.extend(find_crashes(runner, second_half, | |
| 96 include_ranges, find_all)) | |
| 97 return result | |
| 98 | |
| 99 | |
| 100 def main(): | |
| 101 desc = 'Bisection debugging helper script' | |
| 102 argparser = argparse.ArgumentParser(description=desc) | |
| 103 argparser.add_argument('--cmd', required=True, | |
| 104 dest='cmd', | |
| 105 help='Runnable command') | |
| 106 argparser.add_argument('--start', dest='start', default=0, | |
| 107 help='Start of initial range') | |
| 108 argparser.add_argument('--end', dest='end', default=50000, | |
| 109 help='End of initial range') | |
| 110 argparser.add_argument('--timeout', dest='timeout', default=60, | |
| 111 help='Timeout for each invocation of the input') | |
| 112 | |
| 113 argparser.add_argument('--all', dest='all', action='store_true') | |
| 114 argparser.add_argument('--no-all', dest='all', action='store_false') | |
| 115 argparser.set_defaults(all=True) | |
| 116 | |
| 117 argparser.add_argument('--comma-join', dest='comma_join', action='store_true') | |
| 118 argparser.add_argument('--separate', dest='comma_join', action='store_false') | |
| 119 argparser.set_defaults(all=True) | |
| 120 | |
| 121 args = argparser.parse_args() | |
| 122 | |
| 123 fail_list = [] | |
| 124 | |
| 125 initial_range = (int(args.start), int(args.end)) | |
| 126 timeout = int(args.timeout) | |
| 127 runner = Runner(args.cmd, timeout, args.comma_join) | |
| 128 if runner.Run([initial_range]) != 0: | |
|
John
2016/07/20 22:23:05
why not
crashes = find_crashes(runner, initial_ra
| |
| 129 fail_list = find_crashes(runner, initial_range, [], args.all) | |
| 130 else: | |
| 131 print "Pass" # The whole input range works, maybe check subzero build flags? | |
| 132 | |
| 133 if fail_list: | |
| 134 print "Failing Functions:" | |
| 135 for fail in fail_list: | |
| 136 print fail | |
| 137 print "Number of tries: " + str(runner._num_tries) | |
| 138 # TODO(manasijm): Pretty print, associate these numbers with filenames | |
| 139 | |
| 140 if __name__ == '__main__': | |
| 141 main() | |
| OLD | NEW |