1/* 2 [The "BSD licence"] 3 Copyright (c) 2007-2008 Leon, Jen-Yuan Su 4 All rights reserved. 5 6 Redistribution and use in source and binary forms, with or without 7 modification, are permitted provided that the following conditions 8 are met: 9 1. Redistributions of source code must retain the above copyright 10 notice, this list of conditions and the following disclaimer. 11 2. Redistributions in binary form must reproduce the above copyright 12 notice, this list of conditions and the following disclaimer in the 13 documentation and/or other materials provided with the distribution. 14 3. The name of the author may not be used to endorse or promote products 15 derived from this software without specific prior written permission. 16 17 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27*/ 28package org.antlr.gunit; 29 30import junit.framework.TestCase; 31import org.antlr.runtime.*; 32import org.antlr.runtime.tree.CommonTree; 33import org.antlr.runtime.tree.CommonTreeNodeStream; 34import org.antlr.runtime.tree.TreeAdaptor; 35import org.antlr.runtime.tree.TreeNodeStream; 36import org.antlr.stringtemplate.StringTemplate; 37 38import java.io.ByteArrayOutputStream; 39import java.io.File; 40import java.io.PrintStream; 41import java.lang.reflect.Constructor; 42import java.lang.reflect.InvocationTargetException; 43import java.lang.reflect.Method; 44 45/** All gUnit-generated JUnit class should extend this class 46 * which implements the essential methods for triggering 47 * ANTLR parser/tree walker 48 */ 49public abstract class gUnitBaseTest extends TestCase { 50 51 public String treeAdaptorPath; 52 public String packagePath; 53 public String lexerPath; 54 public String parserPath; 55 public String treeParserPath; 56 57 protected String stdout; 58 protected String stderr; 59 60 private PrintStream console = System.out; 61 private PrintStream consoleErr = System.err; 62 63 // Invoke target lexer.rule 64 public String execLexer(String testRuleName, int line, String testInput, boolean isFile) throws Exception { 65 CharStream input; 66 /** Set up ANTLR input stream based on input source, file or String */ 67 if ( isFile ) { 68 String filePath = testInput; 69 File testInputFile = new File(filePath); 70 // if input test file is not found under the current dir, also try to look for it under the package dir 71 if ( !testInputFile.exists() && packagePath!=null ) { 72 testInputFile = new File(packagePath, filePath); 73 if ( testInputFile.exists() ) filePath = testInputFile.getCanonicalPath(); 74 } 75 input = new ANTLRFileStream(filePath); 76 } 77 else { 78 input = new ANTLRStringStream(testInput); 79 } 80 Class lexer = null; 81 PrintStream ps = null; // for redirecting stdout later 82 PrintStream ps2 = null; // for redirecting stderr later 83 try { 84 /** Use Reflection to create instances of lexer and parser */ 85 lexer = Class.forName(lexerPath); 86 Class[] lexArgTypes = new Class[]{CharStream.class}; // assign type to lexer's args 87 Constructor lexConstructor = lexer.getConstructor(lexArgTypes); 88 Object[] lexArgs = new Object[]{input}; // assign value to lexer's args 89 Lexer lexObj = (Lexer)lexConstructor.newInstance(lexArgs); // makes new instance of lexer 90 input.setLine(line); 91 92 Method ruleName = lexer.getMethod("m"+testRuleName, new Class[0]); 93 94 /** Start of I/O Redirecting */ 95 ByteArrayOutputStream out = new ByteArrayOutputStream(); 96 ByteArrayOutputStream err = new ByteArrayOutputStream(); 97 ps = new PrintStream(out); 98 ps2 = new PrintStream(err); 99 System.setOut(ps); 100 System.setErr(ps2); 101 /** End of redirecting */ 102 103 /** Invoke lexer rule, and get the current index in CharStream */ 104 ruleName.invoke(lexObj, new Object[0]); 105 Method ruleName2 = lexer.getMethod("getCharIndex", new Class[0]); 106 int currentIndex = (Integer) ruleName2.invoke(lexObj, new Object[0]); 107 if ( currentIndex!=input.size() ) { 108 ps2.println("extra text found, '"+input.substring(currentIndex, input.size()-1)+"'"); 109 } 110 111 this.stdout = null; 112 this.stderr = null; 113 114 if ( err.toString().length()>0 ) { 115 this.stderr = err.toString(); 116 return this.stderr; 117 } 118 if ( out.toString().length()>0 ) { 119 this.stdout = out.toString(); 120 } 121 if ( err.toString().length()==0 && out.toString().length()==0 ) { 122 return null; 123 } 124 } catch (ClassNotFoundException e) { 125 e.printStackTrace(); System.exit(1); 126 } catch (SecurityException e) { 127 e.printStackTrace(); System.exit(1); 128 } catch (NoSuchMethodException e) { 129 e.printStackTrace(); System.exit(1); 130 } catch (IllegalArgumentException e) { 131 e.printStackTrace(); System.exit(1); 132 } catch (InstantiationException e) { 133 e.printStackTrace(); System.exit(1); 134 } catch (IllegalAccessException e) { 135 e.printStackTrace(); System.exit(1); 136 } catch (InvocationTargetException e) { // This exception could be caused from ANTLR Runtime Exception, e.g. MismatchedTokenException 137 if ( e.getCause()!=null ) this.stderr = e.getCause().toString(); 138 else this.stderr = e.toString(); 139 return this.stderr; 140 } finally { 141 try { 142 if ( ps!=null ) ps.close(); 143 if ( ps2!=null ) ps2.close(); 144 System.setOut(console); // Reset standard output 145 System.setErr(consoleErr); // Reset standard err out 146 } catch (Exception e) { 147 e.printStackTrace(); 148 } 149 } 150 return this.stdout; 151 } 152 153 // Invoke target parser.rule 154 155 public Object execParser(String testRuleName, int line, String testInput, boolean isFile) throws Exception { 156 CharStream input; 157 /** Set up ANTLR input stream based on input source, file or String */ 158 if ( isFile ) { 159 String filePath = testInput; 160 File testInputFile = new File(filePath); 161 // if input test file is not found under the current dir, also try to look for it under the package dir 162 if ( !testInputFile.exists() && packagePath!=null ) { 163 testInputFile = new File(packagePath, filePath); 164 if ( testInputFile.exists() ) filePath = testInputFile.getCanonicalPath(); 165 } 166 input = new ANTLRFileStream(filePath); 167 } 168 else { 169 input = new ANTLRStringStream(testInput); 170 } 171 Class lexer = null; 172 Class parser = null; 173 PrintStream ps = null; // for redirecting stdout later 174 PrintStream ps2 = null; // for redirecting stderr later 175 ByteArrayOutputStream out = null; 176 ByteArrayOutputStream err = null; 177 try { 178 /** Use Reflection to create instances of lexer and parser */ 179 lexer = Class.forName(lexerPath); 180 Class[] lexArgTypes = new Class[]{CharStream.class}; // assign type to lexer's args 181 Constructor lexConstructor = lexer.getConstructor(lexArgTypes); 182 Object[] lexArgs = new Object[]{input}; // assign value to lexer's args 183 Lexer lexObj = (Lexer)lexConstructor.newInstance(lexArgs); // makes new instance of lexer 184 input.setLine(line); 185 186 CommonTokenStream tokens = new CommonTokenStream(lexObj); 187 parser = Class.forName(parserPath); 188 Class[] parArgTypes = new Class[]{TokenStream.class}; // assign type to parser's args 189 Constructor parConstructor = parser.getConstructor(parArgTypes); 190 Object[] parArgs = new Object[]{tokens}; // assign value to parser's args 191 Parser parObj = (Parser)parConstructor.newInstance(parArgs); // makes new instance of parser 192 193 // set up customized tree adaptor if necessary 194 if ( treeAdaptorPath!=null ) { 195 parArgTypes = new Class[]{TreeAdaptor.class}; 196 Method _setTreeAdaptor = parser.getMethod("setTreeAdaptor", parArgTypes); 197 Class _treeAdaptor = Class.forName(treeAdaptorPath); 198 _setTreeAdaptor.invoke(parObj, _treeAdaptor.newInstance()); 199 } 200 201 Method ruleName = parser.getMethod(testRuleName); 202 203 /** Start of I/O Redirecting */ 204 out = new ByteArrayOutputStream(); 205 err = new ByteArrayOutputStream(); 206 ps = new PrintStream(out); 207 ps2 = new PrintStream(err); 208 System.setOut(ps); 209 System.setErr(ps2); 210 /** End of redirecting */ 211 212 /** Invoke grammar rule, and store if there is a return value */ 213 Object ruleReturn = ruleName.invoke(parObj); 214 String astString = null; 215 String stString = null; 216 /** If rule has return value, determine if it contains an AST or a ST */ 217 if ( ruleReturn!=null ) { 218 if ( ruleReturn.getClass().toString().indexOf(testRuleName+"_return")>0 ) { 219 try { // NullPointerException may happen here... 220 Class _return = Class.forName(parserPath+"$"+testRuleName+"_return"); 221 Method[] methods = _return.getDeclaredMethods(); 222 for(Method method : methods) { 223 if ( method.getName().equals("getTree") ) { 224 Method returnName = _return.getMethod("getTree"); 225 CommonTree tree = (CommonTree) returnName.invoke(ruleReturn); 226 astString = tree.toStringTree(); 227 } 228 else if ( method.getName().equals("getTemplate") ) { 229 Method returnName = _return.getMethod("getTemplate"); 230 StringTemplate st = (StringTemplate) returnName.invoke(ruleReturn); 231 stString = st.toString(); 232 } 233 } 234 } 235 catch(Exception e) { 236 System.err.println(e); // Note: If any exception occurs, the test is viewed as failed. 237 } 238 } 239 } 240 241 this.stdout = ""; 242 this.stderr = ""; 243 244 /** Invalid input */ 245 if ( tokens.index()!=tokens.size()-1 ) { 246 //throw new InvalidInputException(); 247 this.stderr += "Stopped parsing at token index "+tokens.index()+": "; 248 } 249 250 // retVal could be actual return object from rule, stderr or stdout 251 this.stdout += out.toString(); 252 this.stderr += err.toString(); 253 254 if ( err.toString().length()>0 ) return this.stderr; 255 if ( out.toString().length()>0 ) return this.stdout; 256 if ( astString!=null ) { // Return toStringTree of AST 257 return astString; 258 } 259 else if ( stString!=null ) {// Return toString of ST 260 return stString; 261 } 262 if ( ruleReturn!=null ) { 263 return ruleReturn; 264 } 265 if ( err.toString().length()==0 && out.toString().length()==0 ) { 266 return null; 267 } 268 } 269 catch (ClassNotFoundException e) { 270 e.printStackTrace(); System.exit(1); 271 } 272 catch (SecurityException e) { 273 e.printStackTrace(); System.exit(1); 274 } 275 catch (NoSuchMethodException e) { 276 e.printStackTrace(); System.exit(1); 277 } 278 catch (IllegalAccessException e) { 279 e.printStackTrace(); System.exit(1); 280 } 281 catch (InvocationTargetException e) { 282 this.stdout = out.toString(); 283 this.stderr = err.toString(); 284 285 if ( e.getCause()!=null ) this.stderr += e.getCause().toString(); 286 else this.stderr += e.toString(); 287 return this.stderr; 288 } finally { 289 try { 290 if ( ps!=null ) ps.close(); 291 if ( ps2!=null ) ps2.close(); 292 System.setOut(console); // Reset standard output 293 System.setErr(consoleErr); // Reset standard err out 294 } catch (Exception e) { 295 e.printStackTrace(); 296 } 297 } 298 return this.stdout; 299 } 300 301 // Invoke target parser.rule 302 public Object execTreeParser(String testTreeRuleName, String testRuleName, String testInput, boolean isFile) throws Exception { 303 CharStream input; 304 if ( isFile ) { 305 String filePath = testInput; 306 File testInputFile = new File(filePath); 307 // if input test file is not found under the current dir, also try to look for it under the package dir 308 if ( !testInputFile.exists() && packagePath!=null ) { 309 testInputFile = new File(packagePath, filePath); 310 if ( testInputFile.exists() ) filePath = testInputFile.getCanonicalPath(); 311 } 312 input = new ANTLRFileStream(filePath); 313 } 314 else { 315 input = new ANTLRStringStream(testInput); 316 } 317 Class lexer = null; 318 Class parser = null; 319 Class treeParser = null; 320 PrintStream ps = null; // for redirecting stdout later 321 PrintStream ps2 = null; // for redirecting stderr later 322 try { 323 /** Use Reflection to create instances of lexer and parser */ 324 lexer = Class.forName(lexerPath); 325 Class[] lexArgTypes = new Class[]{CharStream.class}; // assign type to lexer's args 326 Constructor lexConstructor = lexer.getConstructor(lexArgTypes); 327 Object[] lexArgs = new Object[]{input}; // assign value to lexer's args 328 Object lexObj = lexConstructor.newInstance(lexArgs); // makes new instance of lexer 329 330 CommonTokenStream tokens = new CommonTokenStream((Lexer) lexObj); 331 332 parser = Class.forName(parserPath); 333 Class[] parArgTypes = new Class[]{TokenStream.class}; // assign type to parser's args 334 Constructor parConstructor = parser.getConstructor(parArgTypes); 335 Object[] parArgs = new Object[]{tokens}; // assign value to parser's args 336 Object parObj = parConstructor.newInstance(parArgs); // makes new instance of parser 337 338 // set up customized tree adaptor if necessary 339 TreeAdaptor customTreeAdaptor = null; 340 if ( treeAdaptorPath!=null ) { 341 parArgTypes = new Class[]{TreeAdaptor.class}; 342 Method _setTreeAdaptor = parser.getMethod("setTreeAdaptor", parArgTypes); 343 Class _treeAdaptor = Class.forName(treeAdaptorPath); 344 customTreeAdaptor = (TreeAdaptor) _treeAdaptor.newInstance(); 345 _setTreeAdaptor.invoke(parObj, customTreeAdaptor); 346 } 347 348 Method ruleName = parser.getMethod(testRuleName); 349 350 /** Start of I/O Redirecting */ 351 ByteArrayOutputStream out = new ByteArrayOutputStream(); 352 ByteArrayOutputStream err = new ByteArrayOutputStream(); 353 ps = new PrintStream(out); 354 ps2 = new PrintStream(err); 355 System.setOut(ps); 356 System.setErr(ps2); 357 /** End of redirecting */ 358 359 /** Invoke grammar rule, and get the return value */ 360 Object ruleReturn = ruleName.invoke(parObj); 361 362 Class _return = Class.forName(parserPath+"$"+testRuleName+"_return"); 363 Method returnName = _return.getMethod("getTree"); 364 CommonTree tree = (CommonTree) returnName.invoke(ruleReturn); 365 366 // Walk resulting tree; create tree nodes stream first 367 CommonTreeNodeStream nodes; 368 if ( customTreeAdaptor!=null ) { 369 nodes = new CommonTreeNodeStream(customTreeAdaptor, tree); 370 } 371 else { 372 nodes = new CommonTreeNodeStream(tree); 373 } 374 // AST nodes have payload that point into token stream 375 nodes.setTokenStream(tokens); 376 // Create a tree walker attached to the nodes stream 377 treeParser = Class.forName(treeParserPath); 378 Class[] treeParArgTypes = new Class[]{TreeNodeStream.class}; // assign type to tree parser's args 379 Constructor treeParConstructor = treeParser.getConstructor(treeParArgTypes); 380 Object[] treeParArgs = new Object[]{nodes}; // assign value to tree parser's args 381 Object treeParObj = treeParConstructor.newInstance(treeParArgs); // makes new instance of tree parser 382 // Invoke the tree rule, and store the return value if there is 383 Method treeRuleName = treeParser.getMethod(testTreeRuleName); 384 Object treeRuleReturn = treeRuleName.invoke(treeParObj); 385 386 String astString = null; 387 String stString = null; 388 /** If tree rule has return value, determine if it contains an AST or a ST */ 389 if ( treeRuleReturn!=null ) { 390 if ( treeRuleReturn.getClass().toString().indexOf(testTreeRuleName+"_return")>0 ) { 391 try { // NullPointerException may happen here... 392 Class _treeReturn = Class.forName(treeParserPath+"$"+testTreeRuleName+"_return"); 393 Method[] methods = _treeReturn.getDeclaredMethods(); 394 for(Method method : methods) { 395 if ( method.getName().equals("getTree") ) { 396 Method treeReturnName = _treeReturn.getMethod("getTree"); 397 CommonTree returnTree = (CommonTree) treeReturnName.invoke(treeRuleReturn); 398 astString = returnTree.toStringTree(); 399 } 400 else if ( method.getName().equals("getTemplate") ) { 401 Method treeReturnName = _return.getMethod("getTemplate"); 402 StringTemplate st = (StringTemplate) treeReturnName.invoke(treeRuleReturn); 403 stString = st.toString(); 404 } 405 } 406 } 407 catch(Exception e) { 408 System.err.println(e); // Note: If any exception occurs, the test is viewed as failed. 409 } 410 } 411 } 412 413 this.stdout = null; 414 this.stderr = null; 415 416 /** Invalid input */ 417 if ( tokens.index()!=tokens.size()-1 ) { 418 throw new InvalidInputException(); 419 } 420 421 // retVal could be actual return object from rule, stderr or stdout 422 if ( err.toString().length()>0 ) { 423 this.stderr = err.toString(); 424 return this.stderr; 425 } 426 if ( out.toString().length()>0 ) { 427 this.stdout = out.toString(); 428 } 429 if ( astString!=null ) { // Return toStringTree of AST 430 return astString; 431 } 432 else if ( stString!=null ) {// Return toString of ST 433 return stString; 434 } 435 if ( treeRuleReturn!=null ) { 436 return treeRuleReturn; 437 } 438 if ( err.toString().length()==0 && out.toString().length()==0 ) { 439 return null; 440 } 441 } catch (ClassNotFoundException e) { 442 e.printStackTrace(); System.exit(1); 443 } catch (SecurityException e) { 444 e.printStackTrace(); System.exit(1); 445 } catch (NoSuchMethodException e) { 446 e.printStackTrace(); System.exit(1); 447 } catch (IllegalAccessException e) { 448 e.printStackTrace(); System.exit(1); 449 } catch (InvocationTargetException e) { 450 if ( e.getCause()!=null ) this.stderr = e.getCause().toString(); 451 else this.stderr = e.toString(); 452 return this.stderr; 453 } finally { 454 try { 455 if ( ps!=null ) ps.close(); 456 if ( ps2!=null ) ps2.close(); 457 System.setOut(console); // Reset standard output 458 System.setErr(consoleErr); // Reset standard err out 459 } catch (Exception e) { 460 e.printStackTrace(); 461 } 462 } 463 return stdout; 464 } 465 466 // Modify the return value if the expected token type is OK or FAIL 467 public Object examineExecResult(int tokenType, Object retVal) { 468 if ( tokenType==gUnitParser.OK ) { // expected Token: OK 469 if ( this.stderr==null ) { 470 return "OK"; 471 } 472 else { 473 return "FAIL, "+this.stderr; 474 } 475 } 476 else if ( tokenType==gUnitParser.FAIL ) { // expected Token: FAIL 477 if ( this.stderr!=null ) { 478 return "FAIL"; 479 } 480 else { 481 return "OK"; 482 } 483 } 484 else { // return the same object for the other token types 485 return retVal; 486 } 487 } 488 489} 490