Home | History | Annotate | Download | only in runner
      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.concurrent.ConcurrentLinkedQueue;
      9 import java.util.regex.Matcher;
     10 import java.util.regex.Pattern;
     11 
     12 /**
     13  * A <code>Description</code> describes a test which is to be run or has been run. <code>Descriptions</code>
     14  * can be atomic (a single test) or compound (containing children tests). <code>Descriptions</code> are used
     15  * to provide feedback about the tests that are about to run (for example, the tree view
     16  * visible in many IDEs) or tests that have been run (for example, the failures view).
     17  * <p>
     18  * <code>Descriptions</code> are implemented as a single class rather than a Composite because
     19  * they are entirely informational. They contain no logic aside from counting their tests.
     20  * <p>
     21  * In the past, we used the raw {@link junit.framework.TestCase}s and {@link junit.framework.TestSuite}s
     22  * to display the tree of tests. This was no longer viable in JUnit 4 because atomic tests no longer have
     23  * a superclass below {@link Object}. We needed a way to pass a class and name together. Description
     24  * emerged from this.
     25  *
     26  * @see org.junit.runner.Request
     27  * @see org.junit.runner.Runner
     28  * @since 4.0
     29  */
     30 public class Description implements Serializable {
     31     private static final long serialVersionUID = 1L;
     32 
     33     private static final Pattern METHOD_AND_CLASS_NAME_PATTERN = Pattern
     34             .compile("([\\s\\S]*)\\((.*)\\)");
     35 
     36     /**
     37      * Create a <code>Description</code> named <code>name</code>.
     38      * Generally, you will add children to this <code>Description</code>.
     39      *
     40      * @param name the name of the <code>Description</code>
     41      * @param annotations meta-data about the test, for downstream interpreters
     42      * @return a <code>Description</code> named <code>name</code>
     43      */
     44     public static Description createSuiteDescription(String name, Annotation... annotations) {
     45         return new Description(null, name, annotations);
     46     }
     47 
     48     /**
     49      * Create a <code>Description</code> named <code>name</code>.
     50      * Generally, you will add children to this <code>Description</code>.
     51      *
     52      * @param name the name of the <code>Description</code>
     53      * @param uniqueId an arbitrary object used to define uniqueness (in {@link #equals(Object)}
     54      * @param annotations meta-data about the test, for downstream interpreters
     55      * @return a <code>Description</code> named <code>name</code>
     56      */
     57     public static Description createSuiteDescription(String name, Serializable uniqueId, Annotation... annotations) {
     58         return new Description(null, name, uniqueId, annotations);
     59     }
     60 
     61     /**
     62      * Create a <code>Description</code> of a single test named <code>name</code> in the 'class' named
     63      * <code>className</code>. Generally, this will be a leaf <code>Description</code>. This method is a better choice
     64      * than {@link #createTestDescription(Class, String, Annotation...)} for test runners whose test cases are not
     65      * defined in an actual Java <code>Class</code>.
     66      *
     67      * @param className the class name of the test
     68      * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
     69      * @param annotations meta-data about the test, for downstream interpreters
     70      * @return a <code>Description</code> named <code>name</code>
     71      */
     72     public static Description createTestDescription(String className, String name, Annotation... annotations) {
     73         return new Description(null, formatDisplayName(name, className), annotations);
     74     }
     75 
     76     /**
     77      * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>.
     78      * Generally, this will be a leaf <code>Description</code>.
     79      *
     80      * @param clazz the class of the test
     81      * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
     82      * @param annotations meta-data about the test, for downstream interpreters
     83      * @return a <code>Description</code> named <code>name</code>
     84      */
     85     public static Description createTestDescription(Class<?> clazz, String name, Annotation... annotations) {
     86         return new Description(clazz, formatDisplayName(name, clazz.getName()), annotations);
     87     }
     88 
     89     /**
     90      * Create a <code>Description</code> of a single test named <code>name</code> in the class <code>clazz</code>.
     91      * Generally, this will be a leaf <code>Description</code>.
     92      * (This remains for binary compatibility with clients of JUnit 4.3)
     93      *
     94      * @param clazz the class of the test
     95      * @param name the name of the test (a method name for test annotated with {@link org.junit.Test})
     96      * @return a <code>Description</code> named <code>name</code>
     97      */
     98     public static Description createTestDescription(Class<?> clazz, String name) {
     99         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/junit/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 }