1 package junit.framework;
2
3 import java.io.PrintWriter;
4 import java.io.StringWriter;
5 import java.lang.reflect.Constructor;
6 import java.lang.reflect.InvocationTargetException;
7 import java.lang.reflect.Method;
8 import java.lang.reflect.Modifier;
9 import java.util.ArrayList;
10 import java.util.Enumeration;
11 import java.util.List;
12 import 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 */
45 public 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<? extends TestCase> theClass, String name) {
52 Constructor<? extends TestCase> 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<? extends TestCase> getTestConstructor(Class<? extends TestCase> 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<? extends TestCase> theClass) {
129 fName= theClass.getName();
130 try {
131 getTestConstructor(theClass); // Avoid generating multiple error messages
132 } catch (NoSuchMethodException e) {
133 addTest(warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()"));
134 return;
135 }
136
137 if (!Modifier.isPublic(theClass.getModifiers())) {
138 addTest(warning("Class "+theClass.getName()+" is not public"));
139 return;
140 }
141
142 Class<?> superClass= theClass;
143 List<String> names= new ArrayList<String>();
144 while (Test.class.isAssignableFrom(superClass)) {
145 for (Method each : superClass.getDeclaredMethods())
146 addTestMethod(each, names, theClass);
147 superClass= superClass.getSuperclass();
148 }
149 if (fTests.size() == 0)
150 addTest(warning("No tests found in "+theClass.getName()));
151 }
152
153 /**
154 * Constructs a TestSuite from the given class with the given name.
155 * @see TestSuite#TestSuite(Class)
156 */
157 public TestSuite(Class<? extends TestCase> theClass, String name) {
158 this(theClass);
159 setName(name);
160 }
161
162 /**
163 * Constructs an empty TestSuite.
164 */
165 public TestSuite(String name) {
166 setName(name);
167 }
168
169 /**
170 * Constructs a TestSuite from the given array of classes.
171 * @param classes {@link TestCase}s
172 */
173 public TestSuite (Class<?>... classes) {
174 for (Class<?> each : classes)
175 addTest(new TestSuite(each.asSubclass(TestCase.class)));
176 }
177
178 /**
179 * Constructs a TestSuite from the given array of classes with the given name.
180 * @see TestSuite#TestSuite(Class[])
181 */
182 public TestSuite(Class<? extends TestCase>[] classes, String name) {
183 this(classes);
184 setName(name);
185 }
186
187 /**
188 * Adds a test to the suite.
189 */
190 public void addTest(Test test) {
191 fTests.add(test);
192 }
193
194 /**
195 * Adds the tests from the given class to the suite
196 */
197 public void addTestSuite(Class<? extends TestCase> testClass) {
198 addTest(new TestSuite(testClass));
199 }
200
201 /**
202 * Counts the number of test cases that will be run by this test.
203 */
204 public int countTestCases() {
205 int count= 0;
206 for (Test each : fTests)
207 count+= each.countTestCases();
208 return count;
209 }
210
211 /**
212 * Returns the name of the suite. Not all
213 * test suites have a name and this method
214 * can return null.
215 */
216 public String getName() {
217 return fName;
218 }
219
220 /**
221 * Runs the tests and collects their result in a TestResult.
222 */
223 public void run(TestResult result) {
224 for (Test each : fTests) {
225 if (result.shouldStop() )
226 break;
227 runTest(each, result);
228 }
229 }
230
231 public void runTest(Test test, TestResult result) {
232 test.run(result);
233 }
234
235 /**
236 * Sets the name of the suite.
237 * @param name the name to set
238 */
239 public void setName(String name) {
240 fName= name;
241 }
242
243 /**
244 * Returns the test at the given index
245 */
246 public Test testAt(int index) {
247 return fTests.get(index);
248 }
249
250 /**
251 * Returns the number of tests in this suite
252 */
253 public int testCount() {
254 return fTests.size();
255 }
256
257 /**
258 * Returns the tests as an enumeration
259 */
260 public Enumeration<Test> tests() {
261 return fTests.elements();
262 }
263
264 /**
265 */
266 @Override
267 public String toString() {
268 if (getName() != null)
269 return getName();
270 return super.toString();
271 }
272
273 private void addTestMethod(Method m, List<String> names, Class<? extends TestCase> theClass) {
274 String name= m.getName();
275 if (names.contains(name))
276 return;
277 if (! isPublicTestMethod(m)) {
278 if (isTestMethod(m))
279 addTest(warning("Test method isn't public: "+ m.getName() + "(" + theClass.getCanonicalName() + ")"));
280 return;
281 }
282 names.add(name);
283 addTest(createTest(theClass, name));
284 }
285
286 private boolean isPublicTestMethod(Method m) {
287 return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
288 }
289
290 private boolean isTestMethod(Method m) {
291 return
292 m.getParameterTypes().length == 0 &&
293 m.getName().startsWith("test") &&
294 m.getReturnType().equals(Void.TYPE);
295 }
296 }