248 lines
		
	
	
		
			8.2 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			248 lines
		
	
	
		
			8.2 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):
 | |
|             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)
 | |
| 
 | |
|     
 |