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 }