001    package org.junit.runner;
002    
003    import java.io.Serializable;
004    import java.lang.annotation.Annotation;
005    import java.util.ArrayList;
006    import java.util.Arrays;
007    import java.util.Collection;
008    import java.util.concurrent.ConcurrentLinkedQueue;
009    import java.util.regex.Matcher;
010    import java.util.regex.Pattern;
011    
012    /**
013     * A <code>Description</code> describes a test which is to be run or has been run. <code>Descriptions</code>
014     * can be atomic (a single test) or compound (containing children tests). <code>Descriptions</code> are used
015     * to provide feedback about the tests that are about to run (for example, the tree view
016     * visible in many IDEs) or tests that have been run (for example, the failures view).
017     * <p>
018     * <code>Descriptions</code> are implemented as a single class rather than a Composite because
019     * they are entirely informational. They contain no logic aside from counting their tests.
020     * <p>
021     * In the past, we used the raw {@link junit.framework.TestCase}s and {@link junit.framework.TestSuite}s
022     * to display the tree of tests. This was no longer viable in JUnit 4 because atomic tests no longer have
023     * a superclass below {@link Object}. We needed a way to pass a class and name together. Description
024     * emerged from this.
025     *
026     * @see org.junit.runner.Request
027     * @see org.junit.runner.Runner
028     * @since 4.0
029     */
030    public class Description implements Serializable {
031        private static final long serialVersionUID = 1L;
032    
033        private static final Pattern METHOD_AND_CLASS_NAME_PATTERN = Pattern
034                .compile("([\\s\\S]*)\\((.*)\\)");
035    
036        /**
037         * Create a <code>Description</code> named <code>name</code>.
038         * Generally, you will add children to this <code>Description</code>.
039         *
040         * @param name the name of the <code>Description</code>
041         * @param annotations meta-data about the test, for downstream interpreters
042         * @return a <code>Description</code> named <code>name</code>
043         */
044        public static Description createSuiteDescription(String name, Annotation... annotations) {
045            return new Description(null, name, annotations);
046        }
047    
048        /**
049         * Create a <code>Description</code> named <code>name</code>.
050         * Generally, you will add children to this <code>Description</code>.
051         *
052         * @param name the name of the <code>Description</code>
053         * @param uniqueId an arbitrary object used to define uniqueness (in {@link #equals(Object)}
054         * @param annotations meta-data about the test, for downstream interpreters
055         * @return a <code>Description</code> named <code>name</code>
056         */
057        public static Description createSuiteDescription(String name, Serializable uniqueId, Annotation... annotations) {
058            return new Description(null, name, uniqueId, annotations);
059        }
060    
061        /**
062         * Create a <code>Description</code> of a single test named <code>name</code> in the 'class' named
063         * <code>className</code>. Generally, this will be a leaf <code>Description</code>. This method is a better choice
064         * than {@link #createTestDescription(Class, String, Annotation...)} for test runners whose test cases are not
065         * defined in an actual Java <code>Class</code>.
066         *
067         * @param className the class name of the test
068         * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
069         * @param annotations meta-data about the test, for downstream interpreters
070         * @return a <code>Description</code> named <code>name</code>
071         */
072        public static Description createTestDescription(String className, String name, Annotation... annotations) {
073            return new Description(null, formatDisplayName(name, className), annotations);
074        }
075    
076        /**
077         * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>.
078         * Generally, this will be a leaf <code>Description</code>.
079         *
080         * @param clazz the class of the test
081         * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
082         * @param annotations meta-data about the test, for downstream interpreters
083         * @return a <code>Description</code> named <code>name</code>
084         */
085        public static Description createTestDescription(Class<?> clazz, String name, Annotation... annotations) {
086            return new Description(clazz, formatDisplayName(name, clazz.getName()), annotations);
087        }
088    
089        /**
090         * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>.
091         * Generally, this will be a leaf <code>Description</code>.
092         * (This remains for binary compatibility with clients of JUnit 4.3)
093         *
094         * @param clazz the class of the test
095         * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
096         * @return a <code>Description</code> named <code>name</code>
097         */
098        public static Description createTestDescription(Class<?> clazz, String name) {
099            return new Description(clazz, formatDisplayName(name, clazz.getName()));
100        }
101    
102        /**
103         * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>.
104         * Generally, this will be a leaf <code>Description</code>.
105         *
106         * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
107         * @return a <code>Description</code> named <code>name</code>
108         */
109        public static Description createTestDescription(String className, String name, Serializable uniqueId) {
110            return new Description(null, formatDisplayName(name, className), uniqueId);
111        }
112    
113        private static String formatDisplayName(String name, String className) {
114            return String.format("%s(%s)", name, className);
115        }
116    
117        /**
118         * Create a <code>Description</code> named after <code>testClass</code>
119         *
120         * @param testClass A {@link Class} containing tests
121         * @return a <code>Description</code> of <code>testClass</code>
122         */
123        public static Description createSuiteDescription(Class<?> testClass) {
124            return new Description(testClass, testClass.getName(), testClass.getAnnotations());
125        }
126    
127        /**
128         * Describes a Runner which runs no tests
129         */
130        public static final Description EMPTY = new Description(null, "No Tests");
131    
132        /**
133         * Describes a step in the test-running mechanism that goes so wrong no
134         * other description can be used (for example, an exception thrown from a Runner's
135         * constructor
136         */
137        public static final Description TEST_MECHANISM = new Description(null, "Test mechanism");
138    
139        /*
140         * We have to use the f prefix until the next major release to ensure
141         * serialization compatibility. 
142         * See https://github.com/junit-team/junit4/issues/976
143         */
144        private final Collection<Description> fChildren = new ConcurrentLinkedQueue<Description>();
145        private final String fDisplayName;
146        private final Serializable fUniqueId;
147        private final Annotation[] fAnnotations;
148        private volatile /* write-once */ Class<?> fTestClass;
149    
150        private Description(Class<?> clazz, String displayName, Annotation... annotations) {
151            this(clazz, displayName, displayName, annotations);
152        }
153    
154        private Description(Class<?> testClass, String displayName, Serializable uniqueId, Annotation... annotations) {
155            if ((displayName == null) || (displayName.length() == 0)) {
156                throw new IllegalArgumentException(
157                        "The display name must not be empty.");
158            }
159            if ((uniqueId == null)) {
160                throw new IllegalArgumentException(
161                        "The unique id must not be null.");
162            }
163            this.fTestClass = testClass;
164            this.fDisplayName = displayName;
165            this.fUniqueId = uniqueId;
166            this.fAnnotations = annotations;
167        }
168    
169        /**
170         * @return a user-understandable label
171         */
172        public String getDisplayName() {
173            return fDisplayName;
174        }
175    
176        /**
177         * Add <code>Description</code> as a child of the receiver.
178         *
179         * @param description the soon-to-be child.
180         */
181        public void addChild(Description description) {
182            fChildren.add(description);
183        }
184    
185        /**
186         * Gets the copy of the children of this {@code Description}.
187         * Returns an empty list if there are no children.
188         */
189        public ArrayList<Description> getChildren() {
190            return new ArrayList<Description>(fChildren);
191        }
192    
193        /**
194         * @return <code>true</code> if the receiver is a suite
195         */
196        public boolean isSuite() {
197            return !isTest();
198        }
199    
200        /**
201         * @return <code>true</code> if the receiver is an atomic test
202         */
203        public boolean isTest() {
204            return fChildren.isEmpty();
205        }
206    
207        /**
208         * @return the total number of atomic tests in the receiver
209         */
210        public int testCount() {
211            if (isTest()) {
212                return 1;
213            }
214            int result = 0;
215            for (Description child : fChildren) {
216                result += child.testCount();
217            }
218            return result;
219        }
220    
221        @Override
222        public int hashCode() {
223            return fUniqueId.hashCode();
224        }
225    
226        @Override
227        public boolean equals(Object obj) {
228            if (!(obj instanceof Description)) {
229                return false;
230            }
231            Description d = (Description) obj;
232            return fUniqueId.equals(d.fUniqueId);
233        }
234    
235        @Override
236        public String toString() {
237            return getDisplayName();
238        }
239    
240        /**
241         * @return true if this is a description of a Runner that runs no tests
242         */
243        public boolean isEmpty() {
244            return equals(EMPTY);
245        }
246    
247        /**
248         * @return a copy of this description, with no children (on the assumption that some of the
249         *         children will be added back)
250         */
251        public Description childlessCopy() {
252            return new Description(fTestClass, fDisplayName, fAnnotations);
253        }
254    
255        /**
256         * @return the annotation of type annotationType that is attached to this description node,
257         *         or null if none exists
258         */
259        public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
260            for (Annotation each : fAnnotations) {
261                if (each.annotationType().equals(annotationType)) {
262                    return annotationType.cast(each);
263                }
264            }
265            return null;
266        }
267    
268        /**
269         * @return all of the annotations attached to this description node
270         */
271        public Collection<Annotation> getAnnotations() {
272            return Arrays.asList(fAnnotations);
273        }
274    
275        /**
276         * @return If this describes a method invocation,
277         *         the class of the test instance.
278         */
279        public Class<?> getTestClass() {
280            if (fTestClass != null) {
281                return fTestClass;
282            }
283            String name = getClassName();
284            if (name == null) {
285                return null;
286            }
287            try {
288                fTestClass = Class.forName(name, false, getClass().getClassLoader());
289                return fTestClass;
290            } catch (ClassNotFoundException e) {
291                return null;
292            }
293        }
294    
295        /**
296         * @return If this describes a method invocation,
297         *         the name of the class of the test instance
298         */
299        public String getClassName() {
300            return fTestClass != null ? fTestClass.getName() : methodAndClassNamePatternGroupOrDefault(2, toString());
301        }
302    
303        /**
304         * @return If this describes a method invocation,
305         *         the name of the method (or null if not)
306         */
307        public String getMethodName() {
308            return methodAndClassNamePatternGroupOrDefault(1, null);
309        }
310    
311        private String methodAndClassNamePatternGroupOrDefault(int group,
312                String defaultString) {
313            Matcher matcher = METHOD_AND_CLASS_NAME_PATTERN.matcher(toString());
314            return matcher.matches() ? matcher.group(group) : defaultString;
315        }
316    }