Verified Commit 86a4ae48 authored by Daniel Mangold's avatar Daniel Mangold
Browse files

Added custom assertions

parent 741823ff
package h05;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Map;
import org.opentest4j.AssertionFailedError;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import static java.lang.reflect.Modifier.*;
import static java.lang.reflect.Modifier.ABSTRACT;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.*;
@SuppressWarnings("unused")
public class Assertions {
public static final String[] UNBOUNDED = new String[] {Object.class.getTypeName()};
private static final Map<Integer, Object[]> MODIFIER_METHODS;
static {
Map<Integer, Object[]> modifierMethodsTmp = null;
try {
modifierMethodsTmp = Map.of(
PUBLIC, new Object[] {Modifier.class.getDeclaredMethod("isPublic", int.class), "public"},
PRIVATE, new Object[] {Modifier.class.getDeclaredMethod("isPrivate", int.class), "private"},
PROTECTED, new Object[] {Modifier.class.getDeclaredMethod("isProtected", int.class), "protected"},
STATIC, new Object[] {Modifier.class.getDeclaredMethod("isStatic", int.class), "static"},
FINAL, new Object[] {Modifier.class.getDeclaredMethod("isFinal", int.class), "final"},
ABSTRACT, new Object[] {Modifier.class.getDeclaredMethod("isAbstract", int.class), "abstract"}
);
modifierMethodsTmp = new HashMap<>() {{
put(PUBLIC, new Object[] {Modifier.class.getDeclaredMethod("isPublic", int.class), "public"});
put(PRIVATE, new Object[] {Modifier.class.getDeclaredMethod("isPrivate", int.class), "private"});
put(PROTECTED, new Object[] {Modifier.class.getDeclaredMethod("isProtected", int.class), "protected"});
put(STATIC, new Object[] {Modifier.class.getDeclaredMethod("isStatic", int.class), "static"});
put(FINAL, new Object[] {Modifier.class.getDeclaredMethod("isFinal", int.class), "final"});
put(SYNCHRONIZED, new Object[] {Modifier.class.getDeclaredMethod("isSynchronized", int.class), "synchronized"});
put(VOLATILE, new Object[] {Modifier.class.getDeclaredMethod("isVolatile", int.class), "volatile"});
put(TRANSIENT, new Object[] {Modifier.class.getDeclaredMethod("isTransient", int.class), "transient"});
put(NATIVE, new Object[] {Modifier.class.getDeclaredMethod("isNative", int.class), "native"});
put(INTERFACE, new Object[] {Modifier.class.getDeclaredMethod("isInterface", int.class), "an interface"});
put(ABSTRACT, new Object[] {Modifier.class.getDeclaredMethod("isAbstract", int.class), "abstract"});
put(STRICT, new Object[] {Modifier.class.getDeclaredMethod("isStrict", int.class), "strict"});
}};
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
......@@ -35,36 +43,115 @@ public class Assertions {
}
/**
* Assert that the given member has the specified modifiers. <br>
* Throws a {@link AssertionError} if the assertion fails
* Assert that the given member has the specified modifiers.
* <br><br>
* Calls {@link Assertions#assertHasModifiers(Member, List, String)} with {@code null} as last
* parameter (default error message)
* Calls {@link Assertions#assertHasModifiers(Member, String, Integer...)} with {@code null} as error
* message (default error message)
* @param member the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
* @throws IllegalArgumentException if a value does not correspond to a modifier
*/
public static void assertHasModifiers(Member member, List<Integer> modifiers) {
assertHasModifiers(member, modifiers, null);
public static void assertHasModifiers(Member member, Integer... modifiers) {
assertHasModifiers(member, null, modifiers);
}
/**
* Assert that the given member has the specified modifiers. <br>
* Throws a {@link AssertionError} if the assertion fails
* Assert that the given member has the specified modifiers.
* @param member the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
* @param errorMsg the error message to pass to {@link AssertionError}
* @throws IllegalArgumentException if a value does not correspond to a modifier
*/
public static void assertHasModifiers(Member member, List<Integer> modifiers, String errorMsg) {
public static void assertHasModifiers(Member member, String errorMsg, Integer... modifiers) {
try {
for (Map.Entry<Integer, Object[]> entry : MODIFIER_METHODS.entrySet()) {
Integer modifier = entry.getKey();
Method method = (Method) entry.getValue()[0];
String msgSuffix = (String) entry.getValue()[1];
for (Integer modifier : modifiers) {
Object[] value = MODIFIER_METHODS.getOrDefault(modifier, null);
if (value == null)
throw new IllegalArgumentException(String.valueOf(modifier));
if (modifiers.contains(modifier))
Method method = (Method) value[0];
String msgSuffix = (String) value[1];
if (arrayContains(modifier, modifiers))
assertTrue((Boolean) method.invoke(null, member.getModifiers()),
errorMsg != null ? errorMsg : member.getName() + " must be " + msgSuffix);
else
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
/**
* Assert that the given class has the specified modifiers.
* <br><br>
* Calls {@link Assertions#assertHasModifiers(Class, String, Integer...)} with {@code null} as error
* message (default error message)
* @param c the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
*/
public static void assertHasModifiers(Class<?> c, Integer... modifiers) {
assertHasModifiers(c, null, modifiers);
}
/**
* Assert that the given class has the specified modifiers.
* @param c the member to check the modifiers of
* @param errorMsg the error message to pass to {@link AssertionError}
* @param modifiers a list of modifiers (constants of {@link Modifier})
*/
public static void assertHasModifiers(Class<?> c, String errorMsg, Integer... modifiers) {
try {
for (Integer modifier : modifiers) {
Object[] value = MODIFIER_METHODS.getOrDefault(modifier, null);
if (value == null)
throw new IllegalArgumentException(String.valueOf(modifier));
Method method = (Method) value[0];
String msgSuffix = (String) value[1];
if (arrayContains(modifier, modifiers))
assertTrue((Boolean) method.invoke(null, c.getModifiers()),
errorMsg != null ? errorMsg : c.getName() + " must be " + msgSuffix);
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
/**
* Assert that the given member does not have the specified modifiers.
* <br><br>
* Calls {@link Assertions#assertDoesNotHaveModifiers(Member, String, Integer...)} with {@code null} as error
* message (default error message)
* @param member the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
* @throws IllegalArgumentException if a value does not correspond to a modifier
*/
public static void assertDoesNotHaveModifiers(Member member, Integer... modifiers) {
assertDoesNotHaveModifiers(member, null, modifiers);
}
/**
* Assert that the given member does not have the specified modifiers.
* @param member the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
* @param errorMsg the error message to pass to {@link AssertionError}
* @throws IllegalArgumentException if a value does not correspond to a modifier
*/
public static void assertDoesNotHaveModifiers(Member member, String errorMsg, Integer... modifiers) {
try {
for (Integer modifier : modifiers) {
Object[] value = MODIFIER_METHODS.getOrDefault(modifier, null);
if (value == null)
throw new IllegalArgumentException(String.valueOf(modifier));
Method method = (Method) value[0];
String msgSuffix = (String) value[1];
if (arrayContains(modifier, modifiers))
assertFalse((Boolean) method.invoke(null, member.getModifiers()),
errorMsg != null ? errorMsg : member.getName() + " must not be " + msgSuffix);
}
......@@ -72,4 +159,116 @@ public class Assertions {
e.printStackTrace();
}
}
/**
* Assert that the given class does not have the specified modifiers.
* <br><br>
* Calls {@link Assertions#assertDoesNotHaveModifiers(Class, String, Integer...)} with {@code null} as error
* message (default error message)
* @param c the member to check the modifiers of
* @param modifiers a list of modifiers (constants of {@link Modifier})
*/
public static void assertDoesNotHaveModifiers(Class<?> c, Integer... modifiers) {
assertDoesNotHaveModifiers(c, null, modifiers);
}
/**
* Assert that the given class does not have the specified modifiers.
* @param c the member to check the modifiers of
* @param errorMsg the error message to pass to {@link AssertionError}
* @param modifiers a list of modifiers (constants of {@link Modifier})
*/
public static void assertDoesNotHaveModifiers(Class<?> c, String errorMsg, Integer... modifiers) {
try {
for (Integer modifier : modifiers) {
Object[] value = MODIFIER_METHODS.getOrDefault(modifier, null);
if (value == null)
throw new IllegalArgumentException(String.valueOf(modifier));
Method method = (Method) value[0];
String msgSuffix = (String) value[1];
if (arrayContains(modifier, modifiers))
assertFalse((Boolean) method.invoke(null, c.getModifiers()),
errorMsg != null ? errorMsg : c.getName() + " must not be " + msgSuffix);
}
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
/**
* Assert that the given class is generic and its type parameters have the given bounds
* @param c the class to check
* @param expectedTypeParameters a map of expected type parameters and their bounds
*/
public static void assertIsGeneric(Class<?> c, Map<String, String[]> expectedTypeParameters) {
int i = 0;
TypeVariable<?>[] actualTypeParameters = c.getTypeParameters();
assertNotEquals(0, actualTypeParameters.length, c.getName() + " must be generic");
assertEquals(expectedTypeParameters.size(), actualTypeParameters.length,
c.getName() + " must have exactly " + expectedTypeParameters.size() + " type parameters");
for (Map.Entry<String, String[]> expectedTypeParameter : expectedTypeParameters.entrySet()) {
TypeVariable<?> actualTypeParameter = actualTypeParameters[i++];
assertEquals(expectedTypeParameter.getKey(), actualTypeParameter.getTypeName(), "Unexpected type parameter");
assertEquals(expectedTypeParameter.getValue().length, actualTypeParameter.getBounds().length, "Number of bounds differ");
for (String bound : expectedTypeParameter.getValue())
if (!arrayContains(bound, Arrays.stream(actualTypeParameter.getBounds()).map(Type::getTypeName).toArray()))
throw new AssertionFailedError(
c.getName() + " is missing a required bound for type parameter " + expectedTypeParameter.getKey(),
bound, null);
}
}
/**
* Assert that the given class is not generic
* @param c the class to check
*/
public static void assertNotGeneric(Class<?> c) {
assertEquals(0, c.getTypeParameters().length, c.getName() + " must not be generic");
}
/**
* Assert that the given field has the type {@code typeName}
* @param field the field to check
* @param typeName the type (type name)
*/
public static void assertType(Field field, String typeName) {
assertEquals(typeName, field.getGenericType().getTypeName(), "Type of field " + field.getName() + " is incorrect");
}
/**
* Assert that the given method has the return type {@code typeName}
* @param method the method to check
* @param typeName the return type (type name)
*/
public static void assertReturnType(Method method, String typeName) {
assertEquals(typeName, method.getGenericReturnType().getTypeName(), "Return type of method " + method.getName() + " is incorrect");
}
/**
* Assert that a class implements all given interfaces
* @param c the class to check
* @param interfaces the interfaces the class has to implement (type name)
*/
public static void assertImplements(Class<?> c, String... interfaces) {
List<String> actualInterfaces = Arrays.stream(c.getGenericInterfaces()).map(Type::getTypeName).collect(Collectors.toList());
for (String intf : interfaces)
if (!actualInterfaces.contains(intf))
throw new AssertionFailedError("Required interface not implemented in " + c.getName(), intf, null);
}
private static boolean arrayContains(Object needle, Object[] stack) {
for (Object stackElement : stack)
if (Objects.equals(needle, stackElement))
return true;
return false;
}
}
......@@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import static h05.Assertions.*;
import static h05.Utils.*;
......@@ -20,6 +20,8 @@ public class ListItemTest {
public static Constructor<?> constructor;
public static Field key, next;
private static final String CLASS_NAME = "h05.ListItem";
@BeforeAll
public static void checkClass() {
if (CLASS_CORRECT.containsKey(ListItemTest.class))
......@@ -27,20 +29,19 @@ public class ListItemTest {
listItemClass = getClassForName("h05.ListItem");
listItemClass = getClassForName(CLASS_NAME);
// is generic
assertEquals(1, listItemClass.getTypeParameters().length, "ListItem must be generic");
assertEquals("T", listItemClass.getTypeParameters()[0].getName(), "Type parameter is not named 'T'");
assertIsGeneric(listItemClass, Map.of("T", UNBOUNDED));
// is not abstract
assertFalse(isAbstract(listItemClass.getModifiers()), "ListItem must not be abstract");
assertDoesNotHaveModifiers(listItemClass, ABSTRACT);
// constructors
try {
constructor = listItemClass.getDeclaredConstructor();
} catch (NoSuchMethodException e) {
fail("ListItem must have a parameterless constructor", e);
fail(CLASS_NAME + " must have a parameterless constructor", e);
}
// fields
......@@ -48,17 +49,17 @@ public class ListItemTest {
key = listItemClass.getDeclaredField("key");
next = listItemClass.getDeclaredField("next");
} catch (NoSuchFieldException e) {
fail("ListItem is missing one or more required fields", e);
fail(CLASS_NAME + " is missing one or more required fields", e);
}
key.setAccessible(true);
next.setAccessible(true);
assertHasModifiers(key, List.of(PUBLIC));
assertEquals("T", key.getGenericType().getTypeName(), "Type of field \"key\" is incorrect");
assertHasModifiers(key, PUBLIC);
assertType(key, "T");
assertHasModifiers(next, List.of(PUBLIC));
assertEquals("h05.ListItem<T>", next.getGenericType().getTypeName(), "Type of field \"next\" is incorrect");
assertHasModifiers(next, PUBLIC);
assertType(next, "h05.ListItem<T>");
......
......@@ -9,6 +9,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource;
import java.util.Iterator;
import java.util.NoSuchElementException;
import static h05.Assertions.*;
import static h05.Utils.TestType.Type.CLASS;
import static h05.Utils.*;
import static java.lang.reflect.Modifier.*;
......@@ -32,14 +33,14 @@ public class MyParenthesesTreeIteratorTest {
myParenthesesTreeIteratorClass = getClassForName(CLASS_NAME);
// is not generic
assertEquals(0, myParenthesesTreeIteratorClass.getTypeParameters().length, CLASS_NAME + " must not be generic");
// modifiers
assertDoesNotHaveModifiers(myParenthesesTreeIteratorClass, ABSTRACT);
assertEquals("java.util.Iterator<java.lang.Character>", myParenthesesTreeIteratorClass.getGenericInterfaces()[0].getTypeName(),
CLASS_NAME + " must implement Iterator<Character>");
// is not generic
assertNotGeneric(myParenthesesTreeIteratorClass);
// is not abstract
assertFalse(isAbstract(myParenthesesTreeIteratorClass.getModifiers()), CLASS_NAME + " must not be abstract");
// implements Iterator<Character>
assertImplements(myParenthesesTreeIteratorClass, "java.util.Iterator<java.lang.Character>");
......
......@@ -6,7 +6,6 @@ import org.junit.jupiter.api.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.List;
import static h05.Assertions.*;
import static h05.Utils.*;
......@@ -34,14 +33,12 @@ public class MyTreeNodeTest {
myTreeNodeClass = getClassForName(CLASS_NAME);
// is public
assertTrue(isPublic(myTreeNodeClass.getModifiers()));
// modifiers
assertHasModifiers(myTreeNodeClass, PUBLIC);
assertDoesNotHaveModifiers(myTreeNodeClass, ABSTRACT);
// is not generic
assertEquals(0, myTreeNodeClass.getTypeParameters().length, CLASS_NAME + " must not be generic");
// is not abstract
assertFalse(isAbstract(myTreeNodeClass.getModifiers()), CLASS_NAME + " must not be abstract");
assertNotGeneric(myTreeNodeClass);
// constructors
try {
......@@ -50,7 +47,7 @@ public class MyTreeNodeTest {
fail(CLASS_NAME + " is missing a required constructor", e);
}
assertHasModifiers(constructor, List.of(PUBLIC), "Constructor of " + CLASS_NAME + " must be public");
assertHasModifiers(constructor, "Constructor of " + CLASS_NAME + " must be public", PUBLIC);
// fields
try {
......@@ -60,12 +57,11 @@ public class MyTreeNodeTest {
fail(CLASS_NAME + " is missing one or more required fields", e);
}
assertHasModifiers(nodeID, List.of(PUBLIC, FINAL));
assertEquals(long.class, nodeID.getType(), "nodeID must be of type long");
assertHasModifiers(nodeID, PUBLIC, FINAL);
assertType(nodeID, long.class.getTypeName());
assertHasModifiers(successors, List.of(PUBLIC));
assertEquals("h05.ListItem<h05.MyTreeNode>", successors.getGenericType().getTypeName(),
"successors must be of type ListItem<MyTreeNode>");
assertHasModifiers(successors, PUBLIC);
assertType(successors, "h05.ListItem<h05.MyTreeNode>");
......
......@@ -16,7 +16,6 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
import static h05.Assertions.*;
......@@ -46,15 +45,14 @@ public class MyTreeTest {
myTreeClass = getClassForName(CLASS_NAME);
// modifiers
assertDoesNotHaveModifiers(myTreeClass, ABSTRACT);
// is not generic
assertEquals(0, myTreeClass.getTypeParameters().length, CLASS_NAME + " must not be generic");
assertNotGeneric(myTreeClass);
// implements Iterable<Character>
assertEquals("java.lang.Iterable<java.lang.Character>", myTreeClass.getGenericInterfaces()[0].getTypeName(),
CLASS_NAME + " must implement Iterable<Character>");
// is not abstract
assertFalse(isAbstract(myTreeClass.getModifiers()), CLASS_NAME + " must not be abstract");
assertImplements(myTreeClass, "java.lang.Iterable<java.lang.Character>");
// constructors
try {
......@@ -63,7 +61,7 @@ public class MyTreeTest {
fail(CLASS_NAME + " is missing a required constructor", e);
}
assertHasModifiers(constructor, List.of(PUBLIC), "Constructor of " + CLASS_NAME + " must be public");
assertHasModifiers(constructor, "Constructor of " + CLASS_NAME + " must be public", PUBLIC);
// fields
try {
......@@ -76,11 +74,11 @@ public class MyTreeTest {
root.setAccessible(true);
nextNodeID.setAccessible(true);
assertHasModifiers(root, List.of(PRIVATE));
assertEquals(MyTreeNodeTest.myTreeNodeClass, root.getType(), "root must be of type MyTreeNode");
assertHasModifiers(root, PRIVATE);
assertType(root, MyTreeNodeTest.myTreeNodeClass.getTypeName());
assertHasModifiers(nextNodeID, List.of(PRIVATE, STATIC));
assertEquals(long.class, nextNodeID.getType(), "nextNodeID must be of type long");
assertHasModifiers(nextNodeID, PRIVATE, STATIC);
assertType(nextNodeID, long.class.getTypeName());
// methods
try {
......@@ -92,9 +90,8 @@ public class MyTreeTest {
fail(CLASS_NAME + " is missing one or more required methods", e);
}
assertHasModifiers(buildRecursively, List.of(PRIVATE));
assertEquals(MyTreeNodeTest.myTreeNodeClass, buildRecursively.getReturnType(),
"buildRecursively does not have return type MyTreeNode");
assertHasModifiers(buildRecursively, PRIVATE);
assertReturnType(buildRecursively, MyTreeNodeTest.myTreeNodeClass.getTypeName());
assertTrue(() -> {
boolean hasBadStringOperationException = false, hasIOException = false;
......@@ -107,9 +104,8 @@ public class MyTreeTest {
return hasBadStringOperationException && hasIOException;
}, "buildRecursively does not throw all required potential exceptions");
assertHasModifiers(buildIteratively, List.of(PRIVATE));
assertEquals(MyTreeNodeTest.myTreeNodeClass, buildIteratively.getReturnType(),
"buildIteratively does not have return type MyTreeNode");
assertHasModifiers(buildIteratively, PRIVATE);
assertReturnType(buildIteratively, MyTreeNodeTest.myTreeNodeClass.getTypeName());
assertTrue(() -> {
boolean hasBadStringOperationException = false, hasIOException = false;
......@@ -122,8 +118,8 @@ public class MyTreeTest {
return hasBadStringOperationException && hasIOException;
}, "buildIteratively does not throw all required potential exceptions");
assertHasModifiers(isIsomorphic, List.of(PUBLIC));
assertEquals(boolean.class, isIsomorphic.getReturnType(), "isIsomorphic does not have return type boolean");
assertHasModifiers(isIsomorphic, PUBLIC);
assertReturnType(isIsomorphic, boolean.class.getTypeName());
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment