All Downloads are FREE. Search and download functionalities are using the official Maven repository.

groovy.test.GroovyAssert Maven / Gradle / Ivy

/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package groovy.test;

import groovy.lang.Closure;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.GroovyShell;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

import static org.codehaus.groovy.runtime.DefaultGroovyMethods.isAtLeast;

/**
 * 

{@code GroovyAssert} contains a set of static assertion and test helper methods for JUnit 4+. * They augment the kind of helper methods found in JUnit 4's {@link org.junit.Assert} class. * JUnit 3 users typically don't use these methods but instead, * the equivalent methods in {@link groovy.test.GroovyTestCase}. *

* *

* {@code GroovyAssert} methods can either be used by fully qualifying the static method like: * *

 *     groovy.test.GroovyAssert.shouldFail { ... }
 * 
* * or by importing the static methods with one ore more static imports: * *
 *     import static groovy.test.GroovyAssert.shouldFail
 * 
*

* Backwards compatibility note: * Prior to Groovy 4, {@code GroovyAssert} extended JUnit 4's {@link org.junit.Assert} class. * This meant that you could statically import static methods from that class via {@code GroovyAssert}, e.g.: *
 *     import static groovy.test.GroovyAssert.assertNotNull
 * 
* This is generally regarded as a code smell since inheritance is primarily to do with instance methods. * From Groovy 4, you should import such methods directly, e.g.: *
 *     import static org.junit.Assert.assertNotNull
 * 
* * @see groovy.test.GroovyTestCase * @since 2.3 */ public class GroovyAssert { private static final Logger log = Logger.getLogger(GroovyAssert.class.getName()); private static final int MAX_NESTED_EXCEPTIONS = 10; private static final AtomicInteger counter = new AtomicInteger(0); public static final String TEST_SCRIPT_NAME_PREFIX = "TestScript"; /** * @return a generic script name to be used by {@code GroovyShell#evaluate} calls. */ protected static String genericScriptName() { return TEST_SCRIPT_NAME_PREFIX + (counter.getAndIncrement()) + ".groovy"; } /** * Asserts that the script runs without any exceptions * * @param script the script that should pass without any exception thrown */ public static void assertScript(final String script) throws Exception { assertScript(new GroovyShell(), script); } /** * Asserts that the script runs using the given shell without any exceptions * * @param shell the shell to use to evaluate the script * @param script the script that should pass without any exception thrown */ public static void assertScript(final GroovyShell shell, final String script) { shell.evaluate(script, genericScriptName()); } /** * Asserts that the given code closure fails when it is evaluated * * @param code the code expected to fail * @return the caught exception */ public static Throwable shouldFail(Closure code) { boolean failed = false; Throwable th = null; try { code.call(); } catch (GroovyRuntimeException gre) { failed = true; th = ScriptBytecodeAdapter.unwrap(gre); } catch (Throwable e) { failed = true; th = e; } assertTrue("Closure " + code + " should have failed", failed); return th; } private static void assertTrue(String message, boolean condition) { if (!condition) { fail(message); } } public static void fail(String message) { if (message == null) { throw new AssertionError(); } throw new AssertionError(message); } /** * Asserts that the given code closure fails when it is evaluated * and that a particular type of exception is thrown. * * @param clazz the class of the expected exception * @param code the closure that should fail * @return the caught exception */ public static Throwable shouldFail(Class clazz, Closure code) { Throwable th = null; try { code.call(); } catch (GroovyRuntimeException gre) { th = ScriptBytecodeAdapter.unwrap(gre); } 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; } /** * Asserts that the given code closure fails when it is evaluated * and that a particular Exception type can be attributed to the cause. * The expected exception class is compared recursively with any nested * exceptions using getCause() until either a match is found or no more * nested exceptions exist. *

* If a match is found, the matching exception is returned * otherwise the method will fail. * * @param expectedCause the class of the expected exception * @param code the closure that should fail * @return the cause */ public static Throwable shouldFailWithCause(Class expectedCause, Closure code) { if (expectedCause == null) { fail("The expectedCause class cannot be null"); } Throwable cause = null; Throwable orig = null; int level = 0; try { code.call(); } catch (GroovyRuntimeException gre) { orig = ScriptBytecodeAdapter.unwrap(gre); cause = orig.getCause(); } catch (Throwable e) { orig = e; cause = orig.getCause(); } if (orig != null && cause == null) { fail("Closure " + code + " was expected to fail due to a nested cause of type " + expectedCause.getName() + " but instead got a direct exception of type " + orig.getClass().getName() + " with no nested cause(s). Code under test has a bug or perhaps you meant shouldFail?"); } while (cause != null && !expectedCause.isInstance(cause) && cause != cause.getCause() && level < MAX_NESTED_EXCEPTIONS) { cause = cause.getCause(); level++; } if (orig == null) { fail("Closure " + code + " should have failed with an exception having a nested cause of type " + expectedCause.getName()); } else if (cause == null || !expectedCause.isInstance(cause)) { fail("Closure " + code + " should have failed with an exception having a nested cause of type " + expectedCause.getName() + ", instead found these Exceptions:\n" + buildExceptionList(orig)); } return cause; } /** * Asserts that the given script fails when it is evaluated * and that a particular type of exception is thrown. * * @param clazz the class of the expected exception * @param script the script that should fail * @return the caught exception */ public static Throwable shouldFail(Class clazz, String script) { return shouldFail(new GroovyShell(), clazz, script); } /** * Asserts that the given script fails when it is evaluated using the given shell * and that a particular type of exception is thrown. * * @param shell the shell to use to evaluate the script * @param clazz the class of the expected exception * @param script the script that should fail * @return the caught exception */ public static Throwable shouldFail(GroovyShell shell, Class clazz, String script) { Throwable th = null; try { shell.evaluate(script, genericScriptName()); } catch (GroovyRuntimeException gre) { th = ScriptBytecodeAdapter.unwrap(gre); } catch (Throwable e) { th = e; } if (th == null) { fail("Script should have failed with an exception of type " + clazz.getName()); } else if (!clazz.isInstance(th)) { fail("Script should have failed with an exception of type " + clazz.getName() + ", instead got Exception " + th); } return th; } /** * Asserts that the given script fails when it is evaluated * * @param script the script expected to fail * @return the caught exception */ public static Throwable shouldFail(String script) { return shouldFail(new GroovyShell(), script); } /** * Asserts that the given script fails when it is evaluated using the given shell * * @param shell the shell to use to evaluate the script * @param script the script expected to fail * @return the caught exception */ public static Throwable shouldFail(GroovyShell shell, String script) { boolean failed = false; Throwable th = null; try { shell.evaluate(script, genericScriptName()); } catch (GroovyRuntimeException gre) { failed = true; th = ScriptBytecodeAdapter.unwrap(gre); } catch (Throwable e) { failed = true; th = e; } assertTrue("Script should have failed", failed); return th; } /** * NotYetImplemented Implementation */ private static final ThreadLocal notYetImplementedFlag = new ThreadLocal<>(); /** * 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 initial junit test final StackTraceElement[] stackTrace = new Exception().getStackTrace(); for (int i = stackTrace.length - 1; i >= 0; --i) { final StackTraceElement element = stackTrace[i]; if (element.getClassName().equals(caller.getName())) { try { final Method m = caller.getMethod(element.getMethodName(), args); if (isPublicTestMethod(m)) { return m; } } catch (Exception ignore) { // 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 3 or 4 test. * * @param method the method * @return true 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") || hasTestAnnotation(method)) && returnType.equals(Void.TYPE) && Modifier.isPublic(method.getModifiers()); } private static boolean hasTestAnnotation(Method method) { for (Annotation annotation : method.getAnnotations()) { if ("org.junit.Test".equals(annotation.annotationType().getName())) { return true; } } return false; } /** *

* Runs the calling JUnit test again and fails only if it unexpectedly runs.
* This is helpful for tests that don't currently work but should work one day, * when the tested functionality has been implemented.
*

* *

* The right way to use it for JUnit 3 is: * *

     * public void testXXX() {
     *   if (GroovyAssert.notYetImplemented(this)) return;
     *   ... the real (now failing) unit test
     * }
     * 
* * or for JUnit 4 * *
     * @Test
     * public void XXX() {
     *   if (GroovyAssert.notYetImplemented(this)) return;
     *   ... the real (now failing) unit test
     * }
     * 
*

* *

* Idea copied from HtmlUnit (many thanks to Marc Guillemot). * Future versions maybe available in the JUnit distribution. *

* * @return {@code false} when not itself already in the call stack */ public static boolean notYetImplemented(Object 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, (Object[]) 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.remove(); } return true; } /** * @return true if the JDK version is at least the version given by specVersion (e.g. "1.8", "9.0") * @since 2.5.7 */ public static boolean isAtLeastJdk(String specVersion) { boolean result = false; try { result = isAtLeast(new BigDecimal(System.getProperty("java.specification.version")), specVersion); } catch (Exception ignore) { } return result; } private static String buildExceptionList(Throwable th) { StringBuilder sb = new StringBuilder(); int level = 0; while (th != null) { if (level > 1) { for (int i = 0; i < level - 1; i++) sb.append(" "); } if (level > 0) sb.append("-> "); if (level > MAX_NESTED_EXCEPTIONS) { sb.append("..."); break; } sb.append(th.getClass().getName()).append(": ").append(th.getMessage()).append("\n"); if (th == th.getCause()) { break; } th = th.getCause(); level++; } return sb.toString(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy