Skip to content

Commit

Permalink
Implement optional tags for modifiers in test.json
Browse files Browse the repository at this point in the history
  • Loading branch information
JohannesStoehr committed Mar 31, 2021
1 parent 1fc4acb commit c541aa7
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ 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 @@ -8,11 +8,9 @@
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.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.json.JSONArray;
import org.json.JSONObject;
Expand Down Expand Up @@ -112,7 +110,7 @@ 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 Down Expand Up @@ -152,18 +150,51 @@ 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 = 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
11 changes: 11 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,6 +21,7 @@ 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";
Expand All @@ -36,6 +37,8 @@ class StructuralTest {
private final String testMethodsSomeEnum = "testMethods()/dynamic-test:#3";
private final String testMethodsSomeAbstractClass = "testMethods()/dynamic-test:#4";



@TestTest
void test_testAttributesSomeInterface() {
tests.assertThatEvents().haveExactly(1, testFailedWith(testAttributesSomeInterface, AssertionFailedError.class,
Expand All @@ -62,6 +65,14 @@ 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
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 c541aa7

Please sign in to comment.