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			// calling of the deprecated save method to enable compiling under 1.1.7
60			getPreferences().save(fos, "");
61		} finally {
62			fos.close();
63		}
64	}
65
66	public static void setPreference(String key, String value) {
67		getPreferences().put(key, value);
68	}
69
70	public synchronized void endTest(Test test) {
71		testEnded(test.toString());
72	}
73
74	public synchronized void addError(final Test test, final Throwable t) {
75		testFailed(TestRunListener.STATUS_ERROR, test, t);
76	}
77
78	public synchronized void addFailure(final Test test, final AssertionFailedError t) {
79		testFailed(TestRunListener.STATUS_FAILURE, test, t);
80	}
81
82	// TestRunListener implementation
83
84	public abstract void testStarted(String testName);
85
86	public abstract void testEnded(String testName);
87
88	public abstract void testFailed(int status, Test test, Throwable t);
89
90	/**
91	 * Returns the Test corresponding to the given suite. This is
92	 * a template method, subclasses override runFailed(), clearStatus().
93	 */
94	public Test getTest(String suiteClassName) {
95		if (suiteClassName.length() <= 0) {
96			clearStatus();
97			return null;
98		}
99		Class testClass= null;
100		try {
101			testClass= loadSuiteClass(suiteClassName);
102		} catch (ClassNotFoundException e) {
103			String clazz= e.getMessage();
104			if (clazz == null)
105				clazz= suiteClassName;
106			runFailed("Class not found \""+clazz+"\"");
107			return null;
108		} catch(Exception e) {
109			runFailed("Error: "+e.toString());
110			return null;
111		}
112		Method suiteMethod= null;
113		try {
114			suiteMethod= testClass.getMethod(SUITE_METHODNAME, new Class[0]);
115	 	} catch(Exception e) {
116	 		// try to extract a test suite automatically
117			clearStatus();
118			return new TestSuite(testClass);
119		}
120		if (! Modifier.isStatic(suiteMethod.getModifiers())) {
121			runFailed("Suite() method must be static");
122			return null;
123		}
124		Test test= null;
125		try {
126			test= (Test)suiteMethod.invoke(null, (Object[])new Class[0]); // static method
127			if (test == null)
128				return test;
129		}
130		catch (InvocationTargetException e) {
131			runFailed("Failed to invoke suite():" + e.getTargetException().toString());
132			return null;
133		}
134		catch (IllegalAccessException e) {
135			runFailed("Failed to invoke suite():" + e.toString());
136			return null;
137		}
138
139		clearStatus();
140		return test;
141	}
142
143	/**
144	 * Returns the formatted string of the elapsed time.
145	 */
146	public String elapsedTimeAsString(long runTime) {
147		return NumberFormat.getInstance().format((double)runTime/1000);
148	}
149
150	/**
151	 * Processes the command line arguments and
152	 * returns the name of the suite class to run or null
153	 */
154	protected String processArguments(String[] args) {
155		String suiteName= null;
156		for (int i= 0; i < args.length; i++) {
157			if (args[i].equals("-noloading")) {
158				setLoading(false);
159			} else if (args[i].equals("-nofilterstack")) {
160				fgFilterStack= false;
161			} else if (args[i].equals("-c")) {
162				if (args.length > i+1)
163					suiteName= extractClassName(args[i+1]);
164				else
165					System.out.println("Missing Test class name");
166				i++;
167			} else {
168				suiteName= args[i];
169			}
170		}
171		return suiteName;
172	}
173
174	/**
175	 * Sets the loading behaviour of the test runner
176	 */
177	public void setLoading(boolean enable) {
178		fLoading= enable;
179	}
180	/**
181	 * Extract the class name from a String in VA/Java style
182	 */
183	public String extractClassName(String className) {
184		if(className.startsWith("Default package for"))
185			return className.substring(className.lastIndexOf(".")+1);
186		return className;
187	}
188
189	/**
190	 * Truncates a String to the maximum length.
191	 */
192	public static String truncate(String s) {
193		if (fgMaxMessageLength != -1 && s.length() > fgMaxMessageLength)
194			s= s.substring(0, fgMaxMessageLength)+"...";
195		return s;
196	}
197
198	/**
199	 * Override to define how to handle a failed loading of
200	 * a test suite.
201	 */
202	protected abstract void runFailed(String message);
203
204	/**
205	 * Returns the loaded Class for a suite name.
206	 */
207	protected Class loadSuiteClass(String suiteClassName) throws ClassNotFoundException {
208		return getLoader().load(suiteClassName);
209	}
210
211	/**
212	 * Clears the status message.
213	 */
214	protected void clearStatus() { // Belongs in the GUI TestRunner class
215	}
216
217	/**
218	 * Returns the loader to be used.
219	 */
220	public TestSuiteLoader getLoader() {
221		if (useReloadingTestSuiteLoader())
222			return new ReloadingTestSuiteLoader();
223		return new StandardTestSuiteLoader();
224	}
225
226	protected boolean useReloadingTestSuiteLoader() {
227		return getPreference("loading").equals("true") && !inVAJava() && fLoading;
228	}
229
230	private static File getPreferencesFile() {
231	 	String home= System.getProperty("user.home");
232 		return new File(home, "junit.properties");
233 	}
234
235 	private static void readPreferences() {
236 		InputStream is= null;
237 		try {
238 			is= new FileInputStream(getPreferencesFile());
239 			setPreferences(new Properties(getPreferences()));
240			getPreferences().load(is);
241		} catch (IOException e) {
242			try {
243				if (is != null)
244					is.close();
245			} catch (IOException e1) {
246			}
247		}
248 	}
249
250 	public static String getPreference(String key) {
251 		return getPreferences().getProperty(key);
252 	}
253
254 	public static int getPreference(String key, int dflt) {
255 		String value= getPreference(key);
256 		int intValue= dflt;
257 		if (value == null)
258 			return intValue;
259 		try {
260 			intValue= Integer.parseInt(value);
261 	 	} catch (NumberFormatException ne) {
262 		}
263 		return intValue;
264 	}
265
266 	public static boolean inVAJava() {
267		try {
268			Class.forName("com.ibm.uvm.tools.DebugSupport");
269		}
270		catch (Exception e) {
271			return false;
272		}
273		return true;
274	}
275
276	public static boolean inMac() {
277		return System.getProperty("mrj.version") != null;
278	}
279
280
281	/**
282	 * Returns a filtered stack trace
283	 */
284	public static String getFilteredTrace(Throwable t) {
285		StringWriter stringWriter= new StringWriter();
286		PrintWriter writer= new PrintWriter(stringWriter);
287		t.printStackTrace(writer);
288		StringBuffer buffer= stringWriter.getBuffer();
289		String trace= buffer.toString();
290		return BaseTestRunner.getFilteredTrace(trace);
291	}
292
293	/**
294	 * Filters stack frames from internal JUnit classes
295	 */
296	public static String getFilteredTrace(String stack) {
297		if (showStackRaw())
298			return stack;
299
300		StringWriter sw= new StringWriter();
301		PrintWriter pw= new PrintWriter(sw);
302		StringReader sr= new StringReader(stack);
303		BufferedReader br= new BufferedReader(sr);
304
305		String line;
306		try {
307			while ((line= br.readLine()) != null) {
308				if (!filterLine(line))
309					pw.println(line);
310			}
311		} catch (Exception IOException) {
312			return stack; // return the stack unfiltered
313		}
314		return sw.toString();
315	}
316
317	protected static boolean showStackRaw() {
318		return !getPreference("filterstack").equals("true") || fgFilterStack == false;
319	}
320
321	static boolean filterLine(String line) {
322		String[] patterns= new String[] {
323			"junit.framework.TestCase",
324			"junit.framework.TestResult",
325			"junit.framework.TestSuite",
326			"junit.framework.Assert.", // don't filter AssertionFailure
327			"junit.swingui.TestRunner",
328			"junit.awtui.TestRunner",
329			"junit.textui.TestRunner",
330			"java.lang.reflect.Method.invoke("
331		};
332		for (int i= 0; i < patterns.length; i++) {
333			if (line.indexOf(patterns[i]) > 0)
334				return true;
335		}
336		return false;
337	}
338
339 	static {
340 		fgMaxMessageLength= getPreference("maxmessage", fgMaxMessageLength);
341 	}
342
343}
344