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.regex.Matcher; 9 import java.util.regex.Pattern; 10 11 /** 12 * <p>A <code>Description</code> describes a test which is to be run or has been run. <code>Descriptions</code> 13 * can be atomic (a single test) or compound (containing children tests). <code>Descriptions</code> are used 14 * to provide feedback about the tests that are about to run (for example, the tree view 15 * visible in many IDEs) or tests that have been run (for example, the failures view).</p> 16 * 17 * <p><code>Descriptions</code> are implemented as a single class rather than a Composite because 18 * they are entirely informational. They contain no logic aside from counting their tests.</p> 19 * 20 * <p>In the past, we used the raw {@link junit.framework.TestCase}s and {@link junit.framework.TestSuite}s 21 * to display the tree of tests. This was no longer viable in JUnit 4 because atomic tests no longer have 22 * a superclass below {@link Object}. We needed a way to pass a class and name together. Description 23 * emerged from this.</p> 24 * 25 * @see org.junit.runner.Request 26 * @see org.junit.runner.Runner 27 */ 28 public class Description implements Serializable { 29 private static final long serialVersionUID = 1L; 30 31 /** 32 * Create a <code>Description</code> named <code>name</code>. 33 * Generally, you will add children to this <code>Description</code>. 34 * @param name the name of the <code>Description</code> 35 * @param annotations 36 * @return a <code>Description</code> named <code>name</code> 37 */ createSuiteDescription(String name, Annotation... annotations)38 public static Description createSuiteDescription(String name, Annotation... annotations) { 39 if (name.length() == 0) 40 throw new IllegalArgumentException("name must have non-zero length"); 41 return new Description(name, annotations); 42 } 43 44 /** 45 * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>. 46 * Generally, this will be a leaf <code>Description</code>. 47 * @param clazz the class of the test 48 * @param name the name of the test (a method name for test annotated with {@link org.junit.Test}) 49 * @param annotations meta-data about the test, for downstream interpreters 50 * @return a <code>Description</code> named <code>name</code> 51 */ createTestDescription(Class<?> clazz, String name, Annotation... annotations)52 public static Description createTestDescription(Class<?> clazz, String name, Annotation... annotations) { 53 return new Description(String.format("%s(%s)", name, clazz.getName()), annotations); 54 } 55 56 /** 57 * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>. 58 * Generally, this will be a leaf <code>Description</code>. 59 * (This remains for binary compatibility with clients of JUnit 4.3) 60 * @param clazz the class of the test 61 * @param name the name of the test (a method name for test annotated with {@link org.junit.Test}) 62 * @return a <code>Description</code> named <code>name</code> 63 */ createTestDescription(Class<?> clazz, String name)64 public static Description createTestDescription(Class<?> clazz, String name) { 65 return createTestDescription(clazz, name, new Annotation[0]); 66 } 67 68 /** 69 * Create a <code>Description</code> named after <code>testClass</code> 70 * @param testClass A {@link Class} containing tests 71 * @return a <code>Description</code> of <code>testClass</code> 72 */ createSuiteDescription(Class<?> testClass)73 public static Description createSuiteDescription(Class<?> testClass) { 74 return new Description(testClass.getName(), testClass.getAnnotations()); 75 } 76 77 /** 78 * Describes a Runner which runs no tests 79 */ 80 public static final Description EMPTY= new Description("No Tests"); 81 82 /** 83 * Describes a step in the test-running mechanism that goes so wrong no 84 * other description can be used (for example, an exception thrown from a Runner's 85 * constructor 86 */ 87 public static final Description TEST_MECHANISM= new Description("Test mechanism"); 88 89 private final ArrayList<Description> fChildren= new ArrayList<Description>(); 90 private final String fDisplayName; 91 92 private final Annotation[] fAnnotations; 93 Description(final String displayName, Annotation... annotations)94 private Description(final String displayName, Annotation... annotations) { 95 fDisplayName= displayName; 96 fAnnotations= annotations; 97 } 98 99 /** 100 * @return a user-understandable label 101 */ getDisplayName()102 public String getDisplayName() { 103 return fDisplayName; 104 } 105 106 /** 107 * Add <code>Description</code> as a child of the receiver. 108 * @param description the soon-to-be child. 109 */ addChild(Description description)110 public void addChild(Description description) { 111 getChildren().add(description); 112 } 113 114 /** 115 * @return the receiver's children, if any 116 */ getChildren()117 public ArrayList<Description> getChildren() { 118 return fChildren; 119 } 120 121 /** 122 * @return <code>true</code> if the receiver is a suite 123 */ isSuite()124 public boolean isSuite() { 125 return !isTest(); 126 } 127 128 /** 129 * @return <code>true</code> if the receiver is an atomic test 130 */ isTest()131 public boolean isTest() { 132 return getChildren().isEmpty(); 133 } 134 135 /** 136 * @return the total number of atomic tests in the receiver 137 */ testCount()138 public int testCount() { 139 if (isTest()) 140 return 1; 141 int result= 0; 142 for (Description child : getChildren()) 143 result+= child.testCount(); 144 return result; 145 } 146 147 @Override hashCode()148 public int hashCode() { 149 return getDisplayName().hashCode(); 150 } 151 152 @Override equals(Object obj)153 public boolean equals(Object obj) { 154 if (!(obj instanceof Description)) 155 return false; 156 Description d = (Description) obj; 157 return getDisplayName().equals(d.getDisplayName()); 158 } 159 160 @Override toString()161 public String toString() { 162 return getDisplayName(); 163 } 164 165 /** 166 * @return true if this is a description of a Runner that runs no tests 167 */ isEmpty()168 public boolean isEmpty() { 169 return equals(EMPTY); 170 } 171 172 /** 173 * @return a copy of this description, with no children (on the assumption that some of the 174 * children will be added back) 175 */ childlessCopy()176 public Description childlessCopy() { 177 return new Description(fDisplayName, fAnnotations); 178 } 179 180 /** 181 * @return the annotation of type annotationType that is attached to this description node, 182 * or null if none exists 183 */ getAnnotation(Class<T> annotationType)184 public <T extends Annotation> T getAnnotation(Class<T> annotationType) { 185 for (Annotation each : fAnnotations) 186 if (each.annotationType().equals(annotationType)) 187 return annotationType.cast(each); 188 return null; 189 } 190 191 /** 192 * @return all of the annotations attached to this description node 193 */ getAnnotations()194 public Collection<Annotation> getAnnotations() { 195 return Arrays.asList(fAnnotations); 196 } 197 198 /** 199 * @return If this describes a method invocation, 200 * the class of the test instance. 201 */ getTestClass()202 public Class<?> getTestClass() { 203 String name= getClassName(); 204 if (name == null) 205 return null; 206 try { 207 return Class.forName(name); 208 } catch (ClassNotFoundException e) { 209 return null; 210 } 211 } 212 213 /** 214 * @return If this describes a method invocation, 215 * the name of the class of the test instance 216 */ getClassName()217 public String getClassName() { 218 Matcher matcher= methodStringMatcher(); 219 return matcher.matches() 220 ? matcher.group(2) 221 : toString(); 222 } 223 224 /** 225 * @return If this describes a method invocation, 226 * the name of the method (or null if not) 227 */ getMethodName()228 public String getMethodName() { 229 return parseMethod(); 230 } 231 parseMethod()232 private String parseMethod() { 233 Matcher matcher= methodStringMatcher(); 234 if (matcher.matches()) 235 return matcher.group(1); 236 return null; 237 } 238 methodStringMatcher()239 private Matcher methodStringMatcher() { 240 return Pattern.compile("(.*)\\((.*)\\)").matcher(toString()); 241 } 242 }