/*
* ============LICENSE_START=======================================================
* Common Utils-Test
* ================================================================================
* Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
* ================================================================================
* 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.
* ============LICENSE_END=========================================================
*/
package org.onap.policy.common.utils.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Used to test various Throwable subclasses. Uses reflection to identify the
* constructors that the subclass supports.
*/
public class ThrowablesTester {
private static Logger logger = LoggerFactory.getLogger(ThrowablesTester.class);
public static final String EXPECTED_EXCEPTION_MSG = "expected exception";
private static final String EXPECTED_SUPPRESSED_EXCEPTION_MSG = "expected suppressed exception";
/**
* Passed as a "cause" to constructors.
*/
public static final Exception CAUSE = new Exception(EXPECTED_EXCEPTION_MSG);
/**
* Passed to new objects via the addSuppressed() method..
*/
public static final Throwable SUPPRESSED = new Throwable(EXPECTED_SUPPRESSED_EXCEPTION_MSG);
/**
* Runs tests, on an Throwable subclass, for all of the standard
* constructors. If the Throwable subclass does not support a given type of
* constructor, then it skips that test. Does not throw an exception
* if no standard constructors are found.
*
* @param claz
* subclass to be tested
* @return the number of constructors that were found/tested
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public int testThrowable(Class claz) {
int ncons = 0;
ncons += testThrowable_Default(claz);
ncons += testThrowable_StringConstuctor(claz);
ncons += testThrowable_Throwable(claz);
ncons += testThrowable_StringThrowable(claz);
ncons += testThrowable_StringThrowableBooleanBoolean(claz);
return ncons;
}
/**
* Tests Throwable objects created via the default constructor. Verifies
* that:
*
* - toString() returns a non-null value
* - getMessage() returns null
* - getCause() returns null
*
*
* If the Throwable subclass does not support this type of constructor, then
* this method simply returns.
*
* @param claz
* subclass to be tested
* @return {@code 1}, if the subclass supports this type of constructor,
* {@code 0} otherwise
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public int testThrowable_Default(Class claz) {
Constructor cons = getConstructor(claz, "default");
if (cons == null) {
return 0;
}
T ex = newInstance(cons);
assertNotNull(ex.toString());
assertNull(ex.getMessage());
assertNull(ex.getCause());
return 1;
}
/**
* Tests Throwable objects created via the constructor that takes just a
* String. Verifies that:
*
* - toString() returns a non-null value
* - getMessage() returns the original message passed to the
* constructor
* - getCause() returns null
*
*
* If the Throwable subclass does not support this type of constructor, then
* this method simply returns.
*
* @param claz
* subclass to be tested
* @return {@code 1}, if the subclass supports this type of constructor,
* {@code 0} otherwise
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public int testThrowable_StringConstuctor(Class claz) {
Constructor cons = getConstructor(claz, "string", String.class);
if (cons == null) {
return 0;
}
T ex = newInstance(cons, "hello");
assertNotNull(ex.toString());
assertEquals("hello", ex.getMessage());
assertNull(ex.getCause());
return 1;
}
/**
* Tests Throwable objects created via the constructor that takes just a
* Throwable. Verifies that:
*
* - toString() returns a non-null value
* - getMessage() returns the cause's message
* - getCause() returns the original cause passed to the
* constructor
*
*
* If the Throwable subclass does not support this type of constructor, then
* this method simply returns.
*
* @param claz
* subclass to be tested
* @return {@code 1}, if the subclass supports this type of constructor,
* {@code 0} otherwise
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public int testThrowable_Throwable(Class claz) {
Constructor cons = getConstructor(claz, "throwable", Throwable.class);
if (cons == null) {
return 0;
}
T ex = newInstance(cons, CAUSE);
assertEquals(ex.getMessage(), ex.getMessage());
assertNotNull(ex.toString());
assertEquals(CAUSE, ex.getCause());
return 1;
}
/**
* Tests Throwable objects created via the constructor that takes a String
* and a Throwable. Verifies that:
*
* - toString() returns a non-null value
* - getMessage() returns the original message passed to the
* constructor
* - getCause() returns the original cause passed to the
* constructor
*
*
* If the Throwable subclass does not support this type of constructor, then
* this method simply returns.
*
* @param claz
* subclass to be tested
* @return {@code 1}, if the subclass supports this type of constructor,
* {@code 0} otherwise
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public int testThrowable_StringThrowable(Class claz) {
Constructor cons = getConstructor(claz, "string-throwable", String.class, Throwable.class);
if (cons == null) {
return 0;
}
T ex = newInstance(cons, "world", CAUSE);
assertNotNull(ex.toString());
assertEquals("world", ex.getMessage());
assertEquals(CAUSE, ex.getCause());
return 1;
}
/**
* Tests Throwable objects created via the constructor that takes a String,
* a Throwable, and two booleans. Verifies that:
*
* - toString() returns a non-null value
* - getMessage() returns the original message passed to the
* constructor
* - getCause() returns the original cause passed to the
* constructor
* - suppressed exceptions can be added, if enabled
* - the stack trace can be added, if enabled
*
*
* If the Throwable subclass does not support this type of constructor, then
* this method simply returns.
*
* @param claz
* subclass to be tested
* @return {@code 1}, if the subclass supports this type of constructor,
* {@code 0} otherwise
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public int testThrowable_StringThrowableBooleanBoolean(Class claz) {
Constructor cons = getConstructor(claz, "string-throwable-flags", String.class, Throwable.class,
Boolean.TYPE, Boolean.TYPE);
if (cons == null) {
return 0;
}
// test each combination of "message" and "cause"
testThrowable_MessageCauseCombos(cons);
// test each combination of the boolean flags
testThrowable_SuppressStack(cons);
testThrowable_SuppressNoStack(cons);
testThrowable_NoSuppressStack(cons);
testThrowable_NoSuppressNoStack(cons);
return 1;
}
/**
* Tests each combination of values for the "message" and the "cause" when
* using the constructor that takes a String, a Throwable/Exception, and two
* booleans. Verifies that expected values are returned by toString()/i>,
* getMessage(), and getCause().
*
*
* @param cons
* constructor to be invoked
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public void testThrowable_MessageCauseCombos(Constructor cons) {
T ex;
ex = newInstance(cons, null, null, true, true);
assertNotNull(ex.toString());
assertNull(ex.getMessage());
assertNull(ex.getCause());
ex = newInstance(cons, "abc", null, true, true);
assertNotNull(ex.toString());
assertEquals("abc", ex.getMessage());
assertNull(ex.getCause());
ex = newInstance(cons, null, CAUSE, true, true);
assertNotNull(ex.toString());
assertNull(ex.getMessage());
assertEquals(CAUSE, ex.getCause());
ex = newInstance(cons, "xyz", CAUSE, true, true);
assertNotNull(ex.toString());
assertEquals("xyz", ex.getMessage());
assertEquals(CAUSE, ex.getCause());
}
/**
* Tests each combination of values for the "message" and the "cause" when
* using the constructor that takes a String, a Throwable/Exception, and two
* booleans. Verifies that expected values are returned by toString()/i>,
* getMessage(), and getCause().
*
*
* @param cons
* constructor to be invoked
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public void testThrowable_FlagCombos(Constructor cons) {
testThrowable_SuppressStack(cons);
testThrowable_SuppressNoStack(cons);
testThrowable_NoSuppressStack(cons);
testThrowable_NoSuppressNoStack(cons);
}
/**
* Tests Throwable objects constructed with {@code enableSuppression=true}
* and {@code writableStackTrace=true}. Verifies that:
*
* - toString() returns a non-null value
* - getMessage() returns the original message passed to the
* constructor
* - getCause() returns the original cause passed to the
* constructor
* - suppressed exceptions are added
* - the stack trace is added
*
*
* @param cons
* the throwable's class constructor
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public void testThrowable_SuppressStack(Constructor cons) {
T ex = newInstance(cons, "yes,yes", CAUSE, true, true);
ex.addSuppressed(SUPPRESSED);
assertNotNull(ex.toString());
assertEquals("yes,yes", ex.getMessage());
assertEquals(CAUSE, ex.getCause());
assertEquals(1, ex.getSuppressed().length);
assertEquals(SUPPRESSED, ex.getSuppressed()[0]);
assertTrue(ex.getStackTrace().length > 0);
}
/**
* Tests Throwable objects constructed with {@code enableSuppression=true}
* and {@code writableStackTrace=false}. Verifies that:
*
* - toString() returns a non-null value
* - getMessage() returns the original message passed to the
* constructor
* - getCause() returns the original cause passed to the
* constructor
* - suppressed exceptions are added
* - the stack trace is not added
*
*
* @param cons
* the throwable's class constructor
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public void testThrowable_SuppressNoStack(Constructor cons) {
T ex = newInstance(cons, "yes,no", CAUSE, true, false);
ex.addSuppressed(SUPPRESSED);
assertNotNull(ex.toString());
assertEquals("yes,no", ex.getMessage());
assertEquals(CAUSE, ex.getCause());
assertEquals(1, ex.getSuppressed().length);
assertEquals(SUPPRESSED, ex.getSuppressed()[0]);
assertEquals(0, ex.getStackTrace().length);
}
/**
* Tests Throwable objects constructed with {@code enableSuppression=false}
* and {@code writableStackTrace=true}. Verifies that:
*
* - toString() returns a non-null value
* - getMessage() returns the original message passed to the
* constructor
* - getCause() returns the original cause passed to the
* constructor
* - suppressed exceptions are not added
* - the stack trace is added
*
*
* @param cons
* the throwable's class constructor
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public void testThrowable_NoSuppressStack(Constructor cons) {
T ex = newInstance(cons, "no,yes", CAUSE, false, true);
ex.addSuppressed(SUPPRESSED);
assertNotNull(ex.toString());
assertEquals("no,yes", ex.getMessage());
assertEquals(CAUSE, ex.getCause());
assertEquals(0, ex.getSuppressed().length);
assertTrue(ex.getStackTrace().length > 0);
}
/**
* Tests Throwable objects constructed with {@code enableSuppression=false}
* and {@code writableStackTrace=false}. Verifies that:
*
* - toString() returns a non-null value
* - getMessage() returns the original message passed to the
* constructor
* - getCause() returns the original cause passed to the
* constructor
* - suppressed exceptions are not added
* - the stack trace is not added
*
* @param cons
* the throwable's class constructor
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
* @throws AssertionError
* if the constructed objects fail to pass various tests
*/
public void testThrowable_NoSuppressNoStack(Constructor cons) {
T ex = newInstance(cons, "no,no", CAUSE, false, false);
ex.addSuppressed(SUPPRESSED);
assertNotNull(ex.toString());
assertEquals("no,no", ex.getMessage());
assertEquals(CAUSE, ex.getCause());
assertEquals(0, ex.getSuppressed().length);
assertEquals(0, ex.getStackTrace().length);
}
/**
* Attempts to get a constructor for objects of a given type.
*
* @param claz
* class of objects whose constructor is to be gotten
* @param testType
* type of test being run
* @param argTypes
* argument types to be passed to the constructor
* @return the desired constructor, or {@code null} if the desired
* constructor is not available
*/
protected Constructor getConstructor(Class claz, String testType,
Class>... argTypes) {
try {
return claz.getConstructor(argTypes);
} catch (NoSuchMethodException | SecurityException e) {
// this constructor is not defined so nothing to test
logger.debug("skipped test, no constructor for: " + claz + " due to: " + e);
return null;
}
}
/**
* Creates a new instance of an Throwable subclass.
*
* @param cons
* subclass constructor
* @param args
* arguments to be passed to the constructor
* @return a new instance of the Throwable subclass
* @throws ConstructionError
* if the Throwable subclass cannot be constructed
*/
protected T newInstance(Constructor cons, Object... args) {
try {
return cons.newInstance(args);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
throw new ConstructionError(e);
}
}
}