/*******************************************************************************
* Copyright 2014 Analog Devices, Inc.
*
* Licensed 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 com.analog.lyric.util.test;
import static org.junit.Assert.*;
import java.io.PrintStream;
import java.lang.reflect.InvocationTargetException;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.Nullable;
import com.analog.lyric.collect.Supers;
/**
* Utility functions for testing exception behavior.
*
* @since 0.05
*/
public class ExceptionTester
{
private static class ReflectionFailure extends Error
{
private static final long serialVersionUID = 1L;
ReflectionFailure(String message)
{
super(message);
}
}
/**
* Test case interface for use with {@link #expectThrow(Class, String, Case)}.
*
* @since 0.08
*/
public interface Case
{
void run() throws Throwable;
}
/**
* Standard error stream that will be used for dumping stacks on failure.
* @since 0.08
*/
public static PrintStream stderr = System.err;
/**
* Test for expected delivery of an exception.
* <p>
* If test case fails to throw expected exception with expected message, this will
* invoke {@link org.junit.Assert#fail}. Additionally, if the wrong exception type
* is thrown, this will print the stack on {@link #stderr}.
* <p>
* Examples:
* <blockquote><pre>
* import static com.analog.lyric.util.test.ExpectionTester.*;
* ...
* // Java 8 or later using lambda expression
* expectThrow(SomeException.class, "expected message", () -> doSomething());
*
* // Java 7
* expectThrow(SomeException.class, "expected message",
* new ExceptionTester.Case() {
* public void run() throws Throwable {
* doSomething();
* }
* });
*
* </pre></blockquote>
* <p>
* @param expectedEx is the expected exception type. The caught exception must be a subclass of this.
* @param msgPattern if not-null specifies a regular expression that must match the exception's
* {@linkplain Throwable#getMessage message}
* @param testCase has a {@code run} method that is expected to throw the exception.
* @since 0.08
*/
public static void expectThrow(Class<? extends Throwable> expectedEx, @Nullable String msgPattern, Case testCase)
{
boolean caughtSomething = false;
try
{
testCase.run();
}
catch (ReflectionFailure ex)
{
fail(ex.getMessage());
}
catch (Throwable ex)
{
caughtSomething = true;
if (!expectedEx.isInstance(ex))
{
ex.printStackTrace(stderr);
fail(String.format("Expected '%s' but caught '%s'", expectedEx.getSimpleName(),
ex.getClass().getSimpleName()));
}
if (msgPattern != null && !Pattern.matches(msgPattern, ex.getMessage()))
{
fail(String.format("Expected message matching '%s' but got '%s'", msgPattern, ex.getMessage()));
}
}
if (!caughtSomething)
{
fail(String.format("Expected '%s' but no exception thrown", expectedEx.getSimpleName()));
}
}
/**
* Test for expected delivery of an exception.
* <p>
* @see #expectThrow(Class, String, Case)
* @since 0.08
*/
public static void expectThrow(Class<? extends Throwable> expectedEx, Case testCase)
{
expectThrow(expectedEx, null, testCase);
}
/**
* Use reflective invocation to test for expected exception.
*
* @param expectedException asserts that the invocation will result in an exception of this type (or a subtype).
* @param messagePattern if non-null asserts that the exception's message will match this specified regular
* expression pattern (see {@link Pattern}).
* @param methodName is the method to be invoked
* @param object is the object on which the method will be invoked. If this is a class, then a static call
* will be performed.
* @param args are the parameters of the method call.
*/
@SafeVarargs
public static <T> void expectThrow(Class<? extends Throwable> expectedException, @Nullable String messagePattern,
Object object, String methodName, @Nullable T ... args)
{
Class<?> declaringClass = object instanceof Class ? (Class<?>)object : object.getClass();
expectThrow(expectedException, messagePattern, object, declaringClass, methodName, args);
}
/**
* @deprecated as of release 0.08 it should no longer be necessary to use this method to work around
* calling public methods on non-public subclasses.
* <p>
* Use reflective invocation to test for expected exception.
*
* @param expectedException asserts that the invocation will result in an exception of this type (or a subtype).
* @param messagePattern if non-null asserts that the exception's message will match this specified regular
* expression pattern (see {@link Pattern}).
* @param object is the object on which the method will be invoked. If this is a class, then a static call
* will be performed.
* @param declaringClass is the class in which to look up the method. Use this when runtime type of {@code object}
* is private and you need to invoke public method from superclass or interface.
* @param methodName is the method to be invoked
* @param args are the parameters of the method call.
* @since 0.06
*/
@Deprecated
@SafeVarargs
public static <T> void expectThrow(Class<? extends Throwable> expectedException, @Nullable String messagePattern,
final Object object, final Class<?> declaringClass, final String methodName, final @Nullable T ... args)
{
expectThrow(expectedException, messagePattern,
new Case() {
@Override
public void run() throws Throwable
{
try
{
Supers.invokeMethod(object, declaringClass, methodName, args);
}
catch (InvocationTargetException wrappedEx)
{
throw wrappedEx.getTargetException();
}
catch (Exception ex)
{
throw new ReflectionFailure(ex.toString());
}
}
});
}
/**
* Use reflective invocation to test for expected exception.
*
* Same as {@link #expectThrow(Class, String, Object, String, Object...)} but with null
* {@code messagePattern}.
*/
@SafeVarargs
public static <T> void expectThrow(Class<? extends Throwable> exceptionClass, Object object, String methodName,
@Nullable T ... args)
{
expectThrow(exceptionClass, null, object, methodName, args);
}
}