001 package org.junit.runner;
002
003 import java.io.Serializable;
004 import java.lang.annotation.Annotation;
005 import java.util.ArrayList;
006 import java.util.Arrays;
007 import java.util.Collection;
008 import java.util.concurrent.ConcurrentLinkedQueue;
009 import java.util.regex.Matcher;
010 import java.util.regex.Pattern;
011
012 /**
013 * A <code>Description</code> describes a test which is to be run or has been run. <code>Descriptions</code>
014 * can be atomic (a single test) or compound (containing children tests). <code>Descriptions</code> are used
015 * to provide feedback about the tests that are about to run (for example, the tree view
016 * visible in many IDEs) or tests that have been run (for example, the failures view).
017 * <p>
018 * <code>Descriptions</code> are implemented as a single class rather than a Composite because
019 * they are entirely informational. They contain no logic aside from counting their tests.
020 * <p>
021 * In the past, we used the raw {@link junit.framework.TestCase}s and {@link junit.framework.TestSuite}s
022 * to display the tree of tests. This was no longer viable in JUnit 4 because atomic tests no longer have
023 * a superclass below {@link Object}. We needed a way to pass a class and name together. Description
024 * emerged from this.
025 *
026 * @see org.junit.runner.Request
027 * @see org.junit.runner.Runner
028 * @since 4.0
029 */
030 public class Description implements Serializable {
031 private static final long serialVersionUID = 1L;
032
033 private static final Pattern METHOD_AND_CLASS_NAME_PATTERN = Pattern
034 .compile("([\\s\\S]*)\\((.*)\\)");
035
036 /**
037 * Create a <code>Description</code> named <code>name</code>.
038 * Generally, you will add children to this <code>Description</code>.
039 *
040 * @param name the name of the <code>Description</code>
041 * @param annotations meta-data about the test, for downstream interpreters
042 * @return a <code>Description</code> named <code>name</code>
043 */
044 public static Description createSuiteDescription(String name, Annotation... annotations) {
045 return new Description(null, name, annotations);
046 }
047
048 /**
049 * Create a <code>Description</code> named <code>name</code>.
050 * Generally, you will add children to this <code>Description</code>.
051 *
052 * @param name the name of the <code>Description</code>
053 * @param uniqueId an arbitrary object used to define uniqueness (in {@link #equals(Object)}
054 * @param annotations meta-data about the test, for downstream interpreters
055 * @return a <code>Description</code> named <code>name</code>
056 */
057 public static Description createSuiteDescription(String name, Serializable uniqueId, Annotation... annotations) {
058 return new Description(null, name, uniqueId, annotations);
059 }
060
061 /**
062 * Create a <code>Description</code> of a single test named <code>name</code> in the 'class' named
063 * <code>className</code>. Generally, this will be a leaf <code>Description</code>. This method is a better choice
064 * than {@link #createTestDescription(Class, String, Annotation...)} for test runners whose test cases are not
065 * defined in an actual Java <code>Class</code>.
066 *
067 * @param className the class name of the test
068 * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
069 * @param annotations meta-data about the test, for downstream interpreters
070 * @return a <code>Description</code> named <code>name</code>
071 */
072 public static Description createTestDescription(String className, String name, Annotation... annotations) {
073 return new Description(null, formatDisplayName(name, className), annotations);
074 }
075
076 /**
077 * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>.
078 * Generally, this will be a leaf <code>Description</code>.
079 *
080 * @param clazz the class of the test
081 * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
082 * @param annotations meta-data about the test, for downstream interpreters
083 * @return a <code>Description</code> named <code>name</code>
084 */
085 public static Description createTestDescription(Class<?> clazz, String name, Annotation... annotations) {
086 return new Description(clazz, formatDisplayName(name, clazz.getName()), annotations);
087 }
088
089 /**
090 * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>.
091 * Generally, this will be a leaf <code>Description</code>.
092 * (This remains for binary compatibility with clients of JUnit 4.3)
093 *
094 * @param clazz the class of the test
095 * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
096 * @return a <code>Description</code> named <code>name</code>
097 */
098 public static Description createTestDescription(Class<?> clazz, String name) {
099 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 * 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 */
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
161 private Description(Class<?> clazz, String displayName, Annotation... annotations) {
162 this(clazz, displayName, displayName, annotations);
163 }
164
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 */
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 */
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 */
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 */
207 public boolean isSuite() {
208 return !isTest();
209 }
210
211 /**
212 * @return <code>true</code> if the receiver is an atomic test
213 */
214 public boolean isTest() {
215 return fChildren.isEmpty();
216 }
217
218 /**
219 * @return the total number of atomic tests in the receiver
220 */
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
233 public int hashCode() {
234 return fUniqueId.hashCode();
235 }
236
237 @Override
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
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 */
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 */
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 */
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 */
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 */
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 */
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 */
318 public String getMethodName() {
319 return methodAndClassNamePatternGroupOrDefault(1, null);
320 }
321
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 }