| 1 | #!/usr/bin/env python | 
|---|
| 2 | # use git bisect to work out what commit caused a test failure | 
|---|
| 3 | # Copyright Andrew Tridgell 2010 | 
|---|
| 4 | # released under GNU GPL v3 or later | 
|---|
| 5 |  | 
|---|
| 6 |  | 
|---|
| 7 | from subprocess import call, check_call, Popen, PIPE | 
|---|
| 8 | import os, tempfile, sys | 
|---|
| 9 | from optparse import OptionParser | 
|---|
| 10 |  | 
|---|
| 11 | parser = OptionParser() | 
|---|
| 12 | parser.add_option("", "--good", help="known good revision (default HEAD~100)", default='HEAD~100') | 
|---|
| 13 | parser.add_option("", "--bad", help="known bad revision (default HEAD)", default='HEAD') | 
|---|
| 14 | parser.add_option("", "--skip-build-errors", help="skip revision where make fails", | 
|---|
| 15 | action='store_true', default=False) | 
|---|
| 16 | parser.add_option("", "--autogen", help="run autogen before each build",action="store_true", default=False) | 
|---|
| 17 | parser.add_option("", "--autogen-command", help="command to use for autogen (default ./autogen.sh)", | 
|---|
| 18 | type='str', default="./autogen.sh") | 
|---|
| 19 | parser.add_option("", "--configure", help="run configure.developer before each build", | 
|---|
| 20 | action="store_true", default=False) | 
|---|
| 21 | parser.add_option("", "--configure-command", help="the command for configure (default ./configure.developer)", | 
|---|
| 22 | type='str', default="./configure.developer") | 
|---|
| 23 | parser.add_option("", "--build-command", help="the command to build the tree (default 'make -j')", | 
|---|
| 24 | type='str', default="make -j") | 
|---|
| 25 | parser.add_option("", "--test-command", help="the command to test the tree (default 'make test')", | 
|---|
| 26 | type='str', default="make test") | 
|---|
| 27 | parser.add_option("", "--clean", help="run make clean before each build", | 
|---|
| 28 | action="store_true", default=False) | 
|---|
| 29 |  | 
|---|
| 30 |  | 
|---|
| 31 | (opts, args) = parser.parse_args() | 
|---|
| 32 |  | 
|---|
| 33 |  | 
|---|
| 34 | def run_cmd(cmd, dir=".", show=True, output=False, checkfail=True): | 
|---|
| 35 | if show: | 
|---|
| 36 | print("Running: '%s' in '%s'" % (cmd, dir)) | 
|---|
| 37 | if output: | 
|---|
| 38 | return Popen([cmd], shell=True, stdout=PIPE, cwd=dir).communicate()[0] | 
|---|
| 39 | elif checkfail: | 
|---|
| 40 | return check_call(cmd, shell=True, cwd=dir) | 
|---|
| 41 | else: | 
|---|
| 42 | return call(cmd, shell=True, cwd=dir) | 
|---|
| 43 |  | 
|---|
| 44 | def find_git_root(): | 
|---|
| 45 | '''get to the top of the git repo''' | 
|---|
| 46 | p=os.getcwd() | 
|---|
| 47 | while p != '/': | 
|---|
| 48 | if os.path.isdir(os.path.join(p, ".git")): | 
|---|
| 49 | return p | 
|---|
| 50 | p = os.path.abspath(os.path.join(p, '..')) | 
|---|
| 51 | return None | 
|---|
| 52 |  | 
|---|
| 53 | cwd = os.getcwd() | 
|---|
| 54 | gitroot = find_git_root() | 
|---|
| 55 |  | 
|---|
| 56 | # create a bisect script | 
|---|
| 57 | f = tempfile.NamedTemporaryFile(delete=False) | 
|---|
| 58 | f.write("set -x\n") | 
|---|
| 59 | f.write("cd %s || exit 125\n" % cwd) | 
|---|
| 60 | if opts.autogen: | 
|---|
| 61 | f.write("%s || exit 125\n" % opts.autogen_command) | 
|---|
| 62 | if opts.configure: | 
|---|
| 63 | f.write("%s || exit 125\n" % opts.configure_command) | 
|---|
| 64 | if opts.clean: | 
|---|
| 65 | f.write("make clean || exit 125\n") | 
|---|
| 66 | if opts.skip_build_errors: | 
|---|
| 67 | build_err = 125 | 
|---|
| 68 | else: | 
|---|
| 69 | build_err = 1 | 
|---|
| 70 | f.write("%s || exit %u\n" % (opts.build_command, build_err)) | 
|---|
| 71 | f.write("%s || exit 1\n" % opts.test_command) | 
|---|
| 72 | f.write("exit 0\n") | 
|---|
| 73 | f.close() | 
|---|
| 74 |  | 
|---|
| 75 | def cleanup(): | 
|---|
| 76 | run_cmd("git bisect reset", dir=gitroot) | 
|---|
| 77 | os.unlink(f.name) | 
|---|
| 78 | sys.exit(-1) | 
|---|
| 79 |  | 
|---|
| 80 | # run bisect | 
|---|
| 81 | ret = -1 | 
|---|
| 82 | try: | 
|---|
| 83 | run_cmd("git bisect reset", dir=gitroot, show=False, checkfail=False) | 
|---|
| 84 | run_cmd("git bisect start %s %s --" % (opts.bad, opts.good), dir=gitroot) | 
|---|
| 85 | ret = run_cmd("git bisect run bash %s" % f.name, dir=gitroot, show=True, checkfail=False) | 
|---|
| 86 | except KeyboardInterrupt: | 
|---|
| 87 | print("Cleaning up") | 
|---|
| 88 | cleanup() | 
|---|
| 89 | except Exception, reason: | 
|---|
| 90 | print("Failed bisect: %s" % reason) | 
|---|
| 91 | cleanup() | 
|---|
| 92 |  | 
|---|
| 93 | run_cmd("git bisect reset", dir=gitroot) | 
|---|
| 94 | os.unlink(f.name) | 
|---|
| 95 | sys.exit(ret) | 
|---|