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/junit4/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 }