Skip to content

Commit

Permalink
Simple and canonical name support for Structure Test Oracle files (#67)
Browse files Browse the repository at this point in the history
Co-authored-by: Niclas Schümann <[email protected]>
Co-authored-by: Christian Femers <[email protected]>
  • Loading branch information
3 people authored Dec 8, 2020
1 parent 08fa61b commit 3db2afd
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;

import org.json.JSONArray;
import org.json.JSONObject;
Expand All @@ -29,8 +26,6 @@
*/
public abstract class AttributeTestProvider extends StructuralTestProvider {

private static final Pattern PACKAGE_NAME_IN_GENERIC_TYPE = Pattern.compile("(?:[^\\[\\]<>?,\\s.]++\\.)++");

/**
* This method collects the classes in the structure oracle file for which
* attributes are specified. These classes are then transformed into JUnit 5
Expand Down Expand Up @@ -125,7 +120,8 @@ protected void checkAttributes(String expectedClassName, Class<?> observedClass,
for (Field observedAttribute : observedClass.getDeclaredFields()) {
if (expectedName.equals(observedAttribute.getName())) {
nameIsCorrect = true;
typeIsCorrect = checkType(observedAttribute, expectedTypeName);
typeIsCorrect = checkExpectedType(observedAttribute.getType(), observedAttribute.getGenericType(),
expectedTypeName);
modifiersAreCorrect = checkModifiers(Modifier.toString(observedAttribute.getModifiers()).split(" "),
expectedModifiers);
annotationsAreCorrect = checkAnnotations(observedAttribute.getAnnotations(), expectedAnnotations);
Expand Down Expand Up @@ -200,38 +196,4 @@ protected void checkEnumValues(String expectedClassName, Class<?> observedClass,
}
}
}

/**
* This method checks if the type of an observed attribute matches the expected
* one. It first checks if the type of the attribute is a generic one or not. In
* the first case, it sees if the main and the generic types match, otherwise it
* only looks up the simple name of the attribute.
*
* @param observedAttribute The observed attribute we need to check.
* @param expectedTypeName The name of the expected type.
* @return True, if the types match, false otherwise.
*/
protected boolean checkType(Field observedAttribute, String expectedTypeName) {
boolean expectedTypeIsGeneric = expectedTypeName.contains("<") && expectedTypeName.contains(">");
if (expectedTypeIsGeneric) {
boolean mainTypeIsRight;
boolean genericTypeIsRight = false;

String expectedMainTypeName = expectedTypeName.split("<")[0];
String observedMainTypeName = observedAttribute.getType().getSimpleName();
mainTypeIsRight = expectedMainTypeName.equals(observedMainTypeName);

if (observedAttribute.getGenericType() instanceof ParameterizedType) {
Type observedGenericType = observedAttribute.getGenericType();
// this removes all package names, see section 4.5.1 of the JLS
String observedGenericTypeName = PACKAGE_NAME_IN_GENERIC_TYPE.matcher(observedGenericType.toString())
.replaceAll("");
genericTypeIsRight = expectedTypeName.equals(observedGenericTypeName);
}

return mainTypeIsRight && genericTypeIsRight;
}
String observedTypeName = observedAttribute.getType().getSimpleName();
return expectedTypeName.equals(observedTypeName);
}
}
28 changes: 14 additions & 14 deletions src/main/java/de/tum/in/test/api/structural/ClassTestProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
Expand All @@ -21,7 +22,7 @@
* or an interface or an enum and also if the class extends another superclass
* and if it implements the interfaces and annotations, based on its definition
* in the structure oracle (test.json).
*
*
* @author Stephan Krusche ([email protected])
* @version 5.0 (11.11.2020)
*/
Expand All @@ -31,7 +32,7 @@ public abstract class ClassTestProvider extends StructuralTestProvider {
* This method collects the classes in the structure oracle file for which at
* least one class property is specified. These classes are then transformed
* into JUnit 5 dynamic tests.
*
*
* @return A dynamic test container containing the test for each class which is
* then executed by JUnit.
* @throws URISyntaxException an exception if the URI of the class name cannot
Expand Down Expand Up @@ -80,7 +81,7 @@ protected static boolean hasAdditionalProperties(JSONObject jsonObject) {
* This method gets passed the expected class structure generated by the method
* {@link #generateTestsForAllClasses()}, checks if the class is found at all in
* the assignment and then proceeds to check its properties.
*
*
* @param expectedClassStructure The class structure that we expect to find and
* test against.
*/
Expand Down Expand Up @@ -124,10 +125,11 @@ private static void checkSuperclass(String expectedClassName, Class<?> observedC
if (expectedClassPropertiesJSON.has(JSON_PROPERTY_SUPERCLASS)
&& !expectedClassPropertiesJSON.getString(JSON_PROPERTY_SUPERCLASS).equals("Enum")) {
String expectedSuperClassName = expectedClassPropertiesJSON.getString(JSON_PROPERTY_SUPERCLASS);
String actualSuperClassName = observedClass.getSuperclass().getSimpleName();
String failMessage = THE_CLASS + "'" + expectedClassName + "' is not a subclass of the class '"
+ expectedSuperClassName + "' as expected. Implement the class inheritance properly.";
if (!expectedSuperClassName.equals(actualSuperClassName)) {

if (!checkExpectedType(observedClass.getSuperclass(), observedClass.getGenericSuperclass(),
expectedSuperClassName)) {
String failMessage = THE_CLASS + "'" + expectedClassName + "' is not a subclass of the class '"
+ expectedSuperClassName + "' as expected. Implement the class inheritance properly.";
fail(failMessage);
}
}
Expand All @@ -138,16 +140,14 @@ private static void checkInterfaces(String expectedClassName, Class<?> observedC
if (expectedClassPropertiesJSON.has(JSON_PROPERTY_INTERFACES)) {
JSONArray expectedInterfaces = expectedClassPropertiesJSON.getJSONArray(JSON_PROPERTY_INTERFACES);
Class<?>[] observedInterfaces = observedClass.getInterfaces();
Type[] observedGenericInterfaceTypes = observedClass.getGenericInterfaces();
for (int i = 0; i < expectedInterfaces.length(); i++) {
String expectedInterface = expectedInterfaces.getString(i);
boolean implementsInterface = false;
for (Class<?> observedInterface : observedInterfaces) {
/*
* TODO: this does not work with the current implementation of the test oracle
* generator (which does not print the simple but the full qualified name
* including the package)
*/
if (expectedInterface.equals(observedInterface.getSimpleName())) {
for (int j = 0; j < observedInterfaces.length; j++) {
Class<?> observedInterface = observedInterfaces[j];
Type observedGenericInterfaceType = observedGenericInterfaceTypes[j];
if (checkExpectedType(observedInterface, observedGenericInterfaceType, expectedInterface)) {
implementsInterface = true;
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ protected void checkMethods(String expectedClassName, Class<?> observedClass, JS
checks.modifiers = checkModifiers(Modifier.toString(observedMethod.getModifiers()).split(" "),
expectedModifiers);
checks.annotations = checkAnnotations(observedMethod.getAnnotations(), expectedAnnotations);
checks.returnType = expectedReturnType.equals(observedMethod.getReturnType().getSimpleName());
checks.returnType = checkExpectedType(observedMethod.getReturnType(),
observedMethod.getGenericReturnType(), expectedReturnType);

// If all are correct, then we found the desired method and we can break the
// loop
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;

import org.json.JSONArray;
import org.json.JSONObject;
Expand Down Expand Up @@ -81,6 +83,8 @@ public abstract class StructuralTestProvider {

protected static final String NOT_IMPLEMENTED_AS_EXPECTED = " are not implemented as expected.";

private static final Pattern PACKAGE_NAME_IN_GENERIC_TYPE = Pattern.compile("(?:[^\\[\\]<>?,\\s.]++\\.)++");

protected static JSONArray structureOracleJSON;

protected StructuralTestProvider() {
Expand Down Expand Up @@ -186,7 +190,8 @@ protected static boolean checkAnnotations(Annotation[] observedAnnotations, JSON
boolean expectedAnnotationFound = false;
String expectedAnnotationAsString = (String) expectedAnnotation;
for (Annotation observedAnnotation : observedAnnotations) {
if (expectedAnnotationAsString.equals(observedAnnotation.annotationType().getSimpleName())) {
if (checkExpectedType(observedAnnotation.annotationType(), observedAnnotation.annotationType(),
expectedAnnotationAsString)) {
expectedAnnotationFound = true;
break;
}
Expand Down Expand Up @@ -224,8 +229,8 @@ protected static boolean checkParameters(Class<?>[] observedParameters, JSONArra
}
/*
* Create hash tables to store how often a parameter type occurs. Checking the
* occurrences of a certain parameter type is enough, since the parameter names
* or their order are not relevant to us.
* occurrences of a certain parameter type is enough, since the parameter order
* is not relevant to us.
*/
String[] expectedParameterTypeNames = new String[expectedParameters.length()];
for (int i = 0; i < expectedParameters.length(); i++) {
Expand All @@ -235,13 +240,47 @@ protected static boolean checkParameters(Class<?>[] observedParameters, JSONArra

String[] observedParameterTypeNames = new String[observedParameters.length];
for (int i = 0; i < observedParameters.length; i++) {
// TODO: Canonical names should be supported as well.
observedParameterTypeNames[i] = observedParameters[i].getSimpleName();
}
Map<String, Integer> observedParametersHashtable = createParametersHashMap(observedParameterTypeNames);

return expectedParametersHashtable.equals(observedParametersHashtable);
}

/**
* This method checks whether the actual type of any implemented structural
* element matches its expected name.
*
* @param actualClass The class of the structural element
* @param actualGenericType The actual generic type of the structural element
* @param expectedTypeName The expected name, provided as a simple or canonical
* (generic) name.
* @return True if the names match, false if not.
*/
protected static boolean checkExpectedType(Class<?> actualClass, Type actualGenericType, String expectedTypeName) {
boolean expectedTypeIsGeneric = expectedTypeName.contains("<") && expectedTypeName.contains(">");
String actualName;
if (expectedTypeIsGeneric) {
actualName = actualGenericType.getTypeName();
} else {
actualName = actualClass.getCanonicalName();
if (actualName == null) {
actualName = actualClass.getName();
}
}
String actualSimpleName = PACKAGE_NAME_IN_GENERIC_TYPE.matcher(actualName).replaceAll("");
/*
* If the given expected name contains a '.' it can be assumed that it
* represents a full canonical name. If it does not, we can assume it represents
* a simple name.
*/
if (expectedTypeName.contains(".")) {
return expectedTypeName.equals(actualName);
}
return expectedTypeName.equals(actualSimpleName);
}

/**
* This method creates a hash table where the name of a parameter type is hashed
* to the number of the occurrences in the passed string collection.
Expand Down

0 comments on commit 3db2afd

Please sign in to comment.