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 }