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 }