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