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