~brian-thomason/+junk/groovy-1.7.2

« back to all changes in this revision

Viewing changes to src/main/groovy/util/GroovyTestCase.java

  • Committer: Brian Thomason
  • Date: 2011-12-20 17:31:12 UTC
  • Revision ID: brian.thomason@canonical.com-20111220173112-u53pbzhiy5y1hau0
Initial import

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright 2003-2007 the original author or authors.
 
3
 *
 
4
 * Licensed under the Apache License, Version 2.0 (the "License");
 
5
 * you may not use this file except in compliance with the License.
 
6
 * You may obtain a copy of the License at
 
7
 *
 
8
 *     http://www.apache.org/licenses/LICENSE-2.0
 
9
 *
 
10
 * Unless required by applicable law or agreed to in writing, software
 
11
 * distributed under the License is distributed on an "AS IS" BASIS,
 
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
13
 * See the License for the specific language governing permissions and
 
14
 * limitations under the License.
 
15
 */
 
16
package groovy.util;
 
17
 
 
18
import groovy.lang.Closure;
 
19
import groovy.lang.GroovyRuntimeException;
 
20
import groovy.lang.GroovyShell;
 
21
import junit.framework.TestCase;
 
22
import org.codehaus.groovy.runtime.InvokerHelper;
 
23
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
 
24
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
 
25
 
 
26
import java.lang.reflect.Method;
 
27
import java.lang.reflect.Modifier;
 
28
import java.util.logging.Logger;
 
29
 
 
30
/**
 
31
 * A default JUnit TestCase in Groovy. This provides a number of helper methods
 
32
 * plus avoids the JUnit restriction of requiring all test* methods to be void
 
33
 * return type.
 
34
 *
 
35
 * @author <a href="mailto:bob@werken.com">bob mcwhirter</a>
 
36
 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
 
37
 * @author Dierk Koenig (the notYetImplemented feature, changes to shouldFail)
 
38
 * @version $Revision: 19550 $
 
39
 */
 
40
public class GroovyTestCase extends TestCase {
 
41
 
 
42
    protected static Logger log = Logger.getLogger(GroovyTestCase.class.getName());
 
43
    private static int counter;
 
44
    private static final int MAX_NESTED_EXCEPTIONS = 10;
 
45
    public static final String TEST_SCRIPT_NAME_PREFIX = "TestScript";
 
46
 
 
47
    private boolean useAgileDoxNaming = false;
 
48
 
 
49
    public GroovyTestCase() {
 
50
    }
 
51
 
 
52
    /**
 
53
     * Overload the getName() method to make the test cases look more like AgileDox
 
54
     * (thanks to Joe Walnes for this tip!)
 
55
     */
 
56
    public String getName() {
 
57
        if (useAgileDoxNaming) {
 
58
            return super.getName().substring(4).replaceAll("([A-Z])", " $1").toLowerCase();
 
59
        }
 
60
        else {
 
61
            return super.getName();
 
62
        }
 
63
    }
 
64
 
 
65
    public String getMethodName() {
 
66
        return super.getName();
 
67
    }
 
68
 
 
69
    /**
 
70
     * Asserts that the arrays are equivalent and contain the same values
 
71
     *
 
72
     * @param expected
 
73
     * @param value
 
74
     */
 
75
    protected void assertArrayEquals(Object[] expected, Object[] value) {
 
76
        String message =
 
77
            "expected array: " + InvokerHelper.toString(expected) + " value array: " + InvokerHelper.toString(value);
 
78
        assertNotNull(message + ": expected should not be null", expected);
 
79
        assertNotNull(message + ": value should not be null", value);
 
80
        assertEquals(message, expected.length, value.length);
 
81
        for (int i = 0, size = expected.length; i < size; i++) {
 
82
            assertEquals("value[" + i + "] when " + message, expected[i], value[i]);
 
83
        }
 
84
    }
 
85
 
 
86
    /**
 
87
     * Asserts that the array of characters has a given length
 
88
     *
 
89
     * @param length expected length
 
90
     * @param array the array
 
91
     */
 
92
    protected void assertLength(int length, char[] array) {
 
93
        assertEquals(length, array.length);
 
94
    }
 
95
 
 
96
    /**
 
97
     * Asserts that the array of ints has a given length
 
98
     *
 
99
     * @param length expected length
 
100
     * @param array the array
 
101
     */
 
102
    protected void assertLength(int length, int[] array) {
 
103
        assertEquals(length, array.length);
 
104
    }
 
105
 
 
106
    /**
 
107
     * Asserts that the array of objects has a given length
 
108
     *
 
109
     * @param length expected length
 
110
     * @param array the array
 
111
     */
 
112
    protected void assertLength(int length, Object[] array) {
 
113
        assertEquals(length, array.length);
 
114
    }
 
115
 
 
116
    /**
 
117
     * Asserts that the array of characters contains a given char
 
118
     *
 
119
     * @param expected expected character to be found
 
120
     * @param array the array
 
121
     */
 
122
    protected void assertContains(char expected, char[] array) {
 
123
        for (int i = 0; i < array.length; ++i) {
 
124
            if (array[i] == expected) {
 
125
                return;
 
126
            }
 
127
        }
 
128
 
 
129
        StringBuffer message = new StringBuffer();
 
130
 
 
131
        message.append(expected).append(" not in {");
 
132
 
 
133
        for (int i = 0; i < array.length; ++i) {
 
134
            message.append("'").append(array[i]).append("'");
 
135
 
 
136
            if (i < (array.length - 1)) {
 
137
                message.append(", ");
 
138
            }
 
139
        }
 
140
 
 
141
        message.append(" }");
 
142
 
 
143
        fail(message.toString());
 
144
    }
 
145
 
 
146
    /**
 
147
     * Asserts that the array of ints contains a given int
 
148
     *
 
149
     * @param expected expected int
 
150
     * @param array the array
 
151
     */
 
152
    protected void assertContains(int expected, int[] array) {
 
153
        for (int i = 0; i < array.length; ++i) {
 
154
            if (array[i] == expected) {
 
155
                return;
 
156
            }
 
157
        }
 
158
 
 
159
        StringBuffer message = new StringBuffer();
 
160
 
 
161
        message.append(expected).append(" not in {");
 
162
 
 
163
        for (int i = 0; i < array.length; ++i) {
 
164
            message.append("'").append(array[i]).append("'");
 
165
 
 
166
            if (i < (array.length - 1)) {
 
167
                message.append(", ");
 
168
            }
 
169
        }
 
170
 
 
171
        message.append(" }");
 
172
 
 
173
        fail(message.toString());
 
174
    }
 
175
 
 
176
    /**
 
177
     * Asserts that the value of toString() on the given object matches the
 
178
     * given text string
 
179
     *
 
180
     * @param value the object to be output to the console
 
181
     * @param expected the expected String representation
 
182
     */
 
183
    protected void assertToString(Object value, String expected) {
 
184
        Object console = InvokerHelper.invokeMethod(value, "toString", null);
 
185
        assertEquals("toString() on value: " + value, expected, console);
 
186
    }
 
187
 
 
188
    /**
 
189
     * Asserts that the value of inspect() on the given object matches the
 
190
     * given text string
 
191
     *
 
192
     * @param value the object to be output to the console
 
193
     * @param expected the expected String representation
 
194
     */
 
195
    protected void assertInspect(Object value, String expected) {
 
196
        Object console = InvokerHelper.invokeMethod(value, "inspect", null);
 
197
        assertEquals("inspect() on value: " + value, expected, console);
 
198
    }
 
199
 
 
200
    /**
 
201
     * Asserts that the script runs without any exceptions
 
202
     *
 
203
     * @param script the script that should pass without any exception thrown
 
204
     */
 
205
    protected void assertScript(final String script) throws Exception {
 
206
        GroovyShell shell = new GroovyShell();
 
207
        shell.evaluate(script, getTestClassName());
 
208
    }
 
209
 
 
210
    protected String getTestClassName() {
 
211
        return TEST_SCRIPT_NAME_PREFIX + getMethodName() + (counter++) + ".groovy";
 
212
    }
 
213
 
 
214
    /**
 
215
     * Asserts that the given code closure fails when it is evaluated
 
216
     *
 
217
     * @param code
 
218
     * @return the message of the thrown Throwable
 
219
     */
 
220
    protected String shouldFail(Closure code) {
 
221
        boolean failed = false;
 
222
        String result = null;
 
223
        try {
 
224
            code.call();
 
225
        }
 
226
        catch (GroovyRuntimeException gre) {
 
227
            failed = true;
 
228
            result = ScriptBytecodeAdapter.unwrap(gre).getMessage();
 
229
        }
 
230
        catch (Throwable e) {
 
231
                failed = true;
 
232
                result = e.getMessage();
 
233
        }
 
234
        assertTrue("Closure " + code + " should have failed", failed);
 
235
        return result;
 
236
    }
 
237
 
 
238
    /**
 
239
     * Asserts that the given code closure fails when it is evaluated
 
240
     * and that a particular exception is thrown.
 
241
     *
 
242
     * @param clazz the class of the expected exception
 
243
     * @param code the closure that should fail
 
244
     * @return the message of the expected Throwable
 
245
     */
 
246
    protected String shouldFail(Class clazz, Closure code) {
 
247
        Throwable th = null;
 
248
        try {
 
249
            code.call();
 
250
        } catch (GroovyRuntimeException gre) {
 
251
            th = ScriptBytecodeAdapter.unwrap(gre);
 
252
        } catch (Throwable e) {
 
253
            th = e;
 
254
        }
 
255
 
 
256
        if (th == null) {
 
257
            fail("Closure " + code + " should have failed with an exception of type " + clazz.getName());
 
258
        } else if (!clazz.isInstance(th)) {
 
259
            fail("Closure " + code + " should have failed with an exception of type " + clazz.getName() + ", instead got Exception " + th);
 
260
        }
 
261
        return th.getMessage();
 
262
    }
 
263
 
 
264
    /**
 
265
     * Asserts that the given code closure fails when it is evaluated
 
266
     * and that a particular exception can be attributed to the cause.
 
267
     * The expected exception class is compared recursively with any nested
 
268
     * exceptions using getCause() until either a match is found or no more
 
269
     * nested exceptions exist.
 
270
     * 
 
271
     * If a match is found the error message associated with the matching
 
272
     * exception is returned. If no match was found the method will fail.
 
273
     *
 
274
     * @param clazz the class of the expected exception
 
275
     * @param code the closure that should fail
 
276
     * @return the message of the expected Throwable
 
277
     */
 
278
    protected String shouldFailWithCause(Class clazz, Closure code) {
 
279
        Throwable th = null;
 
280
        Throwable orig = null;
 
281
        int level = 0;
 
282
        try {
 
283
            code.call();
 
284
        } catch (GroovyRuntimeException gre) {
 
285
            orig = ScriptBytecodeAdapter.unwrap(gre);
 
286
            th = orig.getCause();
 
287
        } catch (Throwable e) {
 
288
            orig = e;
 
289
            th = orig.getCause();
 
290
        }
 
291
 
 
292
        while (th != null && !clazz.isInstance(th) && th != th.getCause() && level < MAX_NESTED_EXCEPTIONS) {
 
293
            th = th.getCause();
 
294
            level++;
 
295
        }
 
296
 
 
297
        if (orig == null) {
 
298
            fail("Closure " + code + " should have failed with an exception caused by type " + clazz.getName());
 
299
        } else if (th == null || !clazz.isInstance(th)) {
 
300
            fail("Closure " + code + " should have failed with an exception caused by type " + clazz.getName() + ", instead found these Exceptions:\n" + buildExceptionList(orig));
 
301
        }
 
302
        return th.getMessage();
 
303
    }
 
304
 
 
305
    private String buildExceptionList(Throwable th) {
 
306
        StringBuilder sb = new StringBuilder();
 
307
        int level = 0;
 
308
        while (th != null) {
 
309
            if (level > 1) {
 
310
                for (int i = 0; i < level - 1; i++) sb.append("   ");
 
311
            }
 
312
            if (level > 0) sb.append("-> ");
 
313
            if (level > MAX_NESTED_EXCEPTIONS) {
 
314
                sb.append("...");
 
315
                break;
 
316
            }
 
317
            sb.append(th.getClass().getName()).append(": ").append(th.getMessage()).append("\n");
 
318
            if (th == th.getCause()) {
 
319
                break;
 
320
            }
 
321
            th = th.getCause();
 
322
            level++;
 
323
        }
 
324
        return sb.toString();
 
325
    }
 
326
 
 
327
    /**
 
328
     *  Returns a copy of a string in which all EOLs are \n.
 
329
     */
 
330
    protected String fixEOLs( String value )
 
331
    {
 
332
        return value.replaceAll( "(\\r\\n?)|\n", "\n" );
 
333
    }
 
334
 
 
335
    /**
 
336
     * Runs the calling JUnit test again and fails only if it unexpectedly runs.<br/>
 
337
     * This is helpful for tests that don't currently work but should work one day,
 
338
     * when the tested functionality has been implemented.<br/>
 
339
     * The right way to use it is:
 
340
     * <pre>
 
341
     * public void testXXX() {
 
342
     *   if (GroovyTestCase.notYetImplemented(this)) return;
 
343
     *   ... the real (now failing) unit test
 
344
     * }
 
345
     * </pre>
 
346
     * Idea copied from HtmlUnit (many thanks to Marc Guillemot).
 
347
     * Future versions maybe available in the JUnit distro.
 
348
     * The purpose of providing a 'static' version is such that you can use the
 
349
     * feature even if not subclassing GroovyTestCase.
 
350
     * @return <false> when not itself already in the call stack
 
351
     */
 
352
    public static boolean notYetImplemented(TestCase caller) {
 
353
        if (notYetImplementedFlag.get() != null) {
 
354
            return false;
 
355
        }
 
356
        notYetImplementedFlag.set(Boolean.TRUE);
 
357
 
 
358
        final Method testMethod = findRunningJUnitTestMethod(caller.getClass());
 
359
        try {
 
360
            log.info("Running " + testMethod.getName() + " as not yet implemented");
 
361
            testMethod.invoke(caller, (Object[]) new Class[] {});
 
362
            fail(testMethod.getName() + " is marked as not yet implemented but passes unexpectedly");
 
363
        }
 
364
        catch (final Exception e) {
 
365
            log.info(testMethod.getName() + " fails which is expected as it is not yet implemented");
 
366
            // method execution failed, it is really "not yet implemented"
 
367
        }
 
368
        finally {
 
369
            notYetImplementedFlag.set(null);
 
370
        }
 
371
        return true;
 
372
    }
 
373
 
 
374
    /**
 
375
     * Convenience method for subclasses of GroovyTestCase, identical to
 
376
     * <pre> GroovyTestCase.notYetImplemented(this); </pre>.
 
377
     * @see #notYetImplemented(junit.framework.TestCase)
 
378
     * @return  <false> when not itself already in the call stack
 
379
     */
 
380
    public boolean notYetImplemented() {
 
381
        return notYetImplemented(this);
 
382
    }
 
383
 
 
384
    /**
 
385
     * From JUnit. Finds from the call stack the active running JUnit test case
 
386
     * @return the test case method
 
387
     * @throws RuntimeException if no method could be found.
 
388
     */
 
389
    private static Method findRunningJUnitTestMethod(Class caller) {
 
390
        final Class[] args = new Class[] {};
 
391
 
 
392
        // search the inial junit test
 
393
        final Throwable t = new Exception();
 
394
        for (int i=t.getStackTrace().length-1; i>=0; --i) {
 
395
            final StackTraceElement element = t.getStackTrace()[i];
 
396
            if (element.getClassName().equals(caller.getName())) {
 
397
                try {
 
398
                    final Method m = caller.getMethod(element.getMethodName(), args);
 
399
                    if (isPublicTestMethod(m)) {
 
400
                        return m;
 
401
                    }
 
402
                }
 
403
                catch (final Exception e) {
 
404
                    // can't access, ignore it
 
405
                }
 
406
            }
 
407
        }
 
408
        throw new RuntimeException("No JUnit test case method found in call stack");
 
409
    }
 
410
 
 
411
 
 
412
    /**
 
413
     * From Junit. Test if the method is a junit test.
 
414
     * @param method the method
 
415
     * @return <code>true</code> if this is a junit test.
 
416
     */
 
417
    private static boolean isPublicTestMethod(final Method method) {
 
418
        final String name = method.getName();
 
419
        final Class[] parameters = method.getParameterTypes();
 
420
        final Class returnType = method.getReturnType();
 
421
 
 
422
        return parameters.length == 0 && name.startsWith("test")
 
423
            && returnType.equals(Void.TYPE)
 
424
            && Modifier.isPublic(method.getModifiers());
 
425
    }
 
426
 
 
427
    public static void assertEquals(String message, Object expected, Object actual) {
 
428
        if (expected == null && actual == null)
 
429
                        return;
 
430
                if (expected != null && DefaultTypeTransformation.compareEqual(expected, actual))
 
431
                        return;
 
432
                failNotEquals(message, expected, actual);
 
433
    }
 
434
 
 
435
    public static void assertEquals(Object expected, Object actual) {
 
436
            assertEquals(null, expected, actual);
 
437
        }
 
438
 
 
439
        public static void assertEquals(String expected, String actual) {
 
440
            assertEquals(null, expected, actual);
 
441
        }
 
442
 
 
443
    private static final ThreadLocal notYetImplementedFlag = new ThreadLocal();
 
444
}