1package junit.framework;
2
3import java.io.PrintWriter;
4import java.io.StringWriter;
5import java.lang.reflect.Constructor;
6import java.lang.reflect.InvocationTargetException;
7import java.lang.reflect.Method;
8import java.lang.reflect.Modifier;
9import java.util.Enumeration;
10import java.util.Vector;
11
12/**
13 * A <code>TestSuite</code> is a <code>Composite</code> of Tests.
14 * It runs a collection of test cases. Here is an example using
15 * the dynamic test definition.
16 * <pre>
17 * TestSuite suite= new TestSuite();
18 * suite.addTest(new MathTest("testAdd"));
19 * suite.addTest(new MathTest("testDivideByZero"));
20 * </pre>
21 * Alternatively, a TestSuite can extract the tests to be run automatically.
22 * To do so you pass the class of your TestCase class to the
23 * TestSuite constructor.
24 * <pre>
25 * TestSuite suite= new TestSuite(MathTest.class);
26 * </pre>
27 * This constructor creates a suite with all the methods
28 * starting with "test" that take no arguments.
29 * <p>
30 * A final option is to do the same for a large array of test classes.
31 * <pre>
32 * Class[] testClasses = { MathTest.class, AnotherTest.class }
33 * TestSuite suite= new TestSuite(testClasses);
34 * </pre>
35 *
36 * @see Test
37 */
38public class TestSuite implements Test {
39
40	/**
41	 * ...as the moon sets over the early morning Merlin, Oregon
42	 * mountains, our intrepid adventurers type...
43	 */
44	static public Test createTest(Class theClass, String name) {
45		Constructor constructor;
46		try {
47			constructor= getTestConstructor(theClass);
48		} catch (NoSuchMethodException e) {
49			return warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()");
50		}
51		Object test;
52		try {
53			if (constructor.getParameterTypes().length == 0) {
54				test= constructor.newInstance(new Object[0]);
55				if (test instanceof TestCase)
56					((TestCase) test).setName(name);
57			} else {
58				test= constructor.newInstance(new Object[]{name});
59			}
60		} catch (InstantiationException e) {
61			return(warning("Cannot instantiate test case: "+name+" ("+exceptionToString(e)+")"));
62		} catch (InvocationTargetException e) {
63			return(warning("Exception in constructor: "+name+" ("+exceptionToString(e.getTargetException())+")"));
64		} catch (IllegalAccessException e) {
65			return(warning("Cannot access test case: "+name+" ("+exceptionToString(e)+")"));
66		}
67		return (Test) test;
68	}
69
70	/**
71	 * Gets a constructor which takes a single String as
72	 * its argument or a no arg constructor.
73	 */
74	public static Constructor getTestConstructor(Class theClass) throws NoSuchMethodException {
75		Class[] args= { String.class };
76		try {
77			return theClass.getConstructor(args);
78		} catch (NoSuchMethodException e) {
79			// fall through
80		}
81		return theClass.getConstructor(new Class[0]);
82	}
83
84	/**
85	 * Returns a test which will fail and log a warning message.
86	 */
87	public static Test warning(final String message) {
88		return new TestCase("warning") {
89			protected void runTest() {
90				fail(message);
91			}
92		};
93	}
94
95	/**
96	 * Converts the stack trace into a string
97	 */
98	private static String exceptionToString(Throwable t) {
99		StringWriter stringWriter= new StringWriter();
100		PrintWriter writer= new PrintWriter(stringWriter);
101		t.printStackTrace(writer);
102		return stringWriter.toString();
103
104	}
105	private String fName;
106
107	private Vector fTests= new Vector(10);
108
109    /**
110	 * Constructs an empty TestSuite.
111	 */
112	public TestSuite() {
113	}
114
115	/**
116	 * Constructs a TestSuite from the given class. Adds all the methods
117	 * starting with "test" as test cases to the suite.
118	 * Parts of this method was written at 2337 meters in the Hueffihuette,
119	 * Kanton Uri
120	 */
121	 public TestSuite(final Class theClass) {
122		fName= theClass.getName();
123		try {
124			getTestConstructor(theClass); // Avoid generating multiple error messages
125		} catch (NoSuchMethodException e) {
126			addTest(warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()"));
127			return;
128		}
129
130		if (!Modifier.isPublic(theClass.getModifiers())) {
131			addTest(warning("Class "+theClass.getName()+" is not public"));
132			return;
133		}
134
135		Class superClass= theClass;
136		Vector names= new Vector();
137		while (Test.class.isAssignableFrom(superClass)) {
138			Method[] methods= superClass.getDeclaredMethods();
139			for (int i= 0; i < methods.length; i++) {
140				addTestMethod(methods[i], names, theClass);
141			}
142			superClass= superClass.getSuperclass();
143		}
144		if (fTests.size() == 0)
145			addTest(warning("No tests found in "+theClass.getName()));
146	}
147
148	/**
149	 * Constructs a TestSuite from the given class with the given name.
150	 * @see TestSuite#TestSuite(Class)
151	 */
152	public TestSuite(Class theClass, String name) {
153		this(theClass);
154		setName(name);
155	}
156
157   	/**
158	 * Constructs an empty TestSuite.
159	 */
160	public TestSuite(String name) {
161		setName(name);
162	}
163
164	/**
165	 * Constructs a TestSuite from the given array of classes.
166	 * @param classes
167	 */
168	public TestSuite (Class[] classes) {
169		for (int i= 0; i < classes.length; i++)
170			addTest(new TestSuite(classes[i]));
171	}
172
173	/**
174	 * Constructs a TestSuite from the given array of classes with the given name.
175	 * @see TestSuite#TestSuite(Class[])
176	 */
177	public TestSuite(Class[] classes, String name) {
178		this(classes);
179		setName(name);
180	}
181
182	/**
183	 * Adds a test to the suite.
184	 */
185	public void addTest(Test test) {
186		fTests.addElement(test);
187	}
188
189	/**
190	 * Adds the tests from the given class to the suite
191	 */
192	public void addTestSuite(Class testClass) {
193		addTest(new TestSuite(testClass));
194	}
195
196	/**
197	 * Counts the number of test cases that will be run by this test.
198	 */
199	public int countTestCases() {
200		int count= 0;
201		for (Enumeration e= tests(); e.hasMoreElements(); ) {
202			Test test= (Test)e.nextElement();
203			count= count + test.countTestCases();
204		}
205		return count;
206	}
207
208	/**
209	 * Returns the name of the suite. Not all
210	 * test suites have a name and this method
211	 * can return null.
212	 */
213	public String getName() {
214		return fName;
215	}
216
217	/**
218	 * Runs the tests and collects their result in a TestResult.
219	 */
220	public void run(TestResult result) {
221		for (Enumeration e= tests(); e.hasMoreElements(); ) {
222	  		if (result.shouldStop() )
223	  			break;
224			Test test= (Test)e.nextElement();
225			runTest(test, result);
226		}
227	}
228
229	public void runTest(Test test, TestResult result) {
230		test.run(result);
231	}
232
233	/**
234	 * Sets the name of the suite.
235	 * @param name The name to set
236	 */
237	public void setName(String name) {
238		fName= name;
239	}
240
241	/**
242	 * Returns the test at the given index
243	 */
244	public Test testAt(int index) {
245		return (Test)fTests.elementAt(index);
246	}
247
248	/**
249	 * Returns the number of tests in this suite
250	 */
251	public int testCount() {
252		return fTests.size();
253	}
254
255	/**
256	 * Returns the tests as an enumeration
257	 */
258	public Enumeration tests() {
259		return fTests.elements();
260	}
261
262	/**
263	 */
264	public String toString() {
265		if (getName() != null)
266			return getName();
267		return super.toString();
268	 }
269
270	private void addTestMethod(Method m, Vector names, Class theClass) {
271		String name= m.getName();
272		if (names.contains(name))
273			return;
274		if (! isPublicTestMethod(m)) {
275			if (isTestMethod(m))
276				addTest(warning("Test method isn't public: "+m.getName()));
277			return;
278		}
279		names.addElement(name);
280		addTest(createTest(theClass, name));
281	}
282
283	private boolean isPublicTestMethod(Method m) {
284		return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
285	 }
286
287	private boolean isTestMethod(Method m) {
288		String name= m.getName();
289		Class[] parameters= m.getParameterTypes();
290		Class returnType= m.getReturnType();
291		return parameters.length == 0 && name.startsWith("test") && returnType.equals(Void.TYPE);
292	 }
293}