• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }