testbase.py revision 324c4644fee44b9898524c09511bd33c3f12e2df
1import unittest 2import imp 3import os 4import errno 5import sys 6import glob 7import re 8import tempfile 9import shutil 10import inspect 11import hashlib 12from distutils.errors import * 13import antlr3 14 15def unlink(path): 16 try: 17 os.unlink(path) 18 except OSError, exc: 19 if exc.errno != errno.ENOENT: 20 raise 21 22 23class GrammarCompileError(Exception): 24 """Grammar failed to compile.""" 25 pass 26 27 28# At least on MacOSX tempdir (/tmp) is a symlink. It's sometimes dereferences, 29# sometimes not, breaking the inspect.getmodule() function. 30testbasedir = os.path.join( 31 os.path.realpath(tempfile.gettempdir()), 32 'antlr3-test') 33 34 35class BrokenTest(unittest.TestCase.failureException): 36 def __repr__(self): 37 name, reason = self.args 38 return '%s: %s: %s works now' % ( 39 (self.__class__.__name__, name, reason)) 40 41 42def broken(reason, *exceptions): 43 '''Indicates a failing (or erroneous) test case fails that should succeed. 44 If the test fails with an exception, list the exception type in args''' 45 def wrapper(test_method): 46 def replacement(*args, **kwargs): 47 try: 48 test_method(*args, **kwargs) 49 except exceptions or unittest.TestCase.failureException: 50 pass 51 else: 52 raise BrokenTest(test_method.__name__, reason) 53 replacement.__doc__ = test_method.__doc__ 54 replacement.__name__ = 'XXX_' + test_method.__name__ 55 replacement.todo = reason 56 return replacement 57 return wrapper 58 59 60dependencyCache = {} 61compileErrorCache = {} 62 63# setup java CLASSPATH 64if 'CLASSPATH' not in os.environ: 65 cp = [] 66 67 baseDir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..')) 68 libDir = os.path.join(baseDir, 'lib') 69 70 jar = os.path.join(libDir, 'ST-4.0.1.jar') 71 if not os.path.isfile(jar): 72 raise DistutilsFileError( 73 "Missing file '%s'. Grap it from a distribution package." 74 % jar, 75 ) 76 cp.append(jar) 77 78 jar = os.path.join(libDir, 'antlr-2.7.7.jar') 79 if not os.path.isfile(jar): 80 raise DistutilsFileError( 81 "Missing file '%s'. Grap it from a distribution package." 82 % jar, 83 ) 84 cp.append(jar) 85 86 jar = os.path.join(libDir, 'junit-4.2.jar') 87 if not os.path.isfile(jar): 88 raise DistutilsFileError( 89 "Missing file '%s'. Grap it from a distribution package." 90 % jar, 91 ) 92 cp.append(jar) 93 94 cp.append(os.path.join(baseDir, 'runtime', 'Python', 'build')) 95 96 classpath = '-cp "' + ':'.join([os.path.abspath(p) for p in cp]) + '"' 97 98else: 99 classpath = '' 100 101 102class ANTLRTest(unittest.TestCase): 103 def __init__(self, *args, **kwargs): 104 unittest.TestCase.__init__(self, *args, **kwargs) 105 106 self.moduleName = os.path.splitext(os.path.basename(sys.modules[self.__module__].__file__))[0] 107 self.className = self.__class__.__name__ 108 self._baseDir = None 109 110 self.lexerModule = None 111 self.parserModule = None 112 113 self.grammarName = None 114 self.grammarType = None 115 116 117 def assertListEqual(self, a, b): 118 if a == b: 119 return 120 121 import difflib 122 a = [str(l) + '\n' for l in a] 123 b = [str(l) + '\n' for l in b] 124 125 raise AssertionError(''.join(difflib.unified_diff(a, b))) 126 127 128 @property 129 def baseDir(self): 130 if self._baseDir is None: 131 testName = 'unknownTest' 132 for frame in inspect.stack(): 133 code = frame[0].f_code 134 codeMod = inspect.getmodule(code) 135 if codeMod is None: 136 continue 137 138 # skip frames not in requested module 139 if codeMod is not sys.modules[self.__module__]: 140 continue 141 142 # skip some unwanted names 143 if code.co_name in ('nextToken', '<module>'): 144 continue 145 146 if code.co_name.startswith('test'): 147 testName = code.co_name 148 break 149 150 self._baseDir = os.path.join( 151 testbasedir, 152 self.moduleName, self.className, testName) 153 if not os.path.isdir(self._baseDir): 154 os.makedirs(self._baseDir) 155 156 return self._baseDir 157 158 159 def _invokeantlr(self, dir, file, options, javaOptions=''): 160 cmd = 'cd %s; java %s %s org.antlr.Tool -o . %s %s 2>&1' % ( 161 dir, javaOptions, classpath, options, file 162 ) 163 fp = os.popen(cmd) 164 output = '' 165 failed = False 166 for line in fp: 167 output += line 168 169 if line.startswith('error('): 170 failed = True 171 172 rc = fp.close() 173 if rc is not None: 174 failed = True 175 176 if failed: 177 raise GrammarCompileError( 178 "Failed to compile grammar '%s':\n%s\n\n" % (file, cmd) 179 + output 180 ) 181 182 183 def compileGrammar(self, grammarName=None, options='', javaOptions=''): 184 if grammarName is None: 185 grammarName = self.moduleName + '.g' 186 187 self._baseDir = os.path.join( 188 testbasedir, 189 self.moduleName) 190 if not os.path.isdir(self._baseDir): 191 os.makedirs(self._baseDir) 192 193 if self.grammarName is None: 194 self.grammarName = os.path.splitext(grammarName)[0] 195 196 grammarPath = os.path.join(os.path.dirname(os.path.abspath(__file__)), grammarName) 197 198 # get type and name from first grammar line 199 grammar = open(grammarPath, 'r').read() 200 m = re.match(r'\s*((lexer|parser|tree)\s+|)grammar\s+(\S+);', grammar, re.MULTILINE) 201 assert m is not None, grammar 202 self.grammarType = m.group(2) 203 if self.grammarType is None: 204 self.grammarType = 'combined' 205 206 if self.grammarType is None: 207 assert self.grammarType in ('lexer', 'parser', 'tree', 'combined'), self.grammarType 208 209 # don't try to rebuild grammar, if it already failed 210 if grammarName in compileErrorCache: 211 return 212 213 try: 214 # # get dependencies from antlr 215 # if grammarName in dependencyCache: 216 # dependencies = dependencyCache[grammarName] 217 218 # else: 219 # dependencies = [] 220 # cmd = ('cd %s; java %s %s org.antlr.Tool -o . -depend %s 2>&1' 221 # % (self.baseDir, javaOptions, classpath, grammarPath)) 222 223 # output = "" 224 # failed = False 225 226 # fp = os.popen(cmd) 227 # for line in fp: 228 # output += line 229 230 # if line.startswith('error('): 231 # failed = True 232 # elif ':' in line: 233 # a, b = line.strip().split(':', 1) 234 # dependencies.append( 235 # (os.path.join(self.baseDir, a.strip()), 236 # [os.path.join(self.baseDir, b.strip())]) 237 # ) 238 239 # rc = fp.close() 240 # if rc is not None: 241 # failed = True 242 243 # if failed: 244 # raise GrammarCompileError( 245 # "antlr -depend failed with code %s on grammar '%s':\n\n" 246 # % (rc, grammarName) 247 # + cmd 248 # + "\n" 249 # + output 250 # ) 251 252 # # add dependencies to my .stg files 253 # templateDir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'tool', 'src', 'main', 'resources', 'org', 'antlr', 'codegen', 'templates', 'Python')) 254 # templates = glob.glob(os.path.join(templateDir, '*.stg')) 255 256 # for dst, src in dependencies: 257 # src.extend(templates) 258 259 # dependencyCache[grammarName] = dependencies 260 261 # rebuild = False 262 # for dest, sources in dependencies: 263 # if not os.path.isfile(dest): 264 # rebuild = True 265 # break 266 267 # for source in sources: 268 # if os.path.getmtime(source) > os.path.getmtime(dest): 269 # rebuild = True 270 # break 271 272 273 # if rebuild: 274 # self._invokeantlr(self.baseDir, grammarPath, options, javaOptions) 275 276 self._invokeantlr(self.baseDir, grammarPath, options, javaOptions) 277 278 except: 279 # mark grammar as broken 280 compileErrorCache[grammarName] = True 281 raise 282 283 284 def lexerClass(self, base): 285 """Optionally build a subclass of generated lexer class""" 286 287 return base 288 289 290 def parserClass(self, base): 291 """Optionally build a subclass of generated parser class""" 292 293 return base 294 295 296 def walkerClass(self, base): 297 """Optionally build a subclass of generated walker class""" 298 299 return base 300 301 302 def __load_module(self, name): 303 modFile, modPathname, modDescription \ 304 = imp.find_module(name, [self.baseDir]) 305 306 return imp.load_module( 307 name, modFile, modPathname, modDescription 308 ) 309 310 311 def getLexer(self, *args, **kwargs): 312 """Build lexer instance. Arguments are passed to lexer.__init__().""" 313 314 if self.grammarType == 'lexer': 315 self.lexerModule = self.__load_module(self.grammarName) 316 cls = getattr(self.lexerModule, self.grammarName) 317 else: 318 self.lexerModule = self.__load_module(self.grammarName + 'Lexer') 319 cls = getattr(self.lexerModule, self.grammarName + 'Lexer') 320 321 cls = self.lexerClass(cls) 322 323 lexer = cls(*args, **kwargs) 324 325 return lexer 326 327 328 def getParser(self, *args, **kwargs): 329 """Build parser instance. Arguments are passed to parser.__init__().""" 330 331 if self.grammarType == 'parser': 332 self.lexerModule = self.__load_module(self.grammarName) 333 cls = getattr(self.lexerModule, self.grammarName) 334 else: 335 self.parserModule = self.__load_module(self.grammarName + 'Parser') 336 cls = getattr(self.parserModule, self.grammarName + 'Parser') 337 cls = self.parserClass(cls) 338 339 parser = cls(*args, **kwargs) 340 341 return parser 342 343 344 def getWalker(self, *args, **kwargs): 345 """Build walker instance. Arguments are passed to walker.__init__().""" 346 347 self.walkerModule = self.__load_module(self.grammarName + 'Walker') 348 cls = getattr(self.walkerModule, self.grammarName + 'Walker') 349 cls = self.walkerClass(cls) 350 351 walker = cls(*args, **kwargs) 352 353 return walker 354 355 356 def writeInlineGrammar(self, grammar): 357 # Create a unique ID for this test and use it as the grammar name, 358 # to avoid class name reuse. This kinda sucks. Need to find a way so 359 # tests can use the same grammar name without messing up the namespace. 360 # Well, first I should figure out what the exact problem is... 361 id = hashlib.md5(self.baseDir).hexdigest()[-8:] 362 grammar = grammar.replace('$TP', 'TP' + id) 363 grammar = grammar.replace('$T', 'T' + id) 364 365 # get type and name from first grammar line 366 m = re.match(r'\s*((lexer|parser|tree)\s+|)grammar\s+(\S+);', grammar, re.MULTILINE) 367 assert m is not None, grammar 368 grammarType = m.group(2) 369 if grammarType is None: 370 grammarType = 'combined' 371 grammarName = m.group(3) 372 373 assert grammarType in ('lexer', 'parser', 'tree', 'combined'), grammarType 374 375 grammarPath = os.path.join(self.baseDir, grammarName + '.g') 376 377 # dump temp grammar file 378 fp = open(grammarPath, 'w') 379 fp.write(grammar) 380 fp.close() 381 382 return grammarName, grammarPath, grammarType 383 384 385 def writeFile(self, name, contents): 386 testDir = os.path.dirname(os.path.abspath(__file__)) 387 path = os.path.join(self.baseDir, name) 388 389 fp = open(path, 'w') 390 fp.write(contents) 391 fp.close() 392 393 return path 394 395 396 def compileInlineGrammar(self, grammar, options='', javaOptions='', 397 returnModule=False): 398 # write grammar file 399 grammarName, grammarPath, grammarType = self.writeInlineGrammar(grammar) 400 401 # compile it 402 self._invokeantlr( 403 os.path.dirname(grammarPath), 404 os.path.basename(grammarPath), 405 options, 406 javaOptions 407 ) 408 409 if grammarType == 'combined': 410 lexerMod = self.__load_module(grammarName + 'Lexer') 411 parserMod = self.__load_module(grammarName + 'Parser') 412 if returnModule: 413 return lexerMod, parserMod 414 415 lexerCls = getattr(lexerMod, grammarName + 'Lexer') 416 lexerCls = self.lexerClass(lexerCls) 417 parserCls = getattr(parserMod, grammarName + 'Parser') 418 parserCls = self.parserClass(parserCls) 419 420 return lexerCls, parserCls 421 422 if grammarType == 'lexer': 423 lexerMod = self.__load_module(grammarName) 424 if returnModule: 425 return lexerMod 426 427 lexerCls = getattr(lexerMod, grammarName) 428 lexerCls = self.lexerClass(lexerCls) 429 430 return lexerCls 431 432 if grammarType == 'parser': 433 parserMod = self.__load_module(grammarName) 434 if returnModule: 435 return parserMod 436 437 parserCls = getattr(parserMod, grammarName) 438 parserCls = self.parserClass(parserCls) 439 440 return parserCls 441 442 if grammarType == 'tree': 443 walkerMod = self.__load_module(grammarName) 444 if returnModule: 445 return walkerMod 446 447 walkerCls = getattr(walkerMod, grammarName) 448 walkerCls = self.walkerClass(walkerCls) 449 450 return walkerCls 451