View Javadoc
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       */
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       */
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       */
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       */
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       */
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      */
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/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 
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 }