View Javadoc

1   /*
2    * Copyright (c) 1998, 2005 Gargoyle Software Inc. All rights reserved.
3    *
4    * Redistribution and use in source and binary forms, with or without
5    * modification, are permitted provided that the following conditions are met:
6    *
7    * 1. Redistributions of source code must retain the above copyright notice,
8    *    this list of conditions and the following disclaimer.
9    * 2. Redistributions in binary form must reproduce the above copyright notice,
10   *    this list of conditions and the following disclaimer in the documentation
11   *    and/or other materials provided with the distribution.
12   * 3. The end-user documentation included with the redistribution, if any, must
13   *    include the following acknowledgment:
14   *
15   *       "This product includes software developed by Gargoyle Software Inc.
16   *        (http://www.GargoyleSoftware.com/)."
17   *
18   *    Alternately, this acknowledgment may appear in the software itself, if
19   *    and wherever such third-party acknowledgments normally appear.
20   * 4. The name "Gargoyle Software" must not be used to endorse or promote
21   *    products derived from this software without prior written permission.
22   *    For written permission, please contact info@GargoyleSoftware.com.
23   * 5. Products derived from this software may not be called "GSBase", nor may
24   *    "GSBase" appear in their name, without prior written permission of
25   *    Gargoyle Software Inc.
26   *
27   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
28   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
29   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GARGOYLE
30   * SOFTWARE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
31   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
33   * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
34   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
35   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
36   * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37   */
38  package com.gargoylesoftware.base.testing;
39  
40  import com.gargoylesoftware.base.collections.StringComparator;
41  import com.gargoylesoftware.base.trace.Trace;
42  import com.gargoylesoftware.base.util.DetailedIllegalArgumentException;
43  import java.io.File;
44  import java.io.IOException;
45  import java.lang.reflect.Method;
46  import java.util.ArrayList;
47  import java.util.Iterator;
48  import java.util.List;
49  import java.util.Locale;
50  import java.util.Set;
51  import java.util.SortedSet;
52  import java.util.TreeSet;
53  import junit.framework.Test;
54  import junit.framework.TestCase;
55  import junit.framework.TestSuite;
56  import junit.textui.TestRunner;
57  
58  /***
59   *  A launching point for executing test cases. This recusively walks through
60   *  the directory structure looking for classes that end in Test.class. It then
61   *  loads these classes to see if they are instances of TestCase. If so, they
62   *  are added to the test suite. Once all directories have been scanned, the
63   *  test cases are executed.
64   *
65   * @version  $Revision: 1.4 $
66   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
67   */
68  public class RecursiveTestSuite extends TestSuite {
69  
70      private final TestFilter testFilter_;
71  
72  
73      /***
74       *  Create a new instance
75       *
76       * @param  testFilter The object that will filter out tests that we don't
77       *      wish to run
78       * @param  startingPath The directory that we will start our descent in.
79       * @exception  IOException If anything goes wrong during the directory
80       *      scanning.
81       */
82      public RecursiveTestSuite(
83              final String startingPath,
84              final TestFilter testFilter )
85          throws
86              IOException {
87  
88          this( new File( startingPath ), testFilter );
89      }
90  
91  
92      /***
93       *  Create an instance
94       *
95       * @param  testFilter The object that will filter out tests that we don't
96       *      wish to run
97       * @param  startingPath The directory that we will start our descent in.
98       * @exception  IOException If anything goes wrong during the directory
99       *      scanning.
100      */
101     public RecursiveTestSuite(
102             final File startingPath,
103             final TestFilter testFilter )
104         throws
105             IOException {
106 
107         super( "RecursiveTestSuite(" + startingPath + ")" );
108 
109         if( testFilter == null ) {
110             throw new NullPointerException( "testFilter" );
111         }
112         if( startingPath == null ) {
113             throw new NullPointerException( "startingPath" );
114         }
115         testFilter_ = testFilter;
116 
117         if( startingPath.exists() == false ) {
118             throw new DetailedIllegalArgumentException( "startingPath", startingPath, "Doesn't exist");
119         }
120         if( startingPath.isDirectory() == false ) {
121             throw new DetailedIllegalArgumentException( "startingPath", startingPath, "Isn't a directory" );
122         }
123 
124         final long startTime = System.currentTimeMillis();
125         final Set classNames = findAllTestClasses( startingPath );
126         final long endTime = System.currentTimeMillis();
127         System.out.println( "Scanning for classes: Time=["
128                  + ( ( endTime - startTime ) / 1000F )
129                  + " seconds] Found=["
130                  + classNames.size()
131                  + " classes]" );
132 
133         final Iterator iterator = classNames.iterator();
134 
135         while( iterator.hasNext() ) {
136             addTest( getTestForClass( (Class)iterator.next() ) );
137         }
138     }
139 
140 
141     /***
142      *  Main entry point.
143      *
144      * @param  args The arguments
145      */
146     public static void main( final String args[] ) {
147 
148         Trace.getController().setOutRedirected( true );
149         Trace.getController().setErrRedirected( true );
150 
151         // Force the load of AWT
152         new javax.swing.JPanel();
153 
154         try {
155             final String startingPath;
156             if( args.length == 0 ) {
157                 startingPath = ".";
158             }
159             else {
160                 startingPath = args[0];
161             }
162             final RecursiveTestSuite suite = new RecursiveTestSuite( startingPath, new AcceptAllTestFilter() );
163 
164             // TestRunner.run() will invoke System.exit() so we have
165             // to disable the buffering now.
166             Trace.getController().setBufferingEnabled( false );
167 
168             if( suite.testCount() == 0 ) {
169                 Trace.println( "Nothing to do." );
170             }
171             else {
172                 TestRunner.run( suite );
173             }
174         }
175         catch( final Throwable t ) {
176             Trace.printStackTrace( Trace.out, t );
177         }
178         Trace.exit( 0 );
179     }
180 
181 
182     /***
183      *  Dummy test so that JUnit doesn't complain that this suite doesn't have
184      *  any tests.
185      */
186     public void testFoo() {
187     }
188 
189 
190 //    protected Collection findPotentialClasses() {
191 //        final DirectoryWalker walker = new DirectoryWalker( "." );
192 //        return Collections.EMPTY_LIST;
193 //    }
194 
195 
196     /***
197      *  Return a test suite containing all the tests for the specified class. If
198      *  the class has a suite() method then it will be used to get the tests,
199      *  otherwise reflection will be used.
200      *
201      * @param  clazz Description of Parameter
202      * @return  The testSuiteForClass value
203      */
204     private Test getTestForClass( final Class clazz ) {
205         try {
206             final Method method = clazz.getDeclaredMethod( "suite", new Class[0] );
207             return (Test)method.invoke( null, new Object[0] );
208         }
209         catch( final NoSuchMethodException e ) {
210             // Fall through
211         }
212         catch( final Exception e ) {
213             e.printStackTrace();
214         }
215 
216         return new TestSuite( clazz );
217     }
218 
219 
220     /***
221      * @param  startingDirectory Description of Parameter
222      * @return  Description of the Returned Value
223      * @exception  IOException Description of Exception
224      */
225     private Set findAllTestClasses( final File startingDirectory )
226         throws IOException {
227 
228         final List list = new ArrayList();
229         final SortedSet set
230                  = new TreeSet( new StringComparator( Locale.getDefault() ) );
231 
232         findAllTestClasses( startingDirectory, list );
233 
234         final String startingDirectoryFullPath
235                  = startingDirectory.getCanonicalPath();
236         final int startingDirLength
237                  = startingDirectoryFullPath.length() + File.separator.length();
238 
239         String className;
240         Class clazz;
241 
242         final Iterator iterator = list.iterator();
243         while( iterator.hasNext() ) {
244             className = ( (String)iterator.next() );
245             className = className.substring( startingDirLength,
246                     className.length() - ".class".length() );
247             className = className.replace( File.separatorChar, '.' );
248 
249             try {
250                 clazz = Class.forName( className );
251                 if( TestCase.class.isAssignableFrom( clazz )
252                          && testFilter_.accept( clazz ) ) {
253                     set.add( clazz );
254                 }
255             }
256             catch( final ClassNotFoundException e ) {
257                 Trace.printStackTrace( Trace.out, e );
258             }
259         }
260         return set;
261     }
262 
263 
264     /***
265      * @param  directory Description of Parameter
266      * @param  list Description of Parameter
267      * @exception  IOException Description of Exception
268      */
269     private void findAllTestClasses( final File directory, final List list )
270         throws IOException {
271 
272         final String files[] = directory.list();
273         if( files == null ) {
274             return;
275         }
276 
277         final String directoryName = directory.getCanonicalPath();
278         String fileName;
279 
280         int i;
281         for( i = 0; i < files.length; i++ ) {
282             fileName = directoryName + File.separator + files[i];
283             if( files[i].endsWith( "Test.class" ) ) {
284                 list.add( fileName );
285             }
286             else {
287                 final File file = new File( fileName );
288                 if( file.isDirectory() ) {
289                     findAllTestClasses( file, list );
290                 }
291             }
292         }
293     }
294 }
295