1#------------------------------------------------------------------------- 2# CxxTest: A lightweight C++ unit testing library. 3# Copyright (c) 2008 Sandia Corporation. 4# This software is distributed under the LGPL License v2.1 5# For more information, see the COPYING file in the top CxxTest directory. 6# Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, 7# the U.S. Government retains certain rights in this software. 8#------------------------------------------------------------------------- 9 10 11 12import codecs 13import re 14#import sys 15#import getopt 16#import glob 17from cxxtest.cxxtest_misc import abort 18 19# Global variables 20suites = [] 21suite = None 22inBlock = 0 23options=None 24 25def scanInputFiles(files, _options): 26 '''Scan all input files for test suites''' 27 global options 28 options=_options 29 for file in files: 30 scanInputFile(file) 31 global suites 32 if len(suites) is 0 and not options.root: 33 abort( 'No tests defined' ) 34 return [options,suites] 35 36lineCont_re = re.compile('(.*)\\\s*$') 37def scanInputFile(fileName): 38 '''Scan single input file for test suites''' 39 # mode 'rb' is problematic in python3 - byte arrays don't behave the same as 40 # strings. 41 # As far as the choice of the default encoding: utf-8 chews through 42 # everything that the previous ascii codec could, plus most of new code. 43 # TODO: figure out how to do this properly - like autodetect encoding from 44 # file header. 45 file = codecs.open(fileName, mode='r', encoding='utf-8') 46 prev = "" 47 lineNo = 0 48 contNo = 0 49 while 1: 50 line = file.readline() 51 if not line: 52 break 53 lineNo += 1 54 55 m = lineCont_re.match(line) 56 if m: 57 prev += m.group(1) + " " 58 contNo += 1 59 else: 60 scanInputLine( fileName, lineNo - contNo, prev + line ) 61 contNo = 0 62 prev = "" 63 if contNo: 64 scanInputLine( fileName, lineNo - contNo, prev + line ) 65 66 closeSuite() 67 file.close() 68 69def scanInputLine( fileName, lineNo, line ): 70 '''Scan single input line for interesting stuff''' 71 scanLineForExceptionHandling( line ) 72 scanLineForStandardLibrary( line ) 73 74 scanLineForSuiteStart( fileName, lineNo, line ) 75 76 global suite 77 if suite: 78 scanLineInsideSuite( suite, lineNo, line ) 79 80def scanLineInsideSuite( suite, lineNo, line ): 81 '''Analyze line which is part of a suite''' 82 global inBlock 83 if lineBelongsToSuite( suite, lineNo, line ): 84 scanLineForTest( suite, lineNo, line ) 85 scanLineForCreate( suite, lineNo, line ) 86 scanLineForDestroy( suite, lineNo, line ) 87 88def lineBelongsToSuite( suite, lineNo, line ): 89 '''Returns whether current line is part of the current suite. 90 This can be false when we are in a generated suite outside of CXXTEST_CODE() blocks 91 If the suite is generated, adds the line to the list of lines''' 92 if not suite['generated']: 93 return 1 94 95 global inBlock 96 if not inBlock: 97 inBlock = lineStartsBlock( line ) 98 if inBlock: 99 inBlock = addLineToBlock( suite, lineNo, line ) 100 return inBlock 101 102 103std_re = re.compile( r"\b(std\s*::|CXXTEST_STD|using\s+namespace\s+std\b|^\s*\#\s*include\s+<[a-z0-9]+>)" ) 104def scanLineForStandardLibrary( line ): 105 '''Check if current line uses standard library''' 106 global options 107 if not options.haveStandardLibrary and std_re.search(line): 108 if not options.noStandardLibrary: 109 options.haveStandardLibrary = 1 110 111exception_re = re.compile( r"\b(throw|try|catch|TSM?_ASSERT_THROWS[A-Z_]*)\b" ) 112def scanLineForExceptionHandling( line ): 113 '''Check if current line uses exception handling''' 114 global options 115 if not options.haveExceptionHandling and exception_re.search(line): 116 if not options.noExceptionHandling: 117 options.haveExceptionHandling = 1 118 119classdef = '(?:::\s*)?(?:\w+\s*::\s*)*\w+' 120baseclassdef = '(?:public|private|protected)\s+%s' % (classdef,) 121general_suite = r"\bclass\s+(%s)\s*:(?:\s*%s\s*,)*\s*public\s+" \ 122 % (classdef, baseclassdef,) 123testsuite = '(?:(?:::)?\s*CxxTest\s*::\s*)?TestSuite' 124suites_re = { re.compile( general_suite + testsuite ) : None } 125generatedSuite_re = re.compile( r'\bCXXTEST_SUITE\s*\(\s*(\w*)\s*\)' ) 126def scanLineForSuiteStart( fileName, lineNo, line ): 127 '''Check if current line starts a new test suite''' 128 for i in list(suites_re.items()): 129 m = i[0].search( line ) 130 if m: 131 suite = startSuite( m.group(1), fileName, lineNo, 0 ) 132 if i[1] is not None: 133 for test in i[1]['tests']: 134 addTest(suite, test['name'], test['line']) 135 break 136 m = generatedSuite_re.search( line ) 137 if m: 138 sys.stdout.write( "%s:%s: Warning: Inline test suites are deprecated.\n" % (fileName, lineNo) ) 139 startSuite( m.group(1), fileName, lineNo, 1 ) 140 141def startSuite( name, file, line, generated ): 142 '''Start scanning a new suite''' 143 global suite 144 closeSuite() 145 object_name = name.replace(':',"_") 146 suite = { 'name' : name, 147 'file' : file, 148 'cfile' : cstr(file), 149 'line' : line, 150 'generated' : generated, 151 'object' : 'suite_%s' % object_name, 152 'dobject' : 'suiteDescription_%s' % object_name, 153 'tlist' : 'Tests_%s' % object_name, 154 'tests' : [], 155 'lines' : [] } 156 suites_re[re.compile( general_suite + name )] = suite 157 return suite 158 159def lineStartsBlock( line ): 160 '''Check if current line starts a new CXXTEST_CODE() block''' 161 return re.search( r'\bCXXTEST_CODE\s*\(', line ) is not None 162 163test_re = re.compile( r'^([^/]|/[^/])*\bvoid\s+([Tt]est\w+)\s*\(\s*(void)?\s*\)' ) 164def scanLineForTest( suite, lineNo, line ): 165 '''Check if current line starts a test''' 166 m = test_re.search( line ) 167 if m: 168 addTest( suite, m.group(2), lineNo ) 169 170def addTest( suite, name, line ): 171 '''Add a test function to the current suite''' 172 test = { 'name' : name, 173 'suite' : suite, 174 'class' : 'TestDescription_%s_%s' % (suite['object'], name), 175 'object' : 'testDescription_%s_%s' % (suite['object'], name), 176 'line' : line, 177 } 178 suite['tests'].append( test ) 179 180def addLineToBlock( suite, lineNo, line ): 181 '''Append the line to the current CXXTEST_CODE() block''' 182 line = fixBlockLine( suite, lineNo, line ) 183 line = re.sub( r'^.*\{\{', '', line ) 184 185 e = re.search( r'\}\}', line ) 186 if e: 187 line = line[:e.start()] 188 suite['lines'].append( line ) 189 return e is None 190 191def fixBlockLine( suite, lineNo, line): 192 '''Change all [E]TS_ macros used in a line to _[E]TS_ macros with the correct file/line''' 193 return re.sub( r'\b(E?TSM?_(ASSERT[A-Z_]*|FAIL))\s*\(', 194 r'_\1(%s,%s,' % (suite['cfile'], lineNo), 195 line, 0 ) 196 197create_re = re.compile( r'\bstatic\s+\w+\s*\*\s*createSuite\s*\(\s*(void)?\s*\)' ) 198def scanLineForCreate( suite, lineNo, line ): 199 '''Check if current line defines a createSuite() function''' 200 if create_re.search( line ): 201 addSuiteCreateDestroy( suite, 'create', lineNo ) 202 203destroy_re = re.compile( r'\bstatic\s+void\s+destroySuite\s*\(\s*\w+\s*\*\s*\w*\s*\)' ) 204def scanLineForDestroy( suite, lineNo, line ): 205 '''Check if current line defines a destroySuite() function''' 206 if destroy_re.search( line ): 207 addSuiteCreateDestroy( suite, 'destroy', lineNo ) 208 209def cstr( s ): 210 '''Convert a string to its C representation''' 211 return '"' + s.replace( '\\', '\\\\' ) + '"' 212 213 214def addSuiteCreateDestroy( suite, which, line ): 215 '''Add createSuite()/destroySuite() to current suite''' 216 if which in suite: 217 abort( '%s:%s: %sSuite() already declared' % ( suite['file'], str(line), which ) ) 218 suite[which] = line 219 220def closeSuite(): 221 '''Close current suite and add it to the list if valid''' 222 global suite 223 if suite is not None: 224 if len(suite['tests']) is not 0: 225 verifySuite(suite) 226 rememberSuite(suite) 227 suite = None 228 229def verifySuite(suite): 230 '''Verify current suite is legal''' 231 if 'create' in suite and 'destroy' not in suite: 232 abort( '%s:%s: Suite %s has createSuite() but no destroySuite()' % 233 (suite['file'], suite['create'], suite['name']) ) 234 elif 'destroy' in suite and 'create' not in suite: 235 abort( '%s:%s: Suite %s has destroySuite() but no createSuite()' % 236 (suite['file'], suite['destroy'], suite['name']) ) 237 238def rememberSuite(suite): 239 '''Add current suite to list''' 240 global suites 241 suites.append( suite ) 242 243