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 * 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 */ createSuiteDescription(Class<?> testClass, Annotation... annotations)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 Description(Class<?> clazz, String displayName, Annotation... annotations)161 private Description(Class<?> clazz, String displayName, Annotation... annotations) { 162 this(clazz, displayName, displayName, annotations); 163 } 164 Description(Class<?> testClass, String displayName, Serializable uniqueId, Annotation... annotations)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 */ getDisplayName()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 */ addChild(Description description)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 */ getChildren()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 */ isSuite()207 public boolean isSuite() { 208 return !isTest(); 209 } 210 211 /** 212 * @return <code>true</code> if the receiver is an atomic test 213 */ isTest()214 public boolean isTest() { 215 return fChildren.isEmpty(); 216 } 217 218 /** 219 * @return the total number of atomic tests in the receiver 220 */ testCount()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 hashCode()233 public int hashCode() { 234 return fUniqueId.hashCode(); 235 } 236 237 @Override equals(Object obj)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 toString()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 */ isEmpty()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 */ childlessCopy()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 */ getAnnotation(Class<T> annotationType)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 */ getAnnotations()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 */ getTestClass()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 */ getClassName()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 */ getMethodName()318 public String getMethodName() { 319 return methodAndClassNamePatternGroupOrDefault(1, null); 320 } 321 methodAndClassNamePatternGroupOrDefault(int group, String defaultString)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 }