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 }