package org.junit.runner; import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.regex.Matcher; import java.util.regex.Pattern; /** *

A Description describes a test which is to be run or has been run. Descriptions * can be atomic (a single test) or compound (containing children tests). Descriptions are used * to provide feedback about the tests that are about to run (for example, the tree view * visible in many IDEs) or tests that have been run (for example, the failures view).

* *

Descriptions are implemented as a single class rather than a Composite because * they are entirely informational. They contain no logic aside from counting their tests.

* *

In the past, we used the raw {@link junit.framework.TestCase}s and {@link junit.framework.TestSuite}s * to display the tree of tests. This was no longer viable in JUnit 4 because atomic tests no longer have * a superclass below {@link Object}. We needed a way to pass a class and name together. Description * emerged from this.

* * @see org.junit.runner.Request * @see org.junit.runner.Runner */ public class Description implements Serializable { private static final long serialVersionUID = 1L; /** * Create a Description named name. * Generally, you will add children to this Description. * @param name the name of the Description * @param annotations * @return a Description named name */ public static Description createSuiteDescription(String name, Annotation... annotations) { if (name.length() == 0) throw new IllegalArgumentException("name must have non-zero length"); return new Description(name, annotations); } /** * Create a Description of a single test named name in the class clazz. * Generally, this will be a leaf Description. * @param clazz the class of the test * @param name the name of the test (a method name for test annotated with {@link org.junit.Test}) * @param annotations meta-data about the test, for downstream interpreters * @return a Description named name */ public static Description createTestDescription(Class clazz, String name, Annotation... annotations) { return new Description(String.format("%s(%s)", name, clazz.getName()), annotations); } /** * Create a Description of a single test named name in the class clazz. * Generally, this will be a leaf Description. * (This remains for binary compatibility with clients of JUnit 4.3) * @param clazz the class of the test * @param name the name of the test (a method name for test annotated with {@link org.junit.Test}) * @return a Description named name */ public static Description createTestDescription(Class clazz, String name) { return createTestDescription(clazz, name, new Annotation[0]); } /** * Create a Description named after testClass * @param testClass A {@link Class} containing tests * @return a Description of testClass */ public static Description createSuiteDescription(Class testClass) { return new Description(testClass.getName(), testClass.getAnnotations()); } /** * Describes a Runner which runs no tests */ public static final Description EMPTY= new Description("No Tests"); /** * Describes a step in the test-running mechanism that goes so wrong no * other description can be used (for example, an exception thrown from a Runner's * constructor */ public static final Description TEST_MECHANISM= new Description("Test mechanism"); private final ArrayList fChildren= new ArrayList(); private final String fDisplayName; private final Annotation[] fAnnotations; private Description(final String displayName, Annotation... annotations) { fDisplayName= displayName; fAnnotations= annotations; } /** * @return a user-understandable label */ public String getDisplayName() { return fDisplayName; } /** * Add Description as a child of the receiver. * @param description the soon-to-be child. */ public void addChild(Description description) { getChildren().add(description); } /** * @return the receiver's children, if any */ public ArrayList getChildren() { return fChildren; } /** * @return true if the receiver is a suite */ public boolean isSuite() { return !isTest(); } /** * @return true if the receiver is an atomic test */ public boolean isTest() { return getChildren().isEmpty(); } /** * @return the total number of atomic tests in the receiver */ public int testCount() { if (isTest()) return 1; int result= 0; for (Description child : getChildren()) result+= child.testCount(); return result; } @Override public int hashCode() { return getDisplayName().hashCode(); } @Override public boolean equals(Object obj) { if (!(obj instanceof Description)) return false; Description d = (Description) obj; return getDisplayName().equals(d.getDisplayName()); } @Override public String toString() { return getDisplayName(); } /** * @return true if this is a description of a Runner that runs no tests */ public boolean isEmpty() { return equals(EMPTY); } /** * @return a copy of this description, with no children (on the assumption that some of the * children will be added back) */ public Description childlessCopy() { return new Description(fDisplayName, fAnnotations); } /** * @return the annotation of type annotationType that is attached to this description node, * or null if none exists */ public T getAnnotation(Class annotationType) { for (Annotation each : fAnnotations) if (each.annotationType().equals(annotationType)) return annotationType.cast(each); return null; } /** * @return all of the annotations attached to this description node */ public Collection getAnnotations() { return Arrays.asList(fAnnotations); } /** * @return If this describes a method invocation, * the class of the test instance. */ public Class getTestClass() { String name= getClassName(); if (name == null) return null; try { return Class.forName(name); } catch (ClassNotFoundException e) { return null; } } /** * @return If this describes a method invocation, * the name of the class of the test instance */ public String getClassName() { Matcher matcher= methodStringMatcher(); return matcher.matches() ? matcher.group(2) : toString(); } /** * @return If this describes a method invocation, * the name of the method (or null if not) */ public String getMethodName() { return parseMethod(); } private String parseMethod() { Matcher matcher= methodStringMatcher(); if (matcher.matches()) return matcher.group(1); return null; } private Matcher methodStringMatcher() { return Pattern.compile("(.*)\\((.*)\\)").matcher(toString()); } }