1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-04-28 01:00:52 -07:00
emacs/mps/tool/p4-bisect
Gareth Rees f6eb7789ac New program p4-bisect finds, by binary search, the change that introduced a bug.
Copied from Perforce
 Change: 185519
 ServerID: perforce.ravenbrook.com
2014-04-14 16:39:55 +01:00

155 lines
4.7 KiB
Python

#!/usr/bin/env python
#
#
# Ravenbrook
# <http://www.ravenbrook.com/>
#
# P4-BISECT -- FIND CHANGE THAT INTRODUCED A BUG
#
# Gareth Rees, Ravenbrook Limited, 2014-04-14
#
#
# 1. INTRODUCTION
#
# This script automates (or partly automates) the process of finding,
# by binary search, the change that introduced a bug.
#
# The interface is modelled closely on git-bisect(1).
import argparse
from functools import partial
import json
from os import unlink
import p4
import subprocess
import sys
BISECT_FILE = '.p4-bisect'
class State(object):
def __init__(self, **d):
self.filespec = d['filespec']
self.changes = d['changes']
if 'current' in d:
self.current = d['current']
@classmethod
def load(cls):
with open(BISECT_FILE, 'r') as f:
return cls(**json.load(f))
def save(self):
with open(BISECT_FILE, 'w') as f:
json.dump(vars(self), f)
def update(self):
n = len(self.changes)
if n == 0:
print("no changes remaining.".format(**vars(self)))
elif n == 1:
print("{} change remaining: {}.".format(n, self.changes[0]))
elif n == 2:
print("{} changes remaining: [{}, {}]."
.format(n, self.changes[0], self.changes[-1]))
else:
print("{} changes remaining: [{}, ..., {}]."
.format(n, self.changes[0], self.changes[-1]))
if n > 0:
self.current = self.changes[n // 2]
print("Syncing to changelevel {current}.".format(**vars(self)))
p4.do('update', '{filespec}@{current}'.format(**vars(self)))
self.save()
def help(parser, args):
parser.print_help()
def start(args):
changes = sorted(int(c['change']) for c in p4.run('changes', args.filespec))
if not changes:
parser.error("No changes for {filespec}".format(**vars(args)))
if args.first is None:
args.first = changes[0]
if args.last is None:
args.last = changes[-1]
state = State(filespec=args.filespec,
changes=[c for c in changes if args.first <= c <= args.last])
state.update()
def good(args):
state = State.load()
print("Change {current} good.".format(**vars(state)))
state.changes = [c for c in state.changes if c > state.current]
state.update()
def bad(args):
state = State.load()
print("Change {current} bad.".format(**vars(state)))
state.changes = [c for c in state.changes if c < state.current]
state.update()
def skip(args):
state = State.load()
print("Skipping change {current}.".format(**vars(state)))
state.changes.remove(state.current)
state.update()
def reset(args):
state = State.load()
p4.do('update', state.filespec)
unlink(BISECT_FILE)
def run(args):
while True:
state = State.load()
if not state.changes:
break
result = subprocess.call([args.cmd] + args.args)
if result == 0:
good(None)
elif result == 125:
skip(None)
elif 0 < result < 128:
bad(None)
else:
exit(result)
def main(argv):
parser = argparse.ArgumentParser(prog='p4-bisect')
subparsers = parser.add_subparsers()
a = subparsers.add_parser
help_parser = a('help', help='show this help message')
help_parser.set_defaults(func=partial(help, help_parser))
start_parser = a('start', help='start a p4-bisect session')
aa = start_parser.add_argument
start_parser.add_argument('-f', '--filespec', default='...',
help='filespec to search')
start_parser.add_argument('first', nargs='?', type=int,
help='earliest changelevel to examine')
start_parser.add_argument('last', nargs='?', type=int,
help='latest changelevel to examine')
start_parser.set_defaults(func=start)
good_parser = a('good', help='declare current revision good')
good_parser.set_defaults(func=good)
bad_parser = a('bad', help='declare current revision bad')
bad_parser.set_defaults(func=bad)
reset_parser = a('reset', help='finish p4-bisect session')
reset_parser.set_defaults(func=reset)
run_parser = a('run', help='run p4-bisect session automatically')
run_parser.add_argument('cmd',
help='command that determines if current '
'changelevel is good or bad')
run_parser.add_argument('args', nargs=argparse.REMAINDER,
help='arguments to pass to cmd')
run_parser.set_defaults(func=run)
args = parser.parse_args(argv[1:])
args.func(args)
if __name__ == '__main__':
main(sys.argv)