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 * 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 Description(Class<?> clazz, String displayName, Annotation... annotations)150 private Description(Class<?> clazz, String displayName, Annotation... annotations) { 151 this(clazz, displayName, displayName, annotations); 152 } 153 Description(Class<?> testClass, String displayName, Serializable uniqueId, Annotation... annotations)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 */ getDisplayName()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 */ addChild(Description description)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 */ getChildren()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 */ isSuite()196 public boolean isSuite() { 197 return !isTest(); 198 } 199 200 /** 201 * @return <code>true</code> if the receiver is an atomic test 202 */ isTest()203 public boolean isTest() { 204 return fChildren.isEmpty(); 205 } 206 207 /** 208 * @return the total number of atomic tests in the receiver 209 */ testCount()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 hashCode()222 public int hashCode() { 223 return fUniqueId.hashCode(); 224 } 225 226 @Override equals(Object obj)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 toString()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 */ isEmpty()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 */ childlessCopy()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 */ getAnnotation(Class<T> annotationType)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 */ getAnnotations()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 */ getTestClass()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 */ getClassName()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 */ getMethodName()307 public String getMethodName() { 308 return methodAndClassNamePatternGroupOrDefault(1, null); 309 } 310 methodAndClassNamePatternGroupOrDefault(int group, String defaultString)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 }