diff options
Diffstat (limited to 'src/main/java/org/junit/runner/Description.java')
-rw-r--r-- | src/main/java/org/junit/runner/Description.java | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/src/main/java/org/junit/runner/Description.java b/src/main/java/org/junit/runner/Description.java new file mode 100644 index 0000000..b3083d5 --- /dev/null +++ b/src/main/java/org/junit/runner/Description.java @@ -0,0 +1,242 @@ +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; + +/** + * <p>A <code>Description</code> describes a test which is to be run or has been run. <code>Descriptions</code> + * can be atomic (a single test) or compound (containing children tests). <code>Descriptions</code> 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).</p> + * + * <p><code>Descriptions</code> are implemented as a single class rather than a Composite because + * they are entirely informational. They contain no logic aside from counting their tests.</p> + * + * <p>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.</p> + * + * @see org.junit.runner.Request + * @see org.junit.runner.Runner + */ +public class Description implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * Create a <code>Description</code> named <code>name</code>. + * Generally, you will add children to this <code>Description</code>. + * @param name the name of the <code>Description</code> + * @param annotations + * @return a <code>Description</code> named <code>name</code> + */ + 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 <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>. + * Generally, this will be a leaf <code>Description</code>. + * @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 <code>Description</code> named <code>name</code> + */ + public static Description createTestDescription(Class<?> clazz, String name, Annotation... annotations) { + return new Description(String.format("%s(%s)", name, clazz.getName()), annotations); + } + + /** + * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>. + * Generally, this will be a leaf <code>Description</code>. + * (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 <code>Description</code> named <code>name</code> + */ + public static Description createTestDescription(Class<?> clazz, String name) { + return createTestDescription(clazz, name, new Annotation[0]); + } + + /** + * Create a <code>Description</code> named after <code>testClass</code> + * @param testClass A {@link Class} containing tests + * @return a <code>Description</code> of <code>testClass</code> + */ + 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<Description> fChildren= new ArrayList<Description>(); + 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 <code>Description</code> 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<Description> getChildren() { + return fChildren; + } + + /** + * @return <code>true</code> if the receiver is a suite + */ + public boolean isSuite() { + return !isTest(); + } + + /** + * @return <code>true</code> 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 extends Annotation> T getAnnotation(Class<T> 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<Annotation> 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()); + } +}
\ No newline at end of file |