View Javadoc

1   /*
2    $Id: GroovyTestCase.java,v 1.27 2006/02/16 18:35:45 dierk Exp $
3   
4    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
5   
6    Redistribution and use of this software and associated documentation
7    ("Software"), with or without modification, are permitted provided
8    that the following conditions are met:
9   
10   1. Redistributions of source code must retain copyright
11      statements and notices.  Redistributions must also contain a
12      copy of this document.
13  
14   2. Redistributions in binary form must reproduce the
15      above copyright notice, this list of conditions and the
16      following disclaimer in the documentation and/or other
17      materials provided with the distribution.
18  
19   3. The name "groovy" must not be used to endorse or promote
20      products derived from this Software without prior written
21      permission of The Codehaus.  For written permission,
22      please contact info@codehaus.org.
23  
24   4. Products derived from this Software may not be called "groovy"
25      nor may "groovy" appear in their names without prior written
26      permission of The Codehaus. "groovy" is a registered
27      trademark of The Codehaus.
28  
29   5. Due credit should be given to The Codehaus -
30      http://groovy.codehaus.org/
31  
32   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
33   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
34   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
35   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
36   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
37   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
38   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
39   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
40   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
43   OF THE POSSIBILITY OF SUCH DAMAGE.
44  
45   */
46  package groovy.util;
47  
48  import groovy.lang.Closure;
49  import groovy.lang.GroovyRuntimeException;
50  import groovy.lang.GroovyShell;
51  
52  import java.util.logging.Logger;
53  import java.lang.reflect.Method;
54  import java.lang.reflect.Modifier;
55  
56  import junit.framework.TestCase;
57  
58  import org.codehaus.groovy.runtime.InvokerHelper;
59  
60  /***
61   * A default JUnit TestCase in Groovy. This provides a number of helper methods
62   * plus avoids the JUnit restriction of requiring all test* methods to be void
63   * return type.
64   *
65   * @author <a href="mailto:bob@werken.com">bob mcwhirter</a>
66   * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
67   * @author Dierk Koenig (the notYetImplemented feature, changes to shouldFail)
68   * @version $Revision: 1.27 $
69   */
70  public class GroovyTestCase extends TestCase {
71  
72      protected static Logger log = Logger.getLogger(GroovyTestCase.class.getName());
73      private static int counter;
74      private boolean useAgileDoxNaming = false;
75  
76      public GroovyTestCase() {
77      }
78  
79      /***
80       * Overload the getName() method to make the test cases look more like AgileDox
81       * (thanks to Joe Walnes for this tip!)
82       */
83      public String getName() {
84          if (useAgileDoxNaming) {
85              return super.getName().substring(4).replaceAll("([A-Z])", " $1").toLowerCase();
86          }
87          else {
88              return super.getName();
89          }
90      }
91  
92      public String getMethodName() {
93          return super.getName();
94      }
95  
96      /***
97       * Asserts that the arrays are equivalent and contain the same values
98       *
99       * @param expected
100      * @param value
101      */
102     protected void assertArrayEquals(Object[] expected, Object[] value) {
103         String message =
104             "expected array: " + InvokerHelper.toString(expected) + " value array: " + InvokerHelper.toString(value);
105         assertNotNull(message + ": expected should not be null", expected);
106         assertNotNull(message + ": value should not be null", value);
107         assertEquals(message, expected.length, value.length);
108         for (int i = 0, size = expected.length; i < size; i++) {
109             assertEquals("value[" + i + "] when " + message, expected[i], value[i]);
110         }
111     }
112 
113     /***
114      * Asserts that the array of characters has a given length
115      *
116      * @param length expected length
117      * @param array the array
118      */
119     protected void assertLength(int length, char[] array) {
120         assertEquals(length, array.length);
121     }
122 
123     /***
124      * Asserts that the array of ints has a given length
125      *
126      * @param length expected length
127      * @param array the array
128      */
129     protected void assertLength(int length, int[] array) {
130         assertEquals(length, array.length);
131     }
132 
133     /***
134      * Asserts that the array of objects has a given length
135      *
136      * @param length expected length
137      * @param array the array
138      */
139     protected void assertLength(int length, Object[] array) {
140         assertEquals(length, array.length);
141     }
142 
143     /***
144      * Asserts that the array of characters contains a given char
145      *
146      * @param expected expected character to be found
147      * @param array the array
148      */
149     protected void assertContains(char expected, char[] array) {
150         for (int i = 0; i < array.length; ++i) {
151             if (array[i] == expected) {
152                 return;
153             }
154         }
155 
156         StringBuffer message = new StringBuffer();
157 
158         message.append(expected).append(" not in {");
159 
160         for (int i = 0; i < array.length; ++i) {
161             message.append("'").append(array[i]).append("'");
162 
163             if (i < (array.length - 1)) {
164                 message.append(", ");
165             }
166         }
167 
168         message.append(" }");
169 
170         fail(message.toString());
171     }
172 
173     /***
174      * Asserts that the array of ints contains a given int
175      *
176      * @param expected expected int
177      * @param array the array
178      */
179     protected void assertContains(int expected, int[] array) {
180         for (int i = 0; i < array.length; ++i) {
181             if (array[i] == expected) {
182                 return;
183             }
184         }
185 
186         StringBuffer message = new StringBuffer();
187 
188         message.append(expected).append(" not in {");
189 
190         for (int i = 0; i < array.length; ++i) {
191             message.append("'").append(array[i]).append("'");
192 
193             if (i < (array.length - 1)) {
194                 message.append(", ");
195             }
196         }
197 
198         message.append(" }");
199 
200         fail(message.toString());
201     }
202 
203     /***
204      * Asserts that the value of toString() on the given object matches the
205      * given text string
206      *
207      * @param value the object to be output to the console
208      * @param expected the expected String representation
209      */
210     protected void assertToString(Object value, String expected) {
211         Object console = InvokerHelper.invokeMethod(value, "toString", null);
212         assertEquals("toString() on value: " + value, expected, console);
213     }
214 
215     /***
216      * Asserts that the value of inspect() on the given object matches the
217      * given text string
218      *
219      * @param value the object to be output to the console
220      * @param expected the expected String representation
221      */
222     protected void assertInspect(Object value, String expected) {
223         Object console = InvokerHelper.invokeMethod(value, "inspect", null);
224         assertEquals("inspect() on value: " + value, expected, console);
225     }
226 
227     /***
228      * Asserts that the script runs without any exceptions
229      *
230      * @param script the script that should pass without any exception thrown
231      */
232     protected void assertScript(final String script) throws Exception {
233         GroovyShell shell = new GroovyShell();
234         shell.evaluate(script, getTestClassName());
235     }
236 
237     protected String getTestClassName() {
238         return "TestScript" + getMethodName() + (counter++) + ".groovy";
239     }
240 
241     /***
242      * Asserts that the given code closure fails when it is evaluated
243      *
244      * @param code
245      * @return the message of the thrown Throwable
246      */
247     protected String shouldFail(Closure code) {
248         boolean failed = false;
249         String result = null;
250         try {
251             code.call();
252         }
253         catch (Throwable e) {
254                 failed = true;
255                 result = e.getMessage();
256         }
257         assertTrue("Closure " + code + " should have failed", failed);
258         return result;
259     }
260 
261     /***
262      * Asserts that the given code closure fails when it is evaluated
263      * and that a particular exception is thrown.
264      *
265      * @param clazz the class of the expected exception
266      * @param code the closure that should fail
267      * @return the message of the expected Throwable
268      */
269     protected String shouldFail(Class clazz, Closure code) {
270         Throwable th = null;
271         try {
272             code.call();
273         } catch (GroovyRuntimeException gre) {
274             th = gre;
275             while (th.getCause()!=null && th.getCause()!=gre){ // if wrapped, find the root cause
276                 th=th.getCause();
277                 if (th!=gre && (th instanceof GroovyRuntimeException)) {
278                     gre = (GroovyRuntimeException) th;
279                 }
280             }            
281         } catch (Throwable e) {
282             th = e;
283         }
284 
285         if (th==null) {
286             fail("Closure " + code + " should have failed with an exception of type " + clazz.getName());
287         } else if (! clazz.isInstance(th)) {
288             fail("Closure " + code + " should have failed with an exception of type " + clazz.getName() + ", instead got Exception " + th);
289         }
290         return th.getMessage();
291     }
292 
293     /***
294      *  Returns a copy of a string in which all EOLs are \n.
295      */
296     protected String fixEOLs( String value )
297     {
298         return value.replaceAll( "(//r//n?)|\n", "\n" );
299     }
300 
301     /***
302      * Runs the calling JUnit test again and fails only if it unexpectedly runs.<br/>
303      * This is helpful for tests that don't currently work but should work one day,
304      * when the tested functionality has been implemented.<br/>
305      * The right way to use it is:
306      * <pre>
307      * public void testXXX() {
308      *   if (GroovyTestCase.notYetImplemented(this)) return;
309      *   ... the real (now failing) unit test
310      * }
311      * </pre>
312      * Idea copied from HtmlUnit (many thanks to Marc Guillemot).
313      * Future versions maybe available in the JUnit distro.
314      * The purpose of providing a 'static' version is such that you can use the
315      * feature even if not subclassing GroovyTestCase.
316      * @return <false> when not itself already in the call stack
317      */
318     public static boolean notYetImplemented(TestCase caller) {
319         if (notYetImplementedFlag.get() != null) {
320             return false;
321         }
322         notYetImplementedFlag.set(Boolean.TRUE);
323 
324         final Method testMethod = findRunningJUnitTestMethod(caller.getClass());
325         try {
326             log.info("Running " + testMethod.getName() + " as not yet implemented");
327             testMethod.invoke(caller, new Class[] {});
328             fail(testMethod.getName() + " is marked as not yet implemented but passes unexpectedly");
329         }
330         catch (final Exception e) {
331             log.info(testMethod.getName() + " fails what is normal as it is not yet implemented");
332             // method execution failed, it is really "not yet implemented"
333         }
334         finally {
335             notYetImplementedFlag.set(null);
336         }
337         return true;
338     }
339 
340     /***
341      * Convenience method for subclasses of GroovyTestCase, identical to
342      * <pre> GroovyTestCase.notYetImplemented(this); </pre>.
343      * @see #notYetImplemented(junit.framework.TestCase)
344      * @return  <false> when not itself already in the call stack
345      */
346     public boolean notYetImplemented() {
347         return notYetImplemented(this);
348     }
349 
350     /***
351      * From JUnit. Finds from the call stack the active running JUnit test case
352      * @return the test case method
353      * @throws RuntimeException if no method could be found.
354      */
355     private static Method findRunningJUnitTestMethod(Class caller) {
356         final Class[] args = new Class[] {};
357 
358         // search the inial junit test
359         final Throwable t = new Exception();
360         for (int i=t.getStackTrace().length-1; i>=0; --i) {
361             final StackTraceElement element = t.getStackTrace()[i];
362             if (element.getClassName().equals(caller.getName())) {
363                 try {
364                     final Method m = caller.getMethod(element.getMethodName(), args);
365                     if (isPublicTestMethod(m)) {
366                         return m;
367                     }
368                 }
369                 catch (final Exception e) {
370                     // can't access, ignore it
371                 }
372             }
373         }
374         throw new RuntimeException("No JUnit test case method found in call stack");
375     }
376 
377 
378     /***
379      * From Junit. Test if the method is a junit test.
380      * @param method the method
381      * @return <code>true</code> if this is a junit test.
382      */
383     private static boolean isPublicTestMethod(final Method method) {
384         final String name = method.getName();
385         final Class[] parameters = method.getParameterTypes();
386         final Class returnType = method.getReturnType();
387 
388         return parameters.length == 0 && name.startsWith("test")
389             && returnType.equals(Void.TYPE)
390             && Modifier.isPublic(method.getModifiers());
391     }
392 
393     private static final ThreadLocal notYetImplementedFlag = new ThreadLocal();
394 }