• 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      * 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/junit/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 
Description(Class<?> clazz, String displayName, Annotation... annotations)150     private Description(Class<?> clazz, String displayName, Annotation... annotations) {
151         this(clazz, displayName, displayName, annotations);
152     }
153 
Description(Class<?> testClass, String displayName, Serializable uniqueId, Annotation... annotations)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      */
getDisplayName()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      */
addChild(Description description)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      */
getChildren()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      */
isSuite()196     public boolean isSuite() {
197         return !isTest();
198     }
199 
200     /**
201      * @return <code>true</code> if the receiver is an atomic test
202      */
isTest()203     public boolean isTest() {
204         return fChildren.isEmpty();
205     }
206 
207     /**
208      * @return the total number of atomic tests in the receiver
209      */
testCount()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
hashCode()222     public int hashCode() {
223         return fUniqueId.hashCode();
224     }
225 
226     @Override
equals(Object obj)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
toString()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      */
isEmpty()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      */
childlessCopy()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      */
getAnnotation(Class<T> annotationType)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      */
getAnnotations()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      */
getTestClass()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      */
getClassName()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      */
getMethodName()307     public String getMethodName() {
308         return methodAndClassNamePatternGroupOrDefault(1, null);
309     }
310 
methodAndClassNamePatternGroupOrDefault(int group, String defaultString)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 }