Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 #!/usr/bin/env python2 | |
| 2 | |
| 3 import argparse | |
| 4 import os | |
| 5 import signal | |
| 6 import subprocess | |
| 7 import re | |
| 8 import math | |
| 9 | |
| 10 class Runner(object): | |
| 11 def __init__(self, input_cmd, timeout, comma_join, template, find_all): | |
| 12 self._input_cmd = input_cmd | |
| 13 self._timeout = timeout | |
| 14 self._num_tries = 0 | |
| 15 self._comma_join = comma_join | |
| 16 self._template = template | |
| 17 self._find_all = find_all | |
| 18 | |
| 19 def estimate(self, included_ranges): | |
| 20 result = 0 | |
| 21 for i in included_ranges: | |
| 22 if isinstance(i, int): | |
| 23 result += 1 | |
| 24 else: | |
| 25 if i[1] - i[0] > 2: | |
| 26 result += int(math.log(i[1] - i[0], 2)) | |
| 27 else: | |
| 28 result += (i[1] - i[0]) | |
| 29 if self._find_all: | |
| 30 return 2 * result | |
| 31 else: | |
| 32 return result | |
| 33 | |
| 34 def Run(self, included_ranges): | |
| 35 def timeout_handler(signum, frame): | |
| 36 raise RuntimeError("Timeout") | |
|
Jim Stichnoth
2016/07/22 13:35:43
For whatever reason, our scripts prefer 'strings'
manasijm
2016/07/22 15:53:09
Done.
| |
| 37 | |
| 38 self._num_tries += 1 | |
| 39 cmd_addition = '' | |
| 40 for i in included_ranges: | |
| 41 if isinstance(i, int): | |
| 42 range_str = str(i) | |
| 43 else: | |
| 44 range_str = "{start}:{end}".format(start=i[0], end=i[1]) | |
| 45 if self._comma_join: | |
| 46 cmd_addition += "," + range_str | |
| 47 else: | |
| 48 cmd_addition += " -i " + range_str | |
| 49 | |
| 50 if self._template: | |
| 51 cmd = cmd_addition.join(re.split(r'%i' ,self._input_cmd)) | |
| 52 else: | |
| 53 cmd = self._input_cmd + cmd_addition | |
| 54 | |
| 55 print cmd | |
| 56 p = subprocess.Popen(cmd, shell = True, cwd = None, | |
| 57 stdout = subprocess.PIPE, stderr = subprocess.PIPE, env = None) | |
| 58 if self._timeout != -1: | |
| 59 signal.signal(signal.SIGALRM, timeout_handler) | |
| 60 signal.alarm(self._timeout) | |
| 61 | |
| 62 try: | |
| 63 _, _ = p.communicate() | |
| 64 if self._timeout != -1: | |
| 65 signal.alarm(0) | |
| 66 except: | |
| 67 try: | |
| 68 os.kill(p.pid, signal.SIGKILL) | |
| 69 except OSError: | |
| 70 pass | |
| 71 print "Timeout" | |
| 72 return -9 | |
| 73 print "===Return Code===: " + str(p.returncode) | |
| 74 print "===Remaining Steps===: " + str(self.estimate(included_ranges)) | |
|
Jim Stichnoth
2016/07/22 13:35:43
Should probably rephrase the message to convey tha
manasijm
2016/07/22 15:53:09
Done.
| |
| 75 return p.returncode | |
| 76 | |
| 77 def flatten(tree): | |
| 78 if isinstance(tree, list): | |
| 79 result = [] | |
| 80 for node in tree: | |
| 81 result.extend(flatten(node)) | |
| 82 return result | |
| 83 else: | |
| 84 return [tree] # leaf | |
| 85 | |
| 86 def find_crashes(runner, current_interval, include_ranges, find_all): | |
|
Jim Stichnoth
2016/07/22 13:35:43
Maybe find_failures? It finds more than just cras
manasijm
2016/07/22 15:53:09
Done.
| |
| 87 if current_interval[0] == current_interval[1]: | |
| 88 return [] | |
| 89 mid = (current_interval[0] + current_interval[1])/2 | |
| 90 | |
| 91 first_half = (current_interval[0], mid) | |
| 92 second_half = (mid, current_interval[1]) | |
| 93 | |
| 94 exit_code_2 = 0 | |
| 95 | |
| 96 exit_code_1 = runner.Run([first_half] + include_ranges) | |
| 97 if find_all or exit_code_1 == 0: | |
| 98 exit_code_2 = runner.Run([second_half] + include_ranges) | |
| 99 | |
| 100 if exit_code_1 == 0 and exit_code_2 == 0: | |
| 101 # Whole range fails but both halves pass | |
| 102 # So, some conjunction of functions cause a failure, but none individually. | |
| 103 partial_result = flatten(find_crashes(runner, first_half, [second_half] | |
| 104 + include_ranges, find_all)) | |
| 105 # Heavy list concatenation, but this is insignificant compared to the | |
| 106 # process run times | |
| 107 partial_result.extend(flatten(find_crashes(runner, second_half, | |
| 108 partial_result + include_ranges, find_all))) | |
| 109 return [partial_result] | |
| 110 else: | |
| 111 result = [] | |
| 112 if exit_code_1 != 0: | |
| 113 if first_half[1] == first_half[0] + 1: | |
| 114 result.append(first_half[0]) | |
| 115 else: | |
| 116 result.extend(find_crashes(runner, first_half, | |
| 117 include_ranges, find_all)) | |
| 118 if exit_code_2 != 0: | |
| 119 if second_half[1] == second_half[0] + 1: | |
| 120 result.append(second_half[0]) | |
| 121 else: | |
| 122 result.extend(find_crashes(runner, second_half, | |
| 123 include_ranges, find_all)) | |
| 124 return result | |
| 125 | |
| 126 | |
| 127 def main(): | |
| 128 """ | |
| 129 Helper Script for Automating Bisection Debugging | |
| 130 | |
| 131 Example Invocation: | |
| 132 bisection-tool.py --cmd 'bisection-test.py -c 2x3' --end 1000 --timeout 60 | |
| 133 | |
| 134 This will invoke 'bisection-test.py -c 2x3' starting with the range -i 0:1000 | |
| 135 If that fails, it will subdivide the range (initially 0:500 and 500:1000) | |
| 136 recursively to pinpoint a combination of singletons that are needed to cause | |
| 137 the input to return a non zero exit code or timeout. | |
| 138 | |
| 139 For investigating an error in the generated code: | |
| 140 bisection-tool.py --cmd './pydir/szbuild_spec2k.py --run 188.ammp' | |
| 141 | |
| 142 For subzero itself crashing, | |
|
Jim Stichnoth
2016/07/22 13:35:42
Capitalize Subzero
manasijm
2016/07/22 15:53:09
Done.
| |
| 143 bisection-tool.py --cmd 'pnacl-sz -translate-only=' --comma-join | |
|
Jim Stichnoth
2016/07/22 13:35:42
I think there should be only a single space charac
manasijm
2016/07/22 15:53:09
Done.
| |
| 144 The --comma-join flag ensures the ranges are formatted in the manner pnacl-sz | |
| 145 expects. | |
| 146 | |
| 147 If the range specification is not to be appended on the input: | |
| 148 bisection-tool.py --cmd 'echo %i; cmd-main %i; cmd-post' --template | |
| 149 | |
| 150 """ | |
| 151 argparser = argparse.ArgumentParser(main.__doc__) | |
| 152 argparser.add_argument('--cmd', required=True, | |
| 153 dest='cmd', | |
|
Jim Stichnoth
2016/07/22 13:35:43
Probably append this line to the previous line.
manasijm
2016/07/22 15:53:09
Done.
| |
| 154 help='Runnable command') | |
| 155 argparser.add_argument('--start', dest='start', default=0, | |
| 156 help='Start of initial range') | |
| 157 argparser.add_argument('--end', dest='end', default=50000, | |
| 158 help='End of initial range') | |
| 159 argparser.add_argument('--timeout', dest='timeout', default=60, | |
| 160 help='Timeout for each invocation of the input') | |
| 161 | |
| 162 argparser.add_argument('--all', dest='all', action='store_true') | |
|
Jim Stichnoth
2016/07/22 13:35:42
Instead of --all and --no-all, you could do someth
Jim Stichnoth
2016/07/22 13:35:43
All of these args should have help strings.
manasijm
2016/07/22 15:53:09
Done.
| |
| 163 argparser.add_argument('--no-all', dest='all', action='store_false') | |
| 164 argparser.set_defaults(all=True) | |
| 165 | |
| 166 argparser.add_argument('--comma-join', dest='comma_join', action='store_true') | |
| 167 argparser.add_argument('--separate', dest='comma_join', action='store_false') | |
| 168 argparser.set_defaults(comma_join=False) | |
| 169 | |
| 170 argparser.add_argument('--template', dest='template', action='store_true') | |
| 171 argparser.add_argument('--no-template', dest='template', action='store_false') | |
| 172 argparser.set_defaults(template=False) | |
| 173 | |
| 174 args = argparser.parse_args() | |
| 175 | |
| 176 fail_list = [] | |
| 177 | |
| 178 initial_range = (int(args.start), int(args.end)) | |
| 179 timeout = int(args.timeout) | |
| 180 runner = Runner(args.cmd, timeout, args.comma_join, args.template, args.all) | |
| 181 if runner.Run([initial_range]) != 0: | |
| 182 fail_list = find_crashes(runner, initial_range, [], args.all) | |
| 183 else: | |
| 184 print "Pass" # The whole input range works, maybe check subzero build flags? | |
|
Jim Stichnoth
2016/07/22 13:35:43
Another possibility is (if you expected it not to
manasijm
2016/07/22 15:53:09
Done.
| |
| 185 | |
| 186 if fail_list: | |
| 187 print "Failing Functions:" | |
|
Jim Stichnoth
2016/07/22 13:35:43
Here and in the comment above, we aren't necessari
manasijm
2016/07/22 15:53:09
Done.
| |
| 188 for fail in fail_list: | |
| 189 if isinstance(fail, list): | |
| 190 fail.sort() | |
| 191 print '[' + ','.join(str(x) for x in fail) + ']' | |
| 192 else: | |
| 193 print fail | |
| 194 print "Number of tries: " + str(runner._num_tries) | |
| 195 # TODO(manasijm): Pretty print, associate these numbers with filenames | |
|
Jim Stichnoth
2016/07/22 13:35:43
I don't think you need this TODO, since this utili
manasijm
2016/07/22 15:53:09
Done.
| |
| 196 | |
| 197 if __name__ == '__main__': | |
| 198 main() | |
|
Jim Stichnoth
2016/07/22 13:35:43
Indent this only 2 characters?
manasijm
2016/07/22 15:53:09
Done.
| |
| OLD | NEW |