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 * 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 }