001    /*
002     $Id: GroovyTestCase.java 4201 2006-11-05 10:23:50Z paulk $
003    
004     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
005    
006     Redistribution and use of this software and associated documentation
007     ("Software"), with or without modification, are permitted provided
008     that the following conditions are met:
009    
010     1. Redistributions of source code must retain copyright
011        statements and notices.  Redistributions must also contain a
012        copy of this document.
013    
014     2. Redistributions in binary form must reproduce the
015        above copyright notice, this list of conditions and the
016        following disclaimer in the documentation and/or other
017        materials provided with the distribution.
018    
019     3. The name "groovy" must not be used to endorse or promote
020        products derived from this Software without prior written
021        permission of The Codehaus.  For written permission,
022        please contact info@codehaus.org.
023    
024     4. Products derived from this Software may not be called "groovy"
025        nor may "groovy" appear in their names without prior written
026        permission of The Codehaus. "groovy" is a registered
027        trademark of The Codehaus.
028    
029     5. Due credit should be given to The Codehaus -
030        http://groovy.codehaus.org/
031    
032     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
033     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
034     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
035     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
036     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
037     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
038     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
039     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
040     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
041     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
042     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
043     OF THE POSSIBILITY OF SUCH DAMAGE.
044    
045     */
046    package groovy.util;
047    
048    import groovy.lang.Closure;
049    import groovy.lang.GroovyRuntimeException;
050    import groovy.lang.GroovyShell;
051    
052    import java.util.logging.Logger;
053    import java.lang.reflect.Method;
054    import java.lang.reflect.Modifier;
055    
056    import junit.framework.TestCase;
057    
058    import org.codehaus.groovy.runtime.InvokerHelper;
059    
060    /**
061     * A default JUnit TestCase in Groovy. This provides a number of helper methods
062     * plus avoids the JUnit restriction of requiring all test* methods to be void
063     * return type.
064     *
065     * @author <a href="mailto:bob@werken.com">bob mcwhirter</a>
066     * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
067     * @author Dierk Koenig (the notYetImplemented feature, changes to shouldFail)
068     * @version $Revision: 4201 $
069     */
070    public class GroovyTestCase extends TestCase {
071    
072        protected static Logger log = Logger.getLogger(GroovyTestCase.class.getName());
073        private static int counter;
074        private boolean useAgileDoxNaming = false;
075    
076        public GroovyTestCase() {
077        }
078    
079        /**
080         * Overload the getName() method to make the test cases look more like AgileDox
081         * (thanks to Joe Walnes for this tip!)
082         */
083        public String getName() {
084            if (useAgileDoxNaming) {
085                return super.getName().substring(4).replaceAll("([A-Z])", " $1").toLowerCase();
086            }
087            else {
088                return super.getName();
089            }
090        }
091    
092        public String getMethodName() {
093            return super.getName();
094        }
095    
096        /**
097         * Asserts that the arrays are equivalent and contain the same values
098         *
099         * @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 which is expected 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    }