Skip to content

Commit

Permalink
Implement optional tags for modifiers in test.json (Merge PR #94)
Browse files Browse the repository at this point in the history
This PR also added support for class modifiers.

Co-authored-by: Johannes Stöhr <[email protected]>
  • Loading branch information
MaisiKoleni and JohannesStoehr committed Mar 31, 2021
2 parents eb782b6 + 1999708 commit 51fe465
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ private static void checkBasicClassProperties(String expectedClassName, Class<?>
&& !Modifier.isInterface(observedClass.getModifiers())) {
fail(THE_TYPE + "'" + expectedClassName + "' is not an interface as it is expected.");
}
if (expectedClassPropertiesJSON.has(JSON_PROPERTY_MODIFIERS)) {
JSONArray expectedModifiers = getExpectedJsonProperty(expectedClassPropertiesJSON, JSON_PROPERTY_MODIFIERS);
boolean modifiersAreCorrect = checkModifiers(Modifier.toString(observedClass.getModifiers()).split(" "),
expectedModifiers);
if (!modifiersAreCorrect) {
fail("The modifier(s) (access type, abstract, etc.) of " + expectedClassName
+ NOT_IMPLEMENTED_AS_EXPECTED);
}
}
}

private static boolean checkBooleanOf(JSONObject expectedClassPropertiesJSON, String booleanProperty) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.json.JSONArray;
import org.json.JSONObject;
Expand All @@ -38,7 +41,7 @@
* their access modifiers, annotations and types and the declared enum values of
* an enum</li>
* </ol>
*
*
* All these elements are tests based on the test.json that specifies the
* structural oracle, i.e. how the solution has to look like in terms of
* structural elements. Note: the file test.json can be automatically generated
Expand All @@ -54,7 +57,7 @@
* JUnit) If no attributes and no enums should be tested for correctness, remove
* {@link AttributeTestProvider}, otherwise one test will fail (limitation of
* JUnit)
*
*
* @author Stephan Krusche ([email protected])
* @version 5.0 (11.11.2020)
*/
Expand Down Expand Up @@ -112,7 +115,8 @@ protected static Class<?> findClassForTestType(ExpectedClassStructure expectedCl
fail(classNameScanMessage);
}
try {
return Class.forName(expectedClassStructure.getQualifiedClassName());
return Class.forName(expectedClassStructure.getQualifiedClassName(), false,
StructuralTestProvider.class.getClassLoader());
} catch (@SuppressWarnings("unused") ClassNotFoundException e) {
// Note: this error happens when the ClassNameScanner finds the correct file,
// e.g. 'Course.java', but the class 'Course' was not yet created correctly in
Expand All @@ -125,7 +129,7 @@ protected static Class<?> findClassForTestType(ExpectedClassStructure expectedCl

/**
* get the expected elements or an empty JSON array
*
*
* @param element the class, attribute, method or constructor JSON
* object element
* @param jsonPropertyKey the key used in JSON
Expand All @@ -152,18 +156,54 @@ protected static boolean checkModifiers(String[] observedModifiers, JSONArray ex
if (Arrays.equals(observedModifiers, new String[] { "" }) && expectedModifiers.length() == 0) {
return true;
}

/*
* If the number of the modifiers does not match, then the modifiers per se do
* not match either.
* Otherwise check if all expected necessary modifiers are contained in the
* array of the observed ones and if any forbidden modifiers were used.
*/
if (observedModifiers.length != expectedModifiers.length()) {
return false;
Set<ModifierSpecification> modifierSpecifications = new HashSet<>();
for (int i = 0; i < expectedModifiers.length(); i++) {
modifierSpecifications.add(ModifierSpecification.getModifierForJsonString(expectedModifiers.getString(i)));
}
Set<String> observedModifiersSet = Set.of(observedModifiers);
Set<String> allowedModifiers = modifierSpecifications.stream().map(ModifierSpecification::getModifier)
.collect(Collectors.toSet());
boolean hasAllNecessaryModifiers = modifierSpecifications.stream().filter(ModifierSpecification::isRequired)
.map(ModifierSpecification::getModifier).allMatch(observedModifiersSet::contains);
boolean hasForbiddenModifier = observedModifiersSet.stream()
.anyMatch(modifier -> !allowedModifiers.contains(modifier));

return hasAllNecessaryModifiers && !hasForbiddenModifier;
}

private static final class ModifierSpecification {

private final String modifier;
private final boolean optional;

private ModifierSpecification(String modifier, boolean optional) {
this.modifier = Objects.requireNonNull(modifier);
this.optional = optional;
}

String getModifier() {
return modifier;
}

boolean isRequired() {
return !optional;
}

static ModifierSpecification getModifierForJsonString(String jsonString) {
String[] sections = jsonString.split(":", -1);
if (sections.length == 1) {
return new ModifierSpecification(jsonString, false);
} else if (sections[0].equals("optional")) {
return new ModifierSpecification(sections[1].trim(), true);
} else {
throw new IllegalArgumentException("Invalid entry for modifier: '" + jsonString + "'");
}
}
/*
* Otherwise check if all expected modifiers are contained in the array of the
* observed ones. If at least one isn't, then the modifiers don't match.
*/
return Arrays.asList(observedModifiers).containsAll(expectedModifiers.toList());
}

protected static boolean checkAnnotations(Annotation[] observedAnnotations, JSONArray expectedAnnotations) {
Expand Down
14 changes: 14 additions & 0 deletions src/test/java/de/tum/in/test/api/StructuralTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ class StructuralTest {
private final String testAttributesSomeClass = "testAttributes()/dynamic-test:#2";
private final String testAttributesSomeEnum = "testAttributes()/dynamic-test:#3";
private final String testAttributesSomeAbstractClass = "testAttributes()/dynamic-test:#4";
private final String testAttributesSomeFailingClass = "testAttributes()/dynamic-test:#5";
private final String testClassDoesNotExist = "testClasses()/dynamic-test:#1";
private final String testClassSomeInterface = "testClasses()/dynamic-test:#2";
private final String testClassMisspelledClas = "testClasses()/dynamic-test:#3";
private final String testClassSomeClass = "testClasses()/dynamic-test:#4";
private final String testClassSomeEnum = "testClasses()/dynamic-test:#5";
private final String testClassSomeAbstractClass = "testClasses()/dynamic-test:#6";
private final String testClassMisspelledclass = "testClasses()/dynamic-test:#7";
private final String testClassSomeFailingClass = "testClasses()/dynamic-test:#8";
private final String testConstructorsSomeClass = "testConstructors()/dynamic-test:#1";
private final String testConstructorsSomeEnum = "testConstructors()/dynamic-test:#2";
private final String testConstructorsSomeAbstractClass = "testConstructors()/dynamic-test:#3";
Expand Down Expand Up @@ -62,6 +64,12 @@ void test_testAttributesSomeAbstractClass() {
+ "of the class 'SomeAbstractClass' are not implemented as expected."));
}

@TestTest
void test_testAttributesSomeFailingClass() {
tests.assertThatEvents().haveExactly(1, testFailedWith(testAttributesSomeFailingClass,
IllegalArgumentException.class, "Invalid entry for modifier: 'penguin: final'"));
}

@TestTest
void test_testClassDoesNotExist() {
tests.assertThatEvents().haveExactly(1, testFailedWith(testClassDoesNotExist, AssertionFailedError.class,
Expand Down Expand Up @@ -107,6 +115,12 @@ void test_testClassMisspelledclass() {
+ "Check for wrong upper case / lower case lettering."));
}

@TestTest
void test_testClassSomeFailingClass() {
tests.assertThatEvents().haveExactly(1, testFailedWith(testClassSomeFailingClass, AssertionFailedError.class,
"The modifier(s) (access type, abstract, etc.) of SomeFailingClass are not implemented as expected."));
}

@TestTest
void test_testConstructorsSomeClass() {
tests.assertThatEvents().haveExactly(1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class SomeClass implements SomeInterface {
private String someAttribute;
private Integer anotherAttribute;
private List<Function<? super String, Integer>> doSomethingOperations;
private final int someFinalAttribute = SOME_CONSTANT;

public SomeClass() {
}
Expand Down
20 changes: 18 additions & 2 deletions src/test/resources/de/tum/in/testuser/test.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,16 @@
"type" : "int"
}, {
"name" : "someAttribute",
"modifiers" : [ "private" ],
"modifiers" : [ "private" , "optional: final"],
"type" : "String"
}, {
"name" : "doSomethingOperations",
"modifiers" : [ "private" ],
"type" : "List<Function<? super String, Integer>>"
}, {
"name" : "someFinalAttribute",
"modifiers" : [ "private" , "optional: final"],
"type" : "int"
} ]
}, {
"class" : {
Expand All @@ -100,7 +104,8 @@
"class" : {
"name" : "SomeAbstractClass",
"package" : "de.tum.in.testuser.subject.structural",
"isAbstract" : true
"isAbstract" : true,
"modifiers" : ["public", "abstract"]
},
"methods" : [ {
"name" : "doNothing",
Expand All @@ -122,4 +127,15 @@
"package" : "de.tum.in.testuser.subject.structural",
"isInterface" : true
}
}, {
"class" : {
"name" : "SomeFailingClass",
"package" : "de.tum.in.testuser.subject.structural",
"modifiers" : [ "public", "final" ]
},
"attributes" : [ {
"name" : "SOME_CONSTANT",
"modifiers" : [ "penguin: final" ],
"type" : "int"
} ]
} ]

0 comments on commit 51fe465

Please sign in to comment.