Home » commons-collections-3.2.1-src » org.apache.commons » collections » [javadoc | source]
    1   /*
    2    * Copyright 1999-2004 The Apache Software Foundation
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   package org.apache.commons.collections;
   17   
   18   import junit.framework.TestCase;
   19   import junit.framework.TestSuite;
   20   import java.lang.reflect.Constructor;
   21   import java.lang.reflect.InvocationTargetException;
   22   import java.lang.reflect.Method;
   23   import java.lang.reflect.Modifier;
   24   import java.util.Arrays;
   25   import java.util.ArrayList;
   26   import java.util.List;
   27   
   28   
   29   /**
   30    *  A {@link TestCase} that can define both simple and bulk test methods.<P>
   31    *
   32    *  A <I>simple test method</I> is the type of test traditionally 
   33    *  supplied by by {@link TestCase}.  To define a simple test, create a public 
   34    *  no-argument method whose name starts with "test".  You can specify the
   35    *  the name of simple test in the constructor of <Code>BulkTest</Code>;
   36    *  a subsequent call to {@link TestCase#run} will run that simple test.<P>
   37    *
   38    *  A <I>bulk test method</I>, on the other hand, returns a new instance
   39    *  of <Code>BulkTest</Code>, which can itself define new simple and bulk
   40    *  test methods.  By using the {@link #makeSuite} method, you can 
   41    *  automatically create a hierarchal suite of tests and child bulk tests.<P>
   42    *
   43    *  For instance, consider the following two classes:
   44    *
   45    *  <Pre>
   46    *  public class TestSet extends BulkTest {
   47    *
   48    *      private Set set;
   49    *
   50    *      public TestSet(Set set) {
   51    *          this.set = set;
   52    *      }
   53    *
   54    *      public void testContains() {
   55    *          boolean r = set.contains(set.iterator().next()));
   56    *          assertTrue("Set should contain first element, r);
   57    *      }
   58    *
   59    *      public void testClear() {
   60    *          set.clear();
   61    *          assertTrue("Set should be empty after clear", set.isEmpty());
   62    *      }
   63    *  }
   64    *
   65    *
   66    *  public class TestHashMap extends BulkTest {
   67    *
   68    *      private Map makeFullMap() {
   69    *          HashMap result = new HashMap();
   70    *          result.put("1", "One");
   71    *          result.put("2", "Two");
   72    *          return result;
   73    *      }
   74    *
   75    *      public void testClear() {
   76    *          Map map = makeFullMap();
   77    *          map.clear();
   78    *          assertTrue("Map empty after clear", map.isEmpty());
   79    *      }
   80    *
   81    *      public BulkTest bulkTestKeySet() {
   82    *          return new TestSet(makeFullMap().keySet());
   83    *      }
   84    *
   85    *      public BulkTest bulkTestEntrySet() {
   86    *          return new TestSet(makeFullMap().entrySet());
   87    *      }
   88    *  }
   89    *  </Pre>
   90    *
   91    *  In the above examples, <Code>TestSet</Code> defines two
   92    *  simple test methods and no bulk test methods; <Code>TestHashMap</Code>
   93    *  defines one simple test method and two bulk test methods.  When
   94    *  <Code>makeSuite(TestHashMap.class).run</Code> is executed, 
   95    *  <I>five</I> simple test methods will be run, in this order:<P>
   96    *
   97    *  <Ol>
   98    *  <Li>TestHashMap.testClear()
   99    *  <Li>TestHashMap.bulkTestKeySet().testContains();
  100    *  <Li>TestHashMap.bulkTestKeySet().testClear();
  101    *  <Li>TestHashMap.bulkTestEntrySet().testContains();
  102    *  <Li>TestHashMap.bulkTestEntrySet().testClear();
  103    *  </Ol>
  104    *
  105    *  In the graphical junit test runners, the tests would be displayed in
  106    *  the following tree:<P>
  107    *
  108    *  <UL>
  109    *  <LI>TestHashMap</LI>
  110    *      <UL>
  111    *      <LI>testClear
  112    *      <LI>bulkTestKeySet
  113    *          <UL>
  114    *          <LI>testContains
  115    *          <LI>testClear
  116    *          </UL>
  117    *      <LI>bulkTestEntrySet
  118    *          <UL>
  119    *          <LI>testContains
  120    *          <LI>testClear
  121    *          </UL>
  122    *      </UL>
  123    *  </UL>
  124    *
  125    *  A subclass can override a superclass's bulk test by
  126    *  returning <Code>null</Code> from the bulk test method.  If you only
  127    *  want to override specific simple tests within a bulk test, use the
  128    *  {@link #ignoredSimpleTests} method.<P>
  129    *
  130    *  Note that if you want to use the bulk test methods, you <I>must</I>
  131    *  define your <Code>suite()</Code> method to use {@link #makeSuite}.
  132    *  The ordinary {@link TestSuite} constructor doesn't know how to 
  133    *  interpret bulk test methods.
  134    *
  135    *  @author Paul Jack
  136    *  @version $Id: BulkTest.java,v 1.2.2.1 2004/05/22 12:14:05 scolebourne Exp $
  137    */
  138   public class BulkTest extends TestCase implements Cloneable {
  139   
  140   
  141       // Note:  BulkTest is Cloneable to make it easier to construct 
  142       // BulkTest instances for simple test methods that are defined in 
  143       // anonymous inner classes.  Basically we don't have to worry about
  144       // finding wierd constructors.  (And even if we found them, techinically
  145       // it'd be illegal for anyone but the outer class to invoke them).  
  146       // Given one BulkTest instance, we can just clone it and reset the 
  147       // method name for every simple test it defines.  
  148   
  149   
  150       /**
  151        *  The full name of this bulk test instance.  This is the full name
  152        *  that is compared to {@link #ignoredSimpleTests} to see if this
  153        *  test should be ignored.  It's also displayed in the text runner
  154        *  to ease debugging.
  155        */
  156       String verboseName;
  157   
  158   
  159       /**
  160        *  Constructs a new <Code>BulkTest</Code> instance that will run the
  161        *  specified simple test.
  162        *
  163        *  @param name  the name of the simple test method to run
  164        */
  165       public BulkTest(String name) {
  166           super(name);
  167           this.verboseName = getClass().getName();
  168       }
  169   
  170   
  171       /**
  172        *  Creates a clone of this <Code>BulkTest</Code>.<P>
  173        *
  174        *  @return  a clone of this <Code>BulkTest</Code>
  175        */
  176       public Object clone() {
  177           try {
  178               return super.clone();
  179           } catch (CloneNotSupportedException e) {
  180               throw new Error(); // should never happen
  181           }
  182       }
  183   
  184   
  185       /**
  186        *  Returns an array of simple test names to ignore.<P>
  187        *
  188        *  If a simple test that's defined by this <Code>BulkTest</Code> or
  189        *  by one of its bulk test methods has a name that's in the returned
  190        *  array, then that simple test will not be executed.<P>
  191        *
  192        *  A simple test's name is formed by taking the class name of the
  193        *  root <Code>BulkTest</Code>, eliminating the package name, then
  194        *  appending the names of any bulk test methods that were invoked
  195        *  to get to the simple test, and then appending the simple test
  196        *  method name.  The method names are delimited by periods:
  197        *
  198        *  <Pre>
  199        *  TestHashMap.bulkTestEntrySet.testClear
  200        *  </Pre>
  201        *
  202        *  is the name of one of the simple tests defined in the sample classes
  203        *  described above.  If the sample <Code>TestHashMap</Code> class
  204        *  included this method:
  205        *
  206        *  <Pre>
  207        *  public String[] ignoredSimpleTests() {
  208        *      return new String[] { "TestHashMap.bulkTestEntrySet.testClear" };
  209        *  }
  210        *  </Pre>
  211        *
  212        *  then the entry set's clear method wouldn't be tested, but the key
  213        *  set's clear method would.
  214        *
  215        *  @return an array of the names of simple tests to ignore, or null if
  216        *   no tests should be ignored
  217        */
  218       public String[] ignoredSimpleTests() {
  219           return null;
  220       }
  221   
  222   
  223       /**
  224        *  Returns the display name of this <Code>BulkTest</Code>.
  225        *
  226        *  @return the display name of this <Code>BulkTest</Code>
  227        */
  228       public String toString() {
  229           return getName() + "(" + verboseName + ") ";
  230       }
  231   
  232   
  233       /**
  234        *  Returns a {@link TestSuite} for testing all of the simple tests
  235        *  <I>and</I> all the bulk tests defined by the given class.<P>
  236        *
  237        *  The class is examined for simple and bulk test methods; any child
  238        *  bulk tests are also examined recursively; and the results are stored
  239        *  in a hierarchal {@link TestSuite}.<P>
  240        *
  241        *  The given class must be a subclass of <Code>BulkTest</Code> and must
  242        *  not be abstract.<P>
  243        *
  244        *  @param c  the class to examine for simple and bulk tests
  245        *  @return  a {@link TestSuite} containing all the simple and bulk tests
  246        *    defined by that class
  247        */
  248       public static TestSuite makeSuite(Class c) {
  249           if (Modifier.isAbstract(c.getModifiers())) {
  250               throw new IllegalArgumentException("Class must not be abstract.");
  251           }
  252           if (!BulkTest.class.isAssignableFrom(c)) {
  253               throw new IllegalArgumentException("Class must extend BulkTest.");
  254           }
  255           return new BulkTestSuiteMaker(c).make();
  256       }
  257   
  258   }
  259   
  260   
  261   // It was easier to use a separate class to do all the reflection stuff
  262   // for making the TestSuite instances.  Having permanent state around makes
  263   // it easier to handle the recursion.
  264   class BulkTestSuiteMaker {
  265   
  266   
  267       /** The class that defines simple and bulk tests methods. */
  268       private Class startingClass;
  269   
  270   
  271       /** List of ignored simple test names. */
  272       private List ignored;
  273   
  274      
  275       /** The TestSuite we're currently populating.  Can change over time. */
  276       private TestSuite result;
  277   
  278   
  279       /** 
  280        *  The prefix for simple test methods.  Used to check if a test is in 
  281        *  the ignored list.
  282        */ 
  283       private String prefix;
  284   
  285   
  286       /** 
  287        *  Constructor.
  288        *
  289        *  @param startingClass  the starting class
  290        */     
  291       public BulkTestSuiteMaker(Class startingClass) {
  292           this.startingClass = startingClass;
  293       }
  294   
  295   
  296       /**
  297        *  Makes a hierarchal TestSuite based on the starting class.
  298        *
  299        *  @return  the hierarchal TestSuite for startingClass
  300        */
  301       public TestSuite make() {
  302            this.result = new TestSuite();
  303            this.prefix = getBaseName(startingClass);
  304            result.setName(prefix);
  305   
  306            BulkTest bulk = makeFirstTestCase(startingClass);
  307            ignored = new ArrayList();
  308            String[] s = bulk.ignoredSimpleTests();
  309            if (s != null) {
  310                ignored.addAll(Arrays.asList(s));
  311            }
  312            make(bulk);
  313            return result;
  314       }
  315   
  316   
  317       /**
  318        *  Appends all the simple tests and bulk tests defined by the given
  319        *  instance's class to the current TestSuite.
  320        *
  321        *  @param bulk  An instance of the class that defines simple and bulk
  322        *    tests for us to append
  323        */
  324       void make(BulkTest bulk) {
  325           Class c = bulk.getClass();
  326           Method[] all = c.getMethods();
  327           for (int i = 0; i < all.length; i++) {
  328               if (isTest(all[i])) addTest(bulk, all[i]);
  329               if (isBulk(all[i])) addBulk(bulk, all[i]);
  330           }
  331       }
  332   
  333   
  334       /**
  335        *  Adds the simple test defined by the given method to the TestSuite.
  336        *
  337        *  @param bulk  The instance of the class that defined the method
  338        *   (I know it's wierd.  But the point is, we can clone the instance
  339        *   and not have to worry about constructors.)
  340        *  @param m  The simple test method
  341        */
  342       void addTest(BulkTest bulk, Method m) {
  343           BulkTest bulk2 = (BulkTest)bulk.clone();
  344           bulk2.setName(m.getName());
  345           bulk2.verboseName = prefix + "." + m.getName();
  346           if (ignored.contains(bulk2.verboseName)) return;
  347           result.addTest(bulk2);
  348       }
  349   
  350   
  351       /**
  352        *  Adds a whole new suite of tests that are defined by the result of
  353        *  the given bulk test method.  In other words, the given bulk test
  354        *  method is invoked, and the resulting BulkTest instance is examined
  355        *  for yet more simple and bulk tests.
  356        *
  357        *  @param bulk  The instance of the class that defined the method
  358        *  @param m  The bulk test method
  359        */
  360       void addBulk(BulkTest bulk, Method m) {
  361           BulkTest bulk2;
  362           try {
  363               bulk2 = (BulkTest)m.invoke(bulk, null);
  364               if (bulk2 == null) return;
  365           } catch (InvocationTargetException e) {
  366               throw new Error(); // FIXME;
  367           } catch (IllegalAccessException e) {
  368               throw new Error(); // FIXME;
  369           }
  370   
  371           // Save current state on the stack.
  372           String oldPrefix = prefix;
  373           TestSuite oldResult = result;
  374   
  375           prefix = prefix + "." + m.getName();
  376           result = new TestSuite();
  377           result.setName(m.getName());
  378   
  379           make(bulk2);
  380   
  381           oldResult.addTest(result);
  382   
  383           // Restore the old state
  384           prefix = oldPrefix;
  385           result = oldResult;
  386       }
  387   
  388   
  389       /**
  390        *  Returns the base name of the given class.
  391        *
  392        *  @param c  the class
  393        *  @return the name of that class, minus any package names
  394        */
  395       private static String getBaseName(Class c) {
  396           String name = c.getName();
  397           int p = name.lastIndexOf('.');
  398           if (p > 0) {
  399               name = name.substring(p + 1);
  400           }
  401           return name;
  402       }
  403   
  404   
  405       // These three methods are used to create a valid BulkTest instance
  406       // from a class.
  407   
  408       private static Constructor getTestCaseConstructor(Class c) {
  409           try {
  410               return c.getConstructor(new Class[] { String.class });
  411           } catch (NoSuchMethodException e) {
  412               throw new IllegalArgumentException(c + " must provide " +
  413                "a (String) constructor");
  414           }
  415       }
  416   
  417   
  418       private static BulkTest makeTestCase(Class c, Method m) {
  419           Constructor con = getTestCaseConstructor(c);
  420           try {
  421               return (BulkTest)con.newInstance(new String[] { m.getName() });
  422           } catch (InvocationTargetException e) {
  423               e.printStackTrace();
  424               throw new RuntimeException(); // FIXME;
  425           } catch (IllegalAccessException e) {
  426               throw new Error(); // should never occur
  427           } catch (InstantiationException e) {
  428               throw new RuntimeException(); // FIXME;
  429           }
  430       }
  431   
  432   
  433       private static BulkTest makeFirstTestCase(Class c) {
  434           Method[] all = c.getMethods();
  435           for (int i = 0; i < all.length; i++) {
  436               if (isTest(all[i])) return makeTestCase(c, all[i]);
  437           }
  438           throw new IllegalArgumentException(c.getName() + " must provide " 
  439             + " at least one test method.");
  440       }
  441   
  442   
  443       /**
  444        *  Returns true if the given method is a simple test method.
  445        */
  446       private static boolean isTest(Method m) {
  447           if (!m.getName().startsWith("test")) return false;
  448           if (m.getReturnType() != Void.TYPE) return false;
  449           if (m.getParameterTypes().length != 0) return false;
  450           int mods = m.getModifiers();
  451           if (Modifier.isStatic(mods)) return false;
  452           if (Modifier.isAbstract(mods)) return false;
  453           return true;
  454       }
  455   
  456   
  457       /**
  458        *  Returns true if the given method is a bulk test method.
  459        */
  460       private static boolean isBulk(Method m) {
  461           if (!m.getName().startsWith("bulkTest")) return false;
  462           if (m.getReturnType() != BulkTest.class) return false;
  463           if (m.getParameterTypes().length != 0) return false;
  464           int mods = m.getModifiers();
  465           if (Modifier.isStatic(mods)) return false;
  466           if (Modifier.isAbstract(mods)) return false;
  467           return true;
  468       }
  469   
  470   
  471   }

Save This Page
Home » commons-collections-3.2.1-src » org.apache.commons » collections » [javadoc | source]