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