248 lines
8.3 KiB
Python
248 lines
8.3 KiB
Python
|
import re
|
||
|
import sys
|
||
|
import parser
|
||
|
|
||
|
d={}
|
||
|
# d is the dictionary of unittest changes, keyed to the old name
|
||
|
# used by unittest.
|
||
|
# d[old][0] is the new replacement function.
|
||
|
# d[old][1] is the operator you will substitute, or '' if there is none.
|
||
|
# d[old][2] is the possible number of arguments to the unittest
|
||
|
# function.
|
||
|
|
||
|
# Old Unittest Name new name operator # of args
|
||
|
d['assertRaises'] = ('raises', '', ['Any'])
|
||
|
d['fail'] = ('raise AssertionError', '', [0,1])
|
||
|
d['assert_'] = ('assert', '', [1,2])
|
||
|
d['failIf'] = ('assert not', '', [1,2])
|
||
|
d['assertEqual'] = ('assert', ' ==', [2,3])
|
||
|
d['failIfEqual'] = ('assert not', ' ==', [2,3])
|
||
|
d['assertIn'] = ('assert', ' in', [2,3])
|
||
|
d['assertNotIn'] = ('assert', ' not in', [2,3])
|
||
|
d['assertNotEqual'] = ('assert', ' !=', [2,3])
|
||
|
d['failUnlessEqual'] = ('assert', ' ==', [2,3])
|
||
|
d['assertAlmostEqual'] = ('assert round', ' ==', [2,3,4])
|
||
|
d['failIfAlmostEqual'] = ('assert not round', ' ==', [2,3,4])
|
||
|
d['assertNotAlmostEqual'] = ('assert round', ' !=', [2,3,4])
|
||
|
d['failUnlessAlmostEquals'] = ('assert round', ' ==', [2,3,4])
|
||
|
|
||
|
# the list of synonyms
|
||
|
d['failUnlessRaises'] = d['assertRaises']
|
||
|
d['failUnless'] = d['assert_']
|
||
|
d['assertEquals'] = d['assertEqual']
|
||
|
d['assertNotEquals'] = d['assertNotEqual']
|
||
|
d['assertAlmostEquals'] = d['assertAlmostEqual']
|
||
|
d['assertNotAlmostEquals'] = d['assertNotAlmostEqual']
|
||
|
|
||
|
# set up the regular expressions we will need
|
||
|
leading_spaces = re.compile(r'^(\s*)') # this never fails
|
||
|
|
||
|
pat = ''
|
||
|
for k in d.keys(): # this complicated pattern to match all unittests
|
||
|
pat += '|' + r'^(\s*)' + 'self.' + k + r'\(' # \tself.whatever(
|
||
|
|
||
|
old_names = re.compile(pat[1:])
|
||
|
linesep='\n' # nobody will really try to convert files not read
|
||
|
# in text mode, will they?
|
||
|
|
||
|
|
||
|
def blocksplitter(fp):
|
||
|
'''split a file into blocks that are headed by functions to rename'''
|
||
|
|
||
|
blocklist = []
|
||
|
blockstring = ''
|
||
|
|
||
|
for line in fp:
|
||
|
interesting = old_names.match(line)
|
||
|
if interesting :
|
||
|
if blockstring:
|
||
|
blocklist.append(blockstring)
|
||
|
blockstring = line # reset the block
|
||
|
else:
|
||
|
blockstring += line
|
||
|
|
||
|
blocklist.append(blockstring)
|
||
|
return blocklist
|
||
|
|
||
|
def rewrite_utest(block):
|
||
|
'''rewrite every block to use the new utest functions'''
|
||
|
|
||
|
'''returns the rewritten unittest, unless it ran into problems,
|
||
|
in which case it just returns the block unchanged.
|
||
|
'''
|
||
|
utest = old_names.match(block)
|
||
|
|
||
|
if not utest:
|
||
|
return block
|
||
|
|
||
|
old = utest.group(0).lstrip()[5:-1] # the name we want to replace
|
||
|
new = d[old][0] # the name of the replacement function
|
||
|
op = d[old][1] # the operator you will use , or '' if there is none.
|
||
|
possible_args = d[old][2] # a list of the number of arguments the
|
||
|
# unittest function could possibly take.
|
||
|
|
||
|
if possible_args == ['Any']: # just rename assertRaises & friends
|
||
|
return re.sub('self.'+old, new, block)
|
||
|
|
||
|
message_pos = possible_args[-1]
|
||
|
# the remaining unittests can have an optional message to print
|
||
|
# when they fail. It is always the last argument to the function.
|
||
|
|
||
|
try:
|
||
|
indent, argl, trailer = decompose_unittest(old, block)
|
||
|
|
||
|
except SyntaxError: # but we couldn't parse it!
|
||
|
return block
|
||
|
|
||
|
argnum = len(argl)
|
||
|
if argnum not in possible_args:
|
||
|
# sanity check - this one isn't real either
|
||
|
return block
|
||
|
|
||
|
elif argnum == message_pos:
|
||
|
message = argl[-1]
|
||
|
argl = argl[:-1]
|
||
|
else:
|
||
|
message = None
|
||
|
|
||
|
if argnum is 0 or (argnum is 1 and argnum is message_pos): #unittest fail()
|
||
|
string = ''
|
||
|
if message:
|
||
|
message = ' ' + message
|
||
|
|
||
|
elif message_pos is 4: # assertAlmostEqual & friends
|
||
|
try:
|
||
|
pos = argl[2].lstrip()
|
||
|
except IndexError:
|
||
|
pos = '7' # default if none is specified
|
||
|
string = '(%s -%s, %s)%s 0' % (argl[0], argl[1], pos, op )
|
||
|
|
||
|
else: # assert_, assertEquals and all the rest
|
||
|
string = ' ' + op.join(argl)
|
||
|
|
||
|
if message:
|
||
|
string = string + ',' + message
|
||
|
|
||
|
return indent + new + string + trailer
|
||
|
|
||
|
def decompose_unittest(old, block):
|
||
|
'''decompose the block into its component parts'''
|
||
|
|
||
|
''' returns indent, arglist, trailer
|
||
|
indent -- the indentation
|
||
|
arglist -- the arguments to the unittest function
|
||
|
trailer -- any extra junk after the closing paren, such as #commment
|
||
|
'''
|
||
|
|
||
|
indent = re.match(r'(\s*)', block).group()
|
||
|
pat = re.search('self.' + old + r'\(', block)
|
||
|
|
||
|
args, trailer = get_expr(block[pat.end():], ')')
|
||
|
arglist = break_args(args, [])
|
||
|
|
||
|
if arglist == ['']: # there weren't any
|
||
|
return indent, [], trailer
|
||
|
|
||
|
for i in range(len(arglist)):
|
||
|
try:
|
||
|
parser.expr(arglist[i].lstrip('\t '))
|
||
|
except SyntaxError:
|
||
|
if i == 0:
|
||
|
arglist[i] = '(' + arglist[i] + ')'
|
||
|
else:
|
||
|
arglist[i] = ' (' + arglist[i] + ')'
|
||
|
|
||
|
return indent, arglist, trailer
|
||
|
|
||
|
def break_args(args, arglist):
|
||
|
'''recursively break a string into a list of arguments'''
|
||
|
try:
|
||
|
first, rest = get_expr(args, ',')
|
||
|
if not rest:
|
||
|
return arglist + [first]
|
||
|
else:
|
||
|
return [first] + break_args(rest, arglist)
|
||
|
except SyntaxError:
|
||
|
return arglist + [args]
|
||
|
|
||
|
def get_expr(s, char):
|
||
|
'''split a string into an expression, and the rest of the string'''
|
||
|
|
||
|
pos=[]
|
||
|
for i in range(len(s)):
|
||
|
if s[i] == char:
|
||
|
pos.append(i)
|
||
|
if pos == []:
|
||
|
raise SyntaxError # we didn't find the expected char. Ick.
|
||
|
|
||
|
for p in pos:
|
||
|
# make the python parser do the hard work of deciding which comma
|
||
|
# splits the string into two expressions
|
||
|
try:
|
||
|
parser.expr('(' + s[:p] + ')')
|
||
|
return s[:p], s[p+1:]
|
||
|
except SyntaxError: # It's not an expression yet
|
||
|
pass
|
||
|
raise SyntaxError # We never found anything that worked.
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
|
||
|
import sys
|
||
|
import py
|
||
|
|
||
|
usage = "usage: %prog [-s [filename ...] | [-i | -c filename ...]]"
|
||
|
optparser = py.compat.optparse.OptionParser(usage)
|
||
|
|
||
|
def select_output (option, opt, value, optparser, **kw):
|
||
|
if hasattr(optparser, 'output'):
|
||
|
optparser.error(
|
||
|
'Cannot combine -s -i and -c options. Use one only.')
|
||
|
else:
|
||
|
optparser.output = kw['output']
|
||
|
|
||
|
optparser.add_option("-s", "--stdout", action="callback",
|
||
|
callback=select_output,
|
||
|
callback_kwargs={'output':'stdout'},
|
||
|
help="send your output to stdout")
|
||
|
|
||
|
optparser.add_option("-i", "--inplace", action="callback",
|
||
|
callback=select_output,
|
||
|
callback_kwargs={'output':'inplace'},
|
||
|
help="overwrite files in place")
|
||
|
|
||
|
optparser.add_option("-c", "--copy", action="callback",
|
||
|
callback=select_output,
|
||
|
callback_kwargs={'output':'copy'},
|
||
|
help="copy files ... fn.py --> fn_cp.py")
|
||
|
|
||
|
options, args = optparser.parse_args()
|
||
|
|
||
|
output = getattr(optparser, 'output', 'stdout')
|
||
|
|
||
|
if output in ['inplace', 'copy'] and not args:
|
||
|
optparser.error(
|
||
|
'-i and -c option require at least one filename')
|
||
|
|
||
|
if not args:
|
||
|
s = ''
|
||
|
for block in blocksplitter(sys.stdin.read()):
|
||
|
s += rewrite_utest(block)
|
||
|
sys.stdout.write(s)
|
||
|
|
||
|
else:
|
||
|
for infilename in args: # no error checking to see if we can open, etc.
|
||
|
infile = file(infilename)
|
||
|
s = ''
|
||
|
for block in blocksplitter(infile):
|
||
|
s += rewrite_utest(block)
|
||
|
if output == 'inplace':
|
||
|
outfile = file(infilename, 'w+')
|
||
|
elif output == 'copy': # yes, just go clobber any existing .cp
|
||
|
outfile = file (infilename[:-3]+ '_cp.py', 'w+')
|
||
|
else:
|
||
|
outfile = sys.stdout
|
||
|
|
||
|
outfile.write(s)
|
||
|
|
||
|
|