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