• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.junit.runner;
2 
3 import java.io.Serializable;
4 import java.lang.annotation.Annotation;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.Collection;
8 import java.util.concurrent.ConcurrentLinkedQueue;
9 import java.util.regex.Matcher;
10 import java.util.regex.Pattern;
11 
12 /**
13  * A <code>Description</code> describes a test which is to be run or has been run. <code>Descriptions</code>
14  * can be atomic (a single test) or compound (containing children tests). <code>Descriptions</code> are used
15  * to provide feedback about the tests that are about to run (for example, the tree view
16  * visible in many IDEs) or tests that have been run (for example, the failures view).
17  * <p>
18  * <code>Descriptions</code> are implemented as a single class rather than a Composite because
19  * they are entirely informational. They contain no logic aside from counting their tests.
20  * <p>
21  * In the past, we used the raw {@link junit.framework.TestCase}s and {@link junit.framework.TestSuite}s
22  * to display the tree of tests. This was no longer viable in JUnit 4 because atomic tests no longer have
23  * a superclass below {@link Object}. We needed a way to pass a class and name together. Description
24  * emerged from this.
25  *
26  * @see org.junit.runner.Request
27  * @see org.junit.runner.Runner
28  * @since 4.0
29  */
30 public class Description implements Serializable {
31     private static final long serialVersionUID = 1L;
32 
33     private static final Pattern METHOD_AND_CLASS_NAME_PATTERN = Pattern
34             .compile("([\\s\\S]*)\\((.*)\\)");
35 
36     /**
37      * Create a <code>Description</code> named <code>name</code>.
38      * Generally, you will add children to this <code>Description</code>.
39      *
40      * @param name the name of the <code>Description</code>
41      * @param annotations meta-data about the test, for downstream interpreters
42      * @return a <code>Description</code> named <code>name</code>
43      */
createSuiteDescription(String name, Annotation... annotations)44     public static Description createSuiteDescription(String name, Annotation... annotations) {
45         return new Description(null, name, annotations);
46     }
47 
48     /**
49      * Create a <code>Description</code> named <code>name</code>.
50      * Generally, you will add children to this <code>Description</code>.
51      *
52      * @param name the name of the <code>Description</code>
53      * @param uniqueId an arbitrary object used to define uniqueness (in {@link #equals(Object)}
54      * @param annotations meta-data about the test, for downstream interpreters
55      * @return a <code>Description</code> named <code>name</code>
56      */
createSuiteDescription(String name, Serializable uniqueId, Annotation... annotations)57     public static Description createSuiteDescription(String name, Serializable uniqueId, Annotation... annotations) {
58         return new Description(null, name, uniqueId, annotations);
59     }
60 
61     /**
62      * Create a <code>Description</code> of a single test named <code>name</code> in the 'class' named
63      * <code>className</code>. Generally, this will be a leaf <code>Description</code>. This method is a better choice
64      * than {@link #createTestDescription(Class, String, Annotation...)} for test runners whose test cases are not
65      * defined in an actual Java <code>Class</code>.
66      *
67      * @param className the class name of the test
68      * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
69      * @param annotations meta-data about the test, for downstream interpreters
70      * @return a <code>Description</code> named <code>name</code>
71      */
createTestDescription(String className, String name, Annotation... annotations)72     public static Description createTestDescription(String className, String name, Annotation... annotations) {
73         return new Description(null, formatDisplayName(name, className), annotations);
74     }
75 
76     /**
77      * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>.
78      * Generally, this will be a leaf <code>Description</code>.
79      *
80      * @param clazz the class of the test
81      * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
82      * @param annotations meta-data about the test, for downstream interpreters
83      * @return a <code>Description</code> named <code>name</code>
84      */
createTestDescription(Class<?> clazz, String name, Annotation... annotations)85     public static Description createTestDescription(Class<?> clazz, String name, Annotation... annotations) {
86         return new Description(clazz, formatDisplayName(name, clazz.getName()), annotations);
87     }
88 
89     /**
90      * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>.
91      * Generally, this will be a leaf <code>Description</code>.
92      * (This remains for binary compatibility with clients of JUnit 4.3)
93      *
94      * @param clazz the class of the test
95      * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
96      * @return a <code>Description</code> named <code>name</code>
97      */
createTestDescription(Class<?> clazz, String name)98     public static Description createTestDescription(Class<?> clazz, String name) {
99         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      */
createTestDescription(String className, String name, Serializable uniqueId)109     public static Description createTestDescription(String className, String name, Serializable uniqueId) {
110         return new Description(null, formatDisplayName(name, className), uniqueId);
111     }
112 
formatDisplayName(String name, String className)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      */
createSuiteDescription(Class<?> testClass)123     public static Description createSuiteDescription(Class<?> testClass) {
124         return new Description(testClass, testClass.getName(), testClass.getAnnotations());
125     }
126 
127     /**
128      * Create a <code>Description</code> named after <code>testClass</code>
129      *
130      * @param testClass A not null {@link Class} containing tests
131      * @param annotations meta-data about the test, for downstream interpreters
132      * @return a <code>Description</code> of <code>testClass</code>
133      */
createSuiteDescription(Class<?> testClass, Annotation... annotations)134     public static Description createSuiteDescription(Class<?> testClass, Annotation... annotations) {
135         return new Description(testClass, testClass.getName(), annotations);
136     }
137 
138     /**
139      * Describes a Runner which runs no tests
140      */
141     public static final Description EMPTY = new Description(null, "No Tests");
142 
143     /**
144      * Describes a step in the test-running mechanism that goes so wrong no
145      * other description can be used (for example, an exception thrown from a Runner's
146      * constructor
147      */
148     public static final Description TEST_MECHANISM = new Description(null, "Test mechanism");
149 
150     /*
151      * We have to use the f prefix until the next major release to ensure
152      * serialization compatibility.
153      * See https://github.com/junit-team/junit4/issues/976
154      */
155     private final Collection<Description> fChildren = new ConcurrentLinkedQueue<Description>();
156     private final String fDisplayName;
157     private final Serializable fUniqueId;
158     private final Annotation[] fAnnotations;
159     private volatile /* write-once */ Class<?> fTestClass;
160 
Description(Class<?> clazz, String displayName, Annotation... annotations)161     private Description(Class<?> clazz, String displayName, Annotation... annotations) {
162         this(clazz, displayName, displayName, annotations);
163     }
164 
Description(Class<?> testClass, String displayName, Serializable uniqueId, Annotation... annotations)165     private Description(Class<?> testClass, String displayName, Serializable uniqueId, Annotation... annotations) {
166         if ((displayName == null) || (displayName.length() == 0)) {
167             throw new IllegalArgumentException(
168                     "The display name must not be empty.");
169         }
170         if ((uniqueId == null)) {
171             throw new IllegalArgumentException(
172                     "The unique id must not be null.");
173         }
174         this.fTestClass = testClass;
175         this.fDisplayName = displayName;
176         this.fUniqueId = uniqueId;
177         this.fAnnotations = annotations;
178     }
179 
180     /**
181      * @return a user-understandable label
182      */
getDisplayName()183     public String getDisplayName() {
184         return fDisplayName;
185     }
186 
187     /**
188      * Add <code>Description</code> as a child of the receiver.
189      *
190      * @param description the soon-to-be child.
191      */
addChild(Description description)192     public void addChild(Description description) {
193         fChildren.add(description);
194     }
195 
196     /**
197      * Gets the copy of the children of this {@code Description}.
198      * Returns an empty list if there are no children.
199      */
getChildren()200     public ArrayList<Description> getChildren() {
201         return new ArrayList<Description>(fChildren);
202     }
203 
204     /**
205      * @return <code>true</code> if the receiver is a suite
206      */
isSuite()207     public boolean isSuite() {
208         return !isTest();
209     }
210 
211     /**
212      * @return <code>true</code> if the receiver is an atomic test
213      */
isTest()214     public boolean isTest() {
215         return fChildren.isEmpty();
216     }
217 
218     /**
219      * @return the total number of atomic tests in the receiver
220      */
testCount()221     public int testCount() {
222         if (isTest()) {
223             return 1;
224         }
225         int result = 0;
226         for (Description child : fChildren) {
227             result += child.testCount();
228         }
229         return result;
230     }
231 
232     @Override
hashCode()233     public int hashCode() {
234         return fUniqueId.hashCode();
235     }
236 
237     @Override
equals(Object obj)238     public boolean equals(Object obj) {
239         if (!(obj instanceof Description)) {
240             return false;
241         }
242         Description d = (Description) obj;
243         return fUniqueId.equals(d.fUniqueId);
244     }
245 
246     @Override
toString()247     public String toString() {
248         return getDisplayName();
249     }
250 
251     /**
252      * @return true if this is a description of a Runner that runs no tests
253      */
isEmpty()254     public boolean isEmpty() {
255         return equals(EMPTY);
256     }
257 
258     /**
259      * @return a copy of this description, with no children (on the assumption that some of the
260      *         children will be added back)
261      */
childlessCopy()262     public Description childlessCopy() {
263         return new Description(fTestClass, fDisplayName, fAnnotations);
264     }
265 
266     /**
267      * @return the annotation of type annotationType that is attached to this description node,
268      *         or null if none exists
269      */
getAnnotation(Class<T> annotationType)270     public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
271         for (Annotation each : fAnnotations) {
272             if (each.annotationType().equals(annotationType)) {
273                 return annotationType.cast(each);
274             }
275         }
276         return null;
277     }
278 
279     /**
280      * @return all of the annotations attached to this description node
281      */
getAnnotations()282     public Collection<Annotation> getAnnotations() {
283         return Arrays.asList(fAnnotations);
284     }
285 
286     /**
287      * @return If this describes a method invocation,
288      *         the class of the test instance.
289      */
getTestClass()290     public Class<?> getTestClass() {
291         if (fTestClass != null) {
292             return fTestClass;
293         }
294         String name = getClassName();
295         if (name == null) {
296             return null;
297         }
298         try {
299             fTestClass = Class.forName(name, false, getClass().getClassLoader());
300             return fTestClass;
301         } catch (ClassNotFoundException e) {
302             return null;
303         }
304     }
305 
306     /**
307      * @return If this describes a method invocation,
308      *         the name of the class of the test instance
309      */
getClassName()310     public String getClassName() {
311         return fTestClass != null ? fTestClass.getName() : methodAndClassNamePatternGroupOrDefault(2, toString());
312     }
313 
314     /**
315      * @return If this describes a method invocation,
316      *         the name of the method (or null if not)
317      */
getMethodName()318     public String getMethodName() {
319         return methodAndClassNamePatternGroupOrDefault(1, null);
320     }
321 
methodAndClassNamePatternGroupOrDefault(int group, String defaultString)322     private String methodAndClassNamePatternGroupOrDefault(int group,
323             String defaultString) {
324         Matcher matcher = METHOD_AND_CLASS_NAME_PATTERN.matcher(toString());
325         return matcher.matches() ? matcher.group(group) : defaultString;
326     }
327 }