BaseTestRunner.java revision b3823db9f1192d8c81345740b3e65bd6738ba55b
1package junit.runner;
2
3import java.io.BufferedReader;
4import java.io.File;
5import java.io.FileInputStream;
6import java.io.FileOutputStream;
7import java.io.IOException;
8import java.io.InputStream;
9import java.io.PrintWriter;
10import java.io.StringReader;
11import java.io.StringWriter;
12import java.lang.reflect.InvocationTargetException;
13import java.lang.reflect.Method;
14import java.lang.reflect.Modifier;
15import java.text.NumberFormat;
16import java.util.Properties;
17
18import junit.framework.AssertionFailedError;
19import junit.framework.Test;
20import junit.framework.TestListener;
21import junit.framework.TestSuite;
22
23/**
24 * Base class for all test runners.
25 * This class was born live on stage in Sardinia during XP2000.
26 */
27public abstract class BaseTestRunner implements TestListener {
28	public static final String SUITE_METHODNAME= "suite";
29
30	private static Properties fPreferences;
31	static int fgMaxMessageLength= 500;
32	static boolean fgFilterStack= true;
33	boolean fLoading= true;
34
35    /*
36    * Implementation of TestListener
37    */
38	public synchronized void startTest(Test test) {
39		testStarted(test.toString());
40	}
41
42	protected static void setPreferences(Properties preferences) {
43		fPreferences= preferences;
44	}
45
46	protected static Properties getPreferences() {
47		if (fPreferences == null) {
48			fPreferences= new Properties();
49	 		fPreferences.put("loading", "true");
50 			fPreferences.put("filterstack", "true");
51  			readPreferences();
52		}
53		return fPreferences;
54	}
55
56	public static void savePreferences() throws IOException {
57		FileOutputStream fos= new FileOutputStream(getPreferencesFile());
58		try {
59			getPreferences().store(fos, "");
60		} finally {
61			fos.close();
62		}
63	}
64
65	public static void setPreference(String key, String value) {
66		getPreferences().put(key, value);
67	}
68
69	public synchronized void endTest(Test test) {
70		testEnded(test.toString());
71	}
72
73	public synchronized void addError(final Test test, final Throwable t) {
74		testFailed(TestRunListener.STATUS_ERROR, test, t);
75	}
76
77	public synchronized void addFailure(final Test test, final AssertionFailedError t) {
78		testFailed(TestRunListener.STATUS_FAILURE, test, t);
79	}
80
81	// TestRunListener implementation
82
83	public abstract void testStarted(String testName);
84
85	public abstract void testEnded(String testName);
86
87	public abstract void testFailed(int status, Test test, Throwable t);
88
89	/**
90	 * Returns the Test corresponding to the given suite. This is
91	 * a template method, subclasses override runFailed(), clearStatus().
92	 */
93	public Test getTest(String suiteClassName) {
94		if (suiteClassName.length() <= 0) {
95			clearStatus();
96			return null;
97		}
98		Class<?> testClass= null;
99		try {
100			testClass= loadSuiteClass(suiteClassName);
101		} catch (ClassNotFoundException e) {
102			String clazz= e.getMessage();
103			if (clazz == null)
104				clazz= suiteClassName;
105			runFailed("Class not found \""+clazz+"\"");
106			return null;
107		} catch(Exception e) {
108			runFailed("Error: "+e.toString());
109			return null;
110		}
111		Method suiteMethod= null;
112		try {
113			suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]);
114	 	} catch(Exception e) {
115	 		// try to extract a test suite automatically
116			clearStatus();
117			return new TestSuite(testClass);
118		}
119		if (! Modifier.isStatic(suiteMethod.getModifiers())) {
120			runFailed("Suite() method must be static");
121			return null;
122		}
123		Test test= null;
124		try {
125			test= (Test)suiteMethod.invoke(null, (Object[])new Class[0]); // static method
126			if (test == null)
127				return test;
128		}
129		catch (InvocationTargetException e) {
130			runFailed("Failed to invoke suite():" + e.getTargetException().toString());
131			return null;
132		}
133		catch (IllegalAccessException e) {
134			runFailed("Failed to invoke suite():" + e.toString());
135			return null;
136		}
137
138		clearStatus();
139		return test;
140	}
141
142	/**
143	 * Returns the formatted string of the elapsed time.
144	 */
145	public String elapsedTimeAsString(long runTime) {
146		return NumberFormat.getInstance().format((double)runTime/1000);
147	}
148
149	/**
150	 * Processes the command line arguments and
151	 * returns the name of the suite class to run or null
152	 */
153	protected String processArguments(String[] args) {
154		String suiteName= null;
155		for (int i= 0; i < args.length; i++) {
156			if (args[i].equals("-noloading")) {
157				setLoading(false);
158			} else if (args[i].equals("-nofilterstack")) {
159				fgFilterStack= false;
160			} else if (args[i].equals("-c")) {
161				if (args.length > i+1)
162					suiteName= extractClassName(args[i+1]);
163				else
164					System.out.println("Missing Test class name");
165				i++;
166			} else {
167				suiteName= args[i];
168			}
169		}
170		return suiteName;
171	}
172
173	/**
174	 * Sets the loading behaviour of the test runner
175	 */
176	public void setLoading(boolean enable) {
177		fLoading= enable;
178	}
179	/**
180	 * Extract the class name from a String in VA/Java style
181	 */
182	public String extractClassName(String className) {
183		if(className.startsWith("Default package for"))
184			return className.substring(className.lastIndexOf(".")+1);
185		return className;
186	}
187
188	/**
189	 * Truncates a String to the maximum length.
190	 */
191	public static String truncate(String s) {
192		if (fgMaxMessageLength != -1 && s.length() > fgMaxMessageLength)
193			s= s.substring(0, fgMaxMessageLength)+"...";
194		return s;
195	}
196
197	/**
198	 * Override to define how to handle a failed loading of
199	 * a test suite.
200	 */
201	protected abstract void runFailed(String message);
202
203	/**
204	 * Returns the loaded Class for a suite name.
205	 */
206	protected Class<?> loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
207		return Class.forName(suiteClassName);
208	}
209
210	/**
211	 * Clears the status message.
212	 */
213	protected void clearStatus() { // Belongs in the GUI TestRunner class
214	}
215
216	protected boolean useReloadingTestSuiteLoader() {
217		return getPreference("loading").equals("true") && fLoading;
218	}
219
220	private static File getPreferencesFile() {
221	 	String home= System.getProperty("user.home");
222 		return new File(home, "junit.properties");
223 	}
224
225 	private static void readPreferences() {
226 		InputStream is= null;
227 		try {
228 			is= new FileInputStream(getPreferencesFile());
229 			setPreferences(new Properties(getPreferences()));
230			getPreferences().load(is);
231		} catch (IOException e) {
232			try {
233				if (is != null)
234					is.close();
235			} catch (IOException e1) {
236			}
237		}
238 	}
239
240 	public static String getPreference(String key) {
241 		return getPreferences().getProperty(key);
242 	}
243
244 	public static int getPreference(String key, int dflt) {
245 		String value= getPreference(key);
246 		int intValue= dflt;
247 		if (value == null)
248 			return intValue;
249 		try {
250 			intValue= Integer.parseInt(value);
251 	 	} catch (NumberFormatException ne) {
252 		}
253 		return intValue;
254 	}
255
256	/**
257	 * Returns a filtered stack trace
258	 */
259	public static String getFilteredTrace(Throwable t) {
260		StringWriter stringWriter= new StringWriter();
261		PrintWriter writer= new PrintWriter(stringWriter);
262		t.printStackTrace(writer);
263		StringBuffer buffer= stringWriter.getBuffer();
264		String trace= buffer.toString();
265		return BaseTestRunner.getFilteredTrace(trace);
266	}
267
268	/**
269	 * Filters stack frames from internal JUnit classes
270	 */
271	public static String getFilteredTrace(String stack) {
272		if (showStackRaw())
273			return stack;
274
275		StringWriter sw= new StringWriter();
276		PrintWriter pw= new PrintWriter(sw);
277		StringReader sr= new StringReader(stack);
278		BufferedReader br= new BufferedReader(sr);
279
280		String line;
281		try {
282			while ((line= br.readLine()) != null) {
283				if (!filterLine(line))
284					pw.println(line);
285			}
286		} catch (Exception IOException) {
287			return stack; // return the stack unfiltered
288		}
289		return sw.toString();
290	}
291
292	protected static boolean showStackRaw() {
293		return !getPreference("filterstack").equals("true") || fgFilterStack == false;
294	}
295
296	static boolean filterLine(String line) {
297		String[] patterns= new String[] {
298			"junit.framework.TestCase",
299			"junit.framework.TestResult",
300			"junit.framework.TestSuite",
301			"junit.framework.Assert.", // don't filter AssertionFailure
302			"junit.swingui.TestRunner",
303			"junit.awtui.TestRunner",
304			"junit.textui.TestRunner",
305			"java.lang.reflect.Method.invoke("
306		};
307		for (int i= 0; i < patterns.length; i++) {
308			if (line.indexOf(patterns[i]) > 0)
309				return true;
310		}
311		return false;
312	}
313
314 	static {
315 		fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
316 	}
317
318}
319