blob: dd94d74fc355e5c2cd448722bd3b76254c82bd53 [file] [log] [blame]
/*
$Id$
Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
Redistribution and use of this software and associated documentation
("Software"), with or without modification, are permitted provided
that the following conditions are met:
1. Redistributions of source code must retain copyright
statements and notices. Redistributions must also contain a
copy of this document.
2. Redistributions in binary form must reproduce the
above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
3. The name "groovy" must not be used to endorse or promote
products derived from this Software without prior written
permission of The Codehaus. For written permission,
please contact info@codehaus.org.
4. Products derived from this Software may not be called "groovy"
nor may "groovy" appear in their names without prior written
permission of The Codehaus. "groovy" is a registered
trademark of The Codehaus.
5. Due credit should be given to The Codehaus -
http://groovy.codehaus.org/
THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package groovy.util;
import groovy.lang.Closure;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovyShell;
import java.util.logging.Logger;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import junit.framework.TestCase;
import org.codehaus.groovy.runtime.InvokerHelper;
/**
* A default JUnit TestCase in Groovy. This provides a number of helper methods
* plus avoids the JUnit restriction of requiring all test* methods to be void
* return type.
*
* @author <a href="mailto:bob@werken.com">bob mcwhirter</a>
* @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
* @author Dierk Koenig (the notYetImplemented feature, changes to shouldFail)
* @version $Revision$
*/
public class GroovyTestCase extends TestCase {
protected static Logger log = Logger.getLogger(GroovyTestCase.class.getName());
private static int counter;
private boolean useAgileDoxNaming = false;
public GroovyTestCase() {
}
/**
* Overload the getName() method to make the test cases look more like AgileDox
* (thanks to Joe Walnes for this tip!)
*/
public String getName() {
if (useAgileDoxNaming) {
return super.getName().substring(4).replaceAll("([A-Z])", " $1").toLowerCase();
}
else {
return super.getName();
}
}
public String getMethodName() {
return super.getName();
}
/**
* Asserts that the arrays are equivalent and contain the same values
*
* @param expected
* @param value
*/
protected void assertArrayEquals(Object[] expected, Object[] value) {
String message =
"expected array: " + InvokerHelper.toString(expected) + " value array: " + InvokerHelper.toString(value);
assertNotNull(message + ": expected should not be null", expected);
assertNotNull(message + ": value should not be null", value);
assertEquals(message, expected.length, value.length);
for (int i = 0, size = expected.length; i < size; i++) {
assertEquals("value[" + i + "] when " + message, expected[i], value[i]);
}
}
/**
* Asserts that the array of characters has a given length
*
* @param length expected length
* @param array the array
*/
protected void assertLength(int length, char[] array) {
assertEquals(length, array.length);
}
/**
* Asserts that the array of ints has a given length
*
* @param length expected length
* @param array the array
*/
protected void assertLength(int length, int[] array) {
assertEquals(length, array.length);
}
/**
* Asserts that the array of objects has a given length
*
* @param length expected length
* @param array the array
*/
protected void assertLength(int length, Object[] array) {
assertEquals(length, array.length);
}
/**
* Asserts that the array of characters contains a given char
*
* @param expected expected character to be found
* @param array the array
*/
protected void assertContains(char expected, char[] array) {
for (int i = 0; i < array.length; ++i) {
if (array[i] == expected) {
return;
}
}
StringBuffer message = new StringBuffer();
message.append(expected).append(" not in {");
for (int i = 0; i < array.length; ++i) {
message.append("'").append(array[i]).append("'");
if (i < (array.length - 1)) {
message.append(", ");
}
}
message.append(" }");
fail(message.toString());
}
/**
* Asserts that the array of ints contains a given int
*
* @param expected expected int
* @param array the array
*/
protected void assertContains(int expected, int[] array) {
for (int i = 0; i < array.length; ++i) {
if (array[i] == expected) {
return;
}
}
StringBuffer message = new StringBuffer();
message.append(expected).append(" not in {");
for (int i = 0; i < array.length; ++i) {
message.append("'").append(array[i]).append("'");
if (i < (array.length - 1)) {
message.append(", ");
}
}
message.append(" }");
fail(message.toString());
}
/**
* Asserts that the value of toString() on the given object matches the
* given text string
*
* @param value the object to be output to the console
* @param expected the expected String representation
*/
protected void assertToString(Object value, String expected) {
Object console = InvokerHelper.invokeMethod(value, "toString", null);
assertEquals("toString() on value: " + value, expected, console);
}
/**
* Asserts that the value of inspect() on the given object matches the
* given text string
*
* @param value the object to be output to the console
* @param expected the expected String representation
*/
protected void assertInspect(Object value, String expected) {
Object console = InvokerHelper.invokeMethod(value, "inspect", null);
assertEquals("inspect() on value: " + value, expected, console);
}
/**
* Asserts that the script runs without any exceptions
*
* @param script the script that should pass without any exception thrown
*/
protected void assertScript(final String script) throws Exception {
GroovyShell shell = new GroovyShell();
shell.evaluate(script, getTestClassName());
}
protected String getTestClassName() {
return "TestScript" + getMethodName() + (counter++) + ".groovy";
}
/**
* Asserts that the given code closure fails when it is evaluated
*
* @param code
* @return the message of the thrown Throwable
*/
protected String shouldFail(Closure code) {
boolean failed = false;
String result = null;
try {
code.call();
}
catch (Throwable e) {
failed = true;
result = e.getMessage();
}
assertTrue("Closure " + code + " should have failed", failed);
return result;
}
/**
* Asserts that the given code closure fails when it is evaluated
* and that a particular exception is thrown.
*
* @param clazz the class of the expected exception
* @param code the closure that should fail
* @return the message of the expected Throwable
*/
protected String shouldFail(Class clazz, Closure code) {
Throwable th = null;
try {
code.call();
} catch (GroovyRuntimeException gre) {
th = gre;
while (th.getCause()!=null && th.getCause()!=gre){ // if wrapped, find the root cause
th=th.getCause();
if (th!=gre && (th instanceof GroovyRuntimeException)) {
gre = (GroovyRuntimeException) th;
}
}
} catch (Throwable e) {
th = e;
}
if (th==null) {
fail("Closure " + code + " should have failed with an exception of type " + clazz.getName());
} else if (! clazz.isInstance(th)) {
fail("Closure " + code + " should have failed with an exception of type " + clazz.getName() + ", instead got Exception " + th);
}
return th.getMessage();
}
/**
* Returns a copy of a string in which all EOLs are \n.
*/
protected String fixEOLs( String value )
{
return value.replaceAll( "(\\r\\n?)|\n", "\n" );
}
/**
* Runs the calling JUnit test again and fails only if it unexpectedly runs.<br/>
* This is helpful for tests that don't currently work but should work one day,
* when the tested functionality has been implemented.<br/>
* The right way to use it is:
* <pre>
* public void testXXX() {
* if (GroovyTestCase.notYetImplemented(this)) return;
* ... the real (now failing) unit test
* }
* </pre>
* Idea copied from HtmlUnit (many thanks to Marc Guillemot).
* Future versions maybe available in the JUnit distro.
* The purpose of providing a 'static' version is such that you can use the
* feature even if not subclassing GroovyTestCase.
* @return <false> when not itself already in the call stack
*/
public static boolean notYetImplemented(TestCase caller) {
if (notYetImplementedFlag.get() != null) {
return false;
}
notYetImplementedFlag.set(Boolean.TRUE);
final Method testMethod = findRunningJUnitTestMethod(caller.getClass());
try {
log.info("Running " + testMethod.getName() + " as not yet implemented");
testMethod.invoke(caller, new Class[] {});
fail(testMethod.getName() + " is marked as not yet implemented but passes unexpectedly");
}
catch (final Exception e) {
log.info(testMethod.getName() + " fails which is expected as it is not yet implemented");
// method execution failed, it is really "not yet implemented"
}
finally {
notYetImplementedFlag.set(null);
}
return true;
}
/**
* Convenience method for subclasses of GroovyTestCase, identical to
* <pre> GroovyTestCase.notYetImplemented(this); </pre>.
* @see #notYetImplemented(junit.framework.TestCase)
* @return <false> when not itself already in the call stack
*/
public boolean notYetImplemented() {
return notYetImplemented(this);
}
/**
* From JUnit. Finds from the call stack the active running JUnit test case
* @return the test case method
* @throws RuntimeException if no method could be found.
*/
private static Method findRunningJUnitTestMethod(Class caller) {
final Class[] args = new Class[] {};
// search the inial junit test
final Throwable t = new Exception();
for (int i=t.getStackTrace().length-1; i>=0; --i) {
final StackTraceElement element = t.getStackTrace()[i];
if (element.getClassName().equals(caller.getName())) {
try {
final Method m = caller.getMethod(element.getMethodName(), args);
if (isPublicTestMethod(m)) {
return m;
}
}
catch (final Exception e) {
// can't access, ignore it
}
}
}
throw new RuntimeException("No JUnit test case method found in call stack");
}
/**
* From Junit. Test if the method is a junit test.
* @param method the method
* @return <code>true</code> if this is a junit test.
*/
private static boolean isPublicTestMethod(final Method method) {
final String name = method.getName();
final Class[] parameters = method.getParameterTypes();
final Class returnType = method.getReturnType();
return parameters.length == 0 && name.startsWith("test")
&& returnType.equals(Void.TYPE)
&& Modifier.isPublic(method.getModifiers());
}
private static final ThreadLocal notYetImplementedFlag = new ThreadLocal();
}