-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add structural and behavior test files from Artemis (Merge PR #62)
Co-authored-by: Stephan Krusche <s.krusche@tum.de> Co-authored-by: Stefan Klöss-Schuster <stefan.kloess-Schuster@tum.de>
- Loading branch information
Showing
29 changed files
with
2,739 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,5 @@ | |
/reports | ||
!/.settings/org.eclipse.* | ||
!/.settings/eclipse*rules* | ||
/artemis-java-test-sandbox.iml | ||
/.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,5 @@ | |
3=com | ||
4=net | ||
5=ch | ||
6=de.tum | ||
6=info | ||
7=de.tum |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
237 changes: 237 additions & 0 deletions
237
src/main/java/de/tum/in/test/api/structural/AttributeTestProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,237 @@ | ||
package de.tum.in.test.api.structural; | ||
|
||
import static org.junit.jupiter.api.Assertions.fail; | ||
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer; | ||
import static org.junit.jupiter.api.DynamicTest.dynamicTest; | ||
|
||
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; | ||
import org.junit.jupiter.api.DynamicContainer; | ||
import org.junit.jupiter.api.DynamicNode; | ||
|
||
/** | ||
* This test evaluates if the specified attributes in the structure oracle are | ||
* correctly implemented with the expected type, visibility modifiers and | ||
* annotations, based on its definition in the structure oracle (test.json). | ||
* | ||
* @author Stephan Krusche (krusche@in.tum.de) | ||
* @version 5.0 (11.11.2020) | ||
*/ | ||
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 | ||
* 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 | ||
* be generated (which seems to be unlikely) | ||
*/ | ||
protected DynamicContainer generateTestsForAllClasses() throws URISyntaxException { | ||
List<DynamicNode> tests = new ArrayList<>(); | ||
if (structureOracleJSON == null) { | ||
fail("The AttributeTest test can only run if the structural oracle (test.json) is present. If you do not provide it, delete AttributeTest.java!"); | ||
} | ||
for (int i = 0; i < structureOracleJSON.length(); i++) { | ||
JSONObject expectedClassJSON = structureOracleJSON.getJSONObject(i); | ||
// Only test the classes that have attributes defined in the oracle. | ||
if (expectedClassJSON.has(JSON_PROPERTY_CLASS) && (expectedClassJSON.has(JSON_PROPERTY_ATTRIBUTES) | ||
|| expectedClassJSON.has(JSON_PROPERTY_ENUM_VALUES))) { | ||
JSONObject expectedClassPropertiesJSON = expectedClassJSON.getJSONObject(JSON_PROPERTY_CLASS); | ||
String expectedClassName = expectedClassPropertiesJSON.getString(JSON_PROPERTY_NAME); | ||
String expectedPackageName = expectedClassPropertiesJSON.getString(JSON_PROPERTY_PACKAGE); | ||
ExpectedClassStructure expectedClassStructure = new ExpectedClassStructure(expectedClassName, | ||
expectedPackageName, expectedClassJSON); | ||
tests.add(dynamicTest("testAttributes[" + expectedClassName + "]", | ||
() -> testAttributes(expectedClassStructure))); | ||
} | ||
} | ||
if (tests.isEmpty()) { | ||
fail("No tests for attributes available in the structural oracle (test.json). Either provide attributes information or delete AttributeTest.java!"); | ||
} | ||
/* | ||
* Using a custom URI here to workaround surefire rendering the JUnit XML | ||
* without the correct test names. | ||
*/ | ||
return dynamicContainer(getClass().getName(), new URI(getClass().getName()), tests.stream()); | ||
} | ||
|
||
/** | ||
* 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 attributes. | ||
* | ||
* @param expectedClassStructure The class structure that we expect to find and | ||
* test against. | ||
*/ | ||
protected void testAttributes(ExpectedClassStructure expectedClassStructure) { | ||
String expectedClassName = expectedClassStructure.getExpectedClassName(); | ||
Class<?> observedClass = findClassForTestType(expectedClassStructure, "attribute"); | ||
if (observedClass == null) { | ||
fail(THE_CLASS + expectedClassName + " was not found for attribute test"); | ||
return; | ||
} | ||
if (expectedClassStructure.hasProperty(JSON_PROPERTY_ATTRIBUTES)) { | ||
JSONArray expectedAttributes = expectedClassStructure.getPropertyAsJsonArray(JSON_PROPERTY_ATTRIBUTES); | ||
checkAttributes(expectedClassName, observedClass, expectedAttributes); | ||
} | ||
if (expectedClassStructure.hasProperty(JSON_PROPERTY_ENUM_VALUES)) { | ||
JSONArray expectedEnumValues = expectedClassStructure.getPropertyAsJsonArray(JSON_PROPERTY_ENUM_VALUES); | ||
checkEnumValues(expectedClassName, observedClass, expectedEnumValues); | ||
} | ||
} | ||
|
||
/** | ||
* This method checks if a observed class' attributes match the expected ones | ||
* defined in the structure oracle. | ||
* | ||
* @param expectedClassName The simple name of the class, mainly used for error | ||
* messages. | ||
* @param observedClass The class that needs to be checked as a Class | ||
* object. | ||
* @param expectedAttributes The information on the expected attributes | ||
* contained in a JSON array. This information | ||
* consists of the name, the type and the visibility | ||
* modifiers of each attribute. | ||
*/ | ||
protected void checkAttributes(String expectedClassName, Class<?> observedClass, JSONArray expectedAttributes) { | ||
for (int i = 0; i < expectedAttributes.length(); i++) { | ||
JSONObject expectedAttribute = expectedAttributes.getJSONObject(i); | ||
String expectedName = expectedAttribute.getString(JSON_PROPERTY_NAME); | ||
String expectedTypeName = expectedAttribute.getString(JSON_PROPERTY_TYPE); | ||
JSONArray expectedModifiers = getExpectedJsonProperty(expectedAttribute, JSON_PROPERTY_MODIFIERS); | ||
JSONArray expectedAnnotations = getExpectedJsonProperty(expectedAttribute, JSON_PROPERTY_ANNOTATIONS); | ||
|
||
// We check for each expected attribute if the name and the type is right. | ||
var nameIsCorrect = false; | ||
var typeIsCorrect = false; | ||
var modifiersAreCorrect = false; | ||
var annotationsAreCorrect = false; | ||
|
||
for (Field observedAttribute : observedClass.getDeclaredFields()) { | ||
if (expectedName.equals(observedAttribute.getName())) { | ||
nameIsCorrect = true; | ||
typeIsCorrect = checkType(observedAttribute, expectedTypeName); | ||
modifiersAreCorrect = checkModifiers(Modifier.toString(observedAttribute.getModifiers()).split(" "), | ||
expectedModifiers); | ||
annotationsAreCorrect = checkAnnotations(observedAttribute.getAnnotations(), expectedAnnotations); | ||
|
||
// If all are correct, then we found our attribute and we can break the loop | ||
if (typeIsCorrect && modifiersAreCorrect && annotationsAreCorrect) { | ||
break; | ||
} | ||
} | ||
// TODO: we should also take wrong case and typos into account (the else case) | ||
} | ||
checkAttributeCorrectness(nameIsCorrect, typeIsCorrect, modifiersAreCorrect, annotationsAreCorrect, | ||
expectedName, expectedClassName); | ||
} | ||
} | ||
|
||
private static void checkAttributeCorrectness(boolean nameIsCorrect, boolean typeIsCorrect, | ||
boolean modifiersAreCorrect, boolean annotationsAreCorrect, String expectedName, String expectedClassName) { | ||
String expectedAttributeInformation = "the expected attribute '" + expectedName + "' of the class '" | ||
+ expectedClassName + "'"; | ||
if (!nameIsCorrect) { | ||
fail("The name of " + expectedAttributeInformation + " is not implemented as expected."); | ||
} | ||
if (!typeIsCorrect) { | ||
fail("The type of " + expectedAttributeInformation + " is not implemented as expected."); | ||
} | ||
if (!modifiersAreCorrect) { | ||
fail("The modifier(s) (access type, abstract, etc.) of " + expectedAttributeInformation | ||
+ NOT_IMPLEMENTED_AS_EXPECTED); | ||
} | ||
if (!annotationsAreCorrect) { | ||
fail("The annotation(s) of " + expectedAttributeInformation + NOT_IMPLEMENTED_AS_EXPECTED); | ||
} | ||
} | ||
|
||
/** | ||
* This method checks if the observed enum values match the expected ones | ||
* defined in the structure oracle. | ||
* | ||
* @param expectedClassName The simple name of the class, mainly used for error | ||
* messages. | ||
* @param observedClass The enum that needs to be checked as a Class | ||
* object. | ||
* @param expectedEnumValues The information on the expected enum values | ||
* contained in a JSON array. This information | ||
* consists of the name of each enum value. | ||
*/ | ||
protected void checkEnumValues(String expectedClassName, Class<?> observedClass, JSONArray expectedEnumValues) { | ||
Object[] observedEnumValues = observedClass.getEnumConstants(); | ||
if (observedEnumValues == null) { | ||
fail(THE_ENUM + "'" + expectedClassName | ||
+ "' does not contain any enum constants. Make sure to implement them."); | ||
return; | ||
} | ||
if (expectedEnumValues.length() != observedEnumValues.length) { | ||
fail(THE_ENUM + "'" + expectedClassName | ||
+ "' does not contain all the expected enum values. Make sure to implement the missing enums."); | ||
return; | ||
} | ||
for (int i = 0; i < expectedEnumValues.length(); i++) { | ||
String expectedEnumValue = expectedEnumValues.getString(i); | ||
boolean enumValueExists = false; | ||
for (Object observedEnumValue : observedEnumValues) { | ||
if (expectedEnumValue.equals(((Enum<?>) observedEnumValue).name())) { | ||
enumValueExists = true; | ||
break; | ||
} | ||
} | ||
if (!enumValueExists) { | ||
fail(THE_CLASS + "'" + expectedClassName + "' does not include the enum value: " + expectedEnumValue | ||
+ ". Make sure to implement it as expected."); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* 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); | ||
} | ||
} |
Oops, something went wrong.