001    package junit.framework;
002    
003    import java.lang.reflect.Constructor;
004    import java.lang.reflect.InvocationTargetException;
005    import java.lang.reflect.Method;
006    import java.lang.reflect.Modifier;
007    import java.util.ArrayList;
008    import java.util.Enumeration;
009    import java.util.List;
010    import java.util.Vector;
011    
012    import org.junit.internal.MethodSorter;
013    import org.junit.internal.Throwables;
014    
015    /**
016     * A <code>TestSuite</code> is a <code>Composite</code> of Tests.
017     * It runs a collection of test cases. Here is an example using
018     * the dynamic test definition.
019     * <pre>
020     * TestSuite suite= new TestSuite();
021     * suite.addTest(new MathTest("testAdd"));
022     * suite.addTest(new MathTest("testDivideByZero"));
023     * </pre>
024     * <p>
025     * Alternatively, a TestSuite can extract the tests to be run automatically.
026     * To do so you pass the class of your TestCase class to the
027     * TestSuite constructor.
028     * <pre>
029     * TestSuite suite= new TestSuite(MathTest.class);
030     * </pre>
031     * <p>
032     * This constructor creates a suite with all the methods
033     * starting with "test" that take no arguments.
034     * <p>
035     * A final option is to do the same for a large array of test classes.
036     * <pre>
037     * Class[] testClasses = { MathTest.class, AnotherTest.class };
038     * TestSuite suite= new TestSuite(testClasses);
039     * </pre>
040     *
041     * @see Test
042     */
043    public class TestSuite implements Test {
044    
045        /**
046         * ...as the moon sets over the early morning Merlin, Oregon
047         * mountains, our intrepid adventurers type...
048         */
049        static public Test createTest(Class<?> theClass, String name) {
050            Constructor<?> constructor;
051            try {
052                constructor = getTestConstructor(theClass);
053            } catch (NoSuchMethodException e) {
054                return warning("Class " + theClass.getName() + " has no public constructor TestCase(String name) or TestCase()");
055            }
056            Object test;
057            try {
058                if (constructor.getParameterTypes().length == 0) {
059                    test = constructor.newInstance(new Object[0]);
060                    if (test instanceof TestCase) {
061                        ((TestCase) test).setName(name);
062                    }
063                } else {
064                    test = constructor.newInstance(new Object[]{name});
065                }
066            } catch (InstantiationException e) {
067                return (warning("Cannot instantiate test case: " + name + " (" + Throwables.getStacktrace(e) + ")"));
068            } catch (InvocationTargetException e) {
069                return (warning("Exception in constructor: " + name + " (" + Throwables.getStacktrace(e.getTargetException()) + ")"));
070            } catch (IllegalAccessException e) {
071                return (warning("Cannot access test case: " + name + " (" + Throwables.getStacktrace(e) + ")"));
072            }
073            return (Test) test;
074        }
075    
076        /**
077         * Gets a constructor which takes a single String as
078         * its argument or a no arg constructor.
079         */
080        public static Constructor<?> getTestConstructor(Class<?> theClass) throws NoSuchMethodException {
081            try {
082                return theClass.getConstructor(String.class);
083            } catch (NoSuchMethodException e) {
084                // fall through
085            }
086            return theClass.getConstructor();
087        }
088    
089        /**
090         * Returns a test which will fail and log a warning message.
091         */
092        public static Test warning(final String message) {
093            return new TestCase("warning") {
094                @Override
095                protected void runTest() {
096                    fail(message);
097                }
098            };
099        }
100    
101        private String fName;
102    
103        private Vector<Test> fTests = new Vector<Test>(10); // Cannot convert this to List because it is used directly by some test runners
104    
105        /**
106         * Constructs an empty TestSuite.
107         */
108        public TestSuite() {
109        }
110    
111        /**
112         * Constructs a TestSuite from the given class. Adds all the methods
113         * starting with "test" as test cases to the suite.
114         * Parts of this method were written at 2337 meters in the Hueffihuette,
115         * Kanton Uri
116         */
117        public TestSuite(final Class<?> theClass) {
118            addTestsFromTestCase(theClass);
119        }
120    
121        private void addTestsFromTestCase(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            List<String> names = new ArrayList<String>();
137            while (Test.class.isAssignableFrom(superClass)) {
138                for (Method each : MethodSorter.getDeclaredMethods(superClass)) {
139                    addTestMethod(each, names, theClass);
140                }
141                superClass = superClass.getSuperclass();
142            }
143            if (fTests.size() == 0) {
144                addTest(warning("No tests found in " + theClass.getName()));
145            }
146        }
147    
148        /**
149         * Constructs a TestSuite from the given class with the given name.
150         *
151         * @see TestSuite#TestSuite(Class)
152         */
153        public TestSuite(Class<? extends TestCase> theClass, String name) {
154            this(theClass);
155            setName(name);
156        }
157    
158        /**
159         * Constructs an empty TestSuite.
160         */
161        public TestSuite(String name) {
162            setName(name);
163        }
164    
165        /**
166         * Constructs a TestSuite from the given array of classes.
167         *
168         * @param classes {@link TestCase}s
169         */
170        public TestSuite(Class<?>... classes) {
171            for (Class<?> each : classes) {
172                addTest(testCaseForClass(each));
173            }
174        }
175    
176        private Test testCaseForClass(Class<?> each) {
177            if (TestCase.class.isAssignableFrom(each)) {
178                return new TestSuite(each.asSubclass(TestCase.class));
179            } else {
180                return warning(each.getCanonicalName() + " does not extend TestCase");
181            }
182        }
183    
184        /**
185         * Constructs a TestSuite from the given array of classes with the given name.
186         *
187         * @see TestSuite#TestSuite(Class[])
188         */
189        public TestSuite(Class<? extends TestCase>[] classes, String name) {
190            this(classes);
191            setName(name);
192        }
193    
194        /**
195         * Adds a test to the suite.
196         */
197        public void addTest(Test test) {
198            fTests.add(test);
199        }
200    
201        /**
202         * Adds the tests from the given class to the suite.
203         */
204        public void addTestSuite(Class<? extends TestCase> testClass) {
205            addTest(new TestSuite(testClass));
206        }
207    
208        /**
209         * Counts the number of test cases that will be run by this test.
210         */
211        public int countTestCases() {
212            int count = 0;
213            for (Test each : fTests) {
214                count += each.countTestCases();
215            }
216            return count;
217        }
218    
219        /**
220         * Returns the name of the suite. Not all
221         * test suites have a name and this method
222         * can return null.
223         */
224        public String getName() {
225            return fName;
226        }
227    
228        /**
229         * Runs the tests and collects their result in a TestResult.
230         */
231        public void run(TestResult result) {
232            for (Test each : fTests) {
233                if (result.shouldStop()) {
234                    break;
235                }
236                runTest(each, result);
237            }
238        }
239    
240        public void runTest(Test test, TestResult result) {
241            test.run(result);
242        }
243    
244        /**
245         * Sets the name of the suite.
246         *
247         * @param name the name to set
248         */
249        public void setName(String name) {
250            fName = name;
251        }
252    
253        /**
254         * Returns the test at the given index.
255         */
256        public Test testAt(int index) {
257            return fTests.get(index);
258        }
259    
260        /**
261         * Returns the number of tests in this suite.
262         */
263        public int testCount() {
264            return fTests.size();
265        }
266    
267        /**
268         * Returns the tests as an enumeration.
269         */
270        public Enumeration<Test> tests() {
271            return fTests.elements();
272        }
273    
274        /**
275         */
276        @Override
277        public String toString() {
278            if (getName() != null) {
279                return getName();
280            }
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            }
289            if (!isPublicTestMethod(m)) {
290                if (isTestMethod(m)) {
291                    addTest(warning("Test method isn't public: " + m.getName() + "(" + theClass.getCanonicalName() + ")"));
292                }
293                return;
294            }
295            names.add(name);
296            addTest(createTest(theClass, name));
297        }
298    
299        private boolean isPublicTestMethod(Method m) {
300            return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
301        }
302    
303        private boolean isTestMethod(Method m) {
304            return m.getParameterTypes().length == 0 &&
305                    m.getName().startsWith("test") &&
306                    m.getReturnType().equals(Void.TYPE);
307        }
308    }