-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add junit test for text fields check (#12057)
* try implement check * add FieldEditorTextAreaTest for check if FieldEditor handle FieldProperty.MULTILINE_TEXT properly * delete unused code * fix issue 11939: Check FieldEditorFX interface, Check new EditorTextArea creation, Check TextInputControl field * try fixes code style issue * try fixes code style issue * try fixes code style issue use check style plugin * try fixes open rewrite issue * 1. Optimize error msg. 2. Rename the test method and file. 3. Use normal exceptions 4. Remove logger * 1. Add additional Javadoc to indicate potential test fragility 2. Optimize indentation and some comments --------- Co-authored-by: Ziying Ye <u7790708@anu.edu.au>
- Loading branch information
1 parent
ce27eb3
commit 28fb91e
Showing
2 changed files
with
237 additions
and
0 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
236 changes: 236 additions & 0 deletions
236
src/test/java/org/jabref/model/entry/field/FieldEditorsMultilinePropertyTest.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,236 @@ | ||
package org.jabref.model.entry.field; | ||
|
||
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.ArrayList; | ||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Objects; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
import com.github.javaparser.JavaParser; | ||
import com.github.javaparser.ParserConfiguration; | ||
import com.github.javaparser.ast.CompilationUnit; | ||
import com.github.javaparser.ast.ImportDeclaration; | ||
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; | ||
import com.github.javaparser.ast.body.FieldDeclaration; | ||
import com.github.javaparser.ast.body.MethodDeclaration; | ||
import com.github.javaparser.ast.expr.MethodCallExpr; | ||
import com.github.javaparser.ast.expr.ObjectCreationExpr; | ||
import com.github.javaparser.ast.stmt.IfStmt; | ||
import com.github.javaparser.ast.stmt.ReturnStmt; | ||
import org.junit.jupiter.api.BeforeAll; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
||
public class FieldEditorsMultilinePropertyTest { | ||
|
||
private static final Pattern FIELD_PROPERTY_PATTERN = Pattern.compile("fieldProperties\\.contains\\s*\\(\\s*FieldProperty\\.(\\w+)\\s*\\)"); | ||
private static final Pattern STANDARD_FIELD_PATTERN = Pattern.compile("==\\s*StandardField\\.(\\w+)"); | ||
private static final Pattern INTERNAL_FIELD_PATTERN = Pattern.compile("==\\s*InternalField\\.(\\w+)"); | ||
private static JavaParser PARSER; | ||
|
||
@BeforeAll | ||
public static void setUp() { | ||
ParserConfiguration configuration = new ParserConfiguration(); | ||
configuration.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_21); | ||
PARSER = new JavaParser(configuration); | ||
} | ||
|
||
/** | ||
* This test is somewhat fragile, as it depends on the structure of FieldEditors.java. | ||
* If the structure of FieldEditors.java is changed, this test might fail. | ||
* This test performs the following steps: | ||
* 1. Use Java parser to parse FieldEditors.java and check all if statements in the getForField method. | ||
* 2. Match the conditions of if statements to extract the field properties. | ||
* 3. Match the created FieldEditor class name with field properties extracted from step 2. This creates a map where: | ||
* - The key is the file path of the FieldEditor class (for example: ....UrlEditor.java) | ||
* - The value is the list of properties of the FieldEditor class (for example: [FieldProperty.EXTERNAL]) | ||
* 4. For every class in the map, when its properties contain MULTILINE_TEXT, check whether it: | ||
* a) Holds a TextInputControl field | ||
* b) Has an EditorTextArea object creation | ||
*/ | ||
@Test | ||
public void fieldEditorsMatchMultilineProperty() throws Exception { | ||
Map<Path, List<FieldProperty>> result = getEditorsWithPropertiesInFieldEditors(); | ||
for (Map.Entry<Path, List<FieldProperty>> entry : result.entrySet()) { | ||
// Now we have the file path and its properties, going to analyze the target Editor class | ||
Path filePath = entry.getKey(); | ||
List<FieldProperty> properties = entry.getValue(); | ||
CompilationUnit cu = PARSER.parse(filePath) | ||
.getResult() | ||
.orElseThrow(() -> | ||
new NullPointerException("Failed to parse " | ||
+ filePath | ||
+ ", java parser returned null CompilationUnit" | ||
+ ", please check if the file exists")); | ||
|
||
if (!implementedFieldEditorFX(cu)) { | ||
continue; // Make sure the class implements FieldEditorFX interface | ||
} | ||
|
||
if (properties.contains(FieldProperty.MULTILINE_TEXT)) { | ||
// If the editor has MULTILINE_TEXT property, we are going to check if the class holds a `TextInputControl` field | ||
// and have performed Text Area creation | ||
assertTrue(holdTextInputControlField(cu) && hasEditorTextAreaCreationExisted(cu), | ||
"Class " + filePath + " should hold a TextInputControl field and have EditorTextArea creation"); | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Parse FieldEditors.java to get all field editors and their properties in function getForField | ||
* | ||
* @return a map of field editor file path and its properties | ||
*/ | ||
private static Map<Path, List<FieldProperty>> getEditorsWithPropertiesInFieldEditors() throws Exception { | ||
final String filePath = "src/main/java/org/jabref/gui/fieldeditors/FieldEditors.java"; | ||
Map<Path, List<FieldProperty>> result = new HashMap<>(); | ||
CompilationUnit cu = PARSER.parse(Paths.get(filePath)) | ||
.getResult() | ||
.orElseThrow(() -> | ||
new NullPointerException("Failed to parse FieldEditors.java")); | ||
|
||
// Locate getForField method in FieldEditors.java | ||
MethodDeclaration getForFieldCall = cu.findAll(MethodDeclaration.class).stream() | ||
.filter(methodDeclaration -> "getForField".equals(methodDeclaration.getNameAsString())) | ||
.findFirst() | ||
.orElseThrow(() -> new Exception("Failed to find getForField method in FieldEditors.java")); | ||
|
||
// Analyze all if statements in getForField method | ||
getForFieldCall.findAll(IfStmt.class).forEach(ifStmt -> { | ||
String condition = ifStmt.getCondition().toString(); | ||
List<FieldProperty> properties = new ArrayList<>(); | ||
// Match `fieldProperties.contains(FieldProperty.XXX)` | ||
Matcher propertyMatcher = FIELD_PROPERTY_PATTERN.matcher(condition); | ||
while (propertyMatcher.find()) { | ||
String propertyName = propertyMatcher.group(1); | ||
FieldProperty property = FieldProperty.valueOf(propertyName); | ||
properties.add(property); | ||
} | ||
// Match `== StandardField.XXX` | ||
Matcher standardFieldMatcher = STANDARD_FIELD_PATTERN.matcher(condition); | ||
if (standardFieldMatcher.find()) { | ||
String fieldName = standardFieldMatcher.group(1); | ||
StandardField standardField = StandardField.valueOf(fieldName); | ||
properties.addAll(standardField.getProperties()); | ||
} | ||
// Match `== InternalField.XXX` | ||
Matcher internalFieldMatcher = INTERNAL_FIELD_PATTERN.matcher(condition); | ||
if (internalFieldMatcher.find()) { | ||
String fieldName = internalFieldMatcher.group(1); | ||
InternalField internalField = InternalField.valueOf(fieldName); | ||
properties.addAll(internalField.getProperties()); | ||
} | ||
|
||
// Check if the return statement contains an object creation | ||
// If so, extract the created class name and its path | ||
ifStmt.getThenStmt().stream() | ||
.filter(ReturnStmt.class::isInstance) | ||
.map(ReturnStmt.class::cast) | ||
.findFirst() | ||
.flatMap(returnStmt -> | ||
// Try to find the object creation in the return statement | ||
returnStmt.stream() | ||
.filter(ObjectCreationExpr.class::isInstance) | ||
.map(ObjectCreationExpr.class::cast) | ||
.findFirst()).ifPresent(creationExpr -> { | ||
String createdClassName = creationExpr.getTypeAsString().replace("<>", ""); | ||
cu.findAll(ImportDeclaration.class) | ||
.stream() | ||
.filter(importDeclaration -> importDeclaration.getNameAsString().endsWith(createdClassName)) | ||
.findFirst() | ||
.ifPresentOrElse(importDeclaration -> { | ||
String classPath = importDeclaration.getNameAsString(); | ||
Path classFilePath = Paths.get("src/main/java/" + classPath.replace(".", "/") + ".java"); | ||
result.put(classFilePath, properties); | ||
}, () -> { | ||
Path classFilePath = Paths.get("src/main/java/org/jabref/gui/fieldeditors/" + createdClassName + ".java"); | ||
result.put(classFilePath, properties); | ||
}); | ||
}); | ||
}); | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* Check if the class implements FieldEditorFX interface | ||
* | ||
* @param cu CompilationUnit | ||
* @return true if the class implements FieldEditorFX interface | ||
*/ | ||
private static boolean implementedFieldEditorFX(CompilationUnit cu) { | ||
return cu.findAll(ClassOrInterfaceDeclaration.class).stream() | ||
.anyMatch(classDecl -> classDecl.getImplementedTypes().stream() | ||
.anyMatch(type -> Objects.equals("FieldEditorFX", type.getNameAsString()))); | ||
} | ||
|
||
/** | ||
* Check if the class has a new EditorTextArea creation | ||
* | ||
* @param cu CompilationUnit | ||
* @return true if the class has a new EditorTextArea creation | ||
*/ | ||
private static boolean hasEditorTextAreaCreationExisted(CompilationUnit cu) { | ||
return cu.findAll(ObjectCreationExpr.class).stream() | ||
.anyMatch(creation -> Objects.equals("EditorTextArea", creation.getType().toString())); | ||
} | ||
|
||
/** | ||
* Check if the class holds a TextInputControl field | ||
* | ||
* @param cu CompilationUnit | ||
* @return true if the class holds a TextInputControl field | ||
*/ | ||
private static boolean holdTextInputControlField(CompilationUnit cu) { | ||
// Since the class implements FieldEditorFX, we are going to check the first parameter when call | ||
// establishBinding method, which should be a TextInputControl | ||
AtomicBoolean hasTextInputControlField = new AtomicBoolean(false); | ||
cu.findAll(MethodCallExpr.class) | ||
.stream() | ||
.filter(methodCallExpr -> "establishBinding".equals(methodCallExpr.getNameAsString())) | ||
.findFirst() | ||
.ifPresent(methodCallExpr -> { | ||
if (!methodCallExpr.getArguments().isEmpty()) { | ||
String firstArgument = methodCallExpr.getArgument(0).toString(); | ||
cu.findAll(FieldDeclaration.class) | ||
.stream() | ||
.filter(fieldDeclaration -> fieldDeclaration.getVariables().stream() | ||
.anyMatch(variableDeclarator -> variableDeclarator.getNameAsString().equals(firstArgument))) | ||
.findFirst() | ||
.ifPresent(fieldDeclaration -> { | ||
String classType = fieldDeclaration.getElementType().asString(); | ||
if ("TextInputControl".equals(classType)) { | ||
hasTextInputControlField.set(true); | ||
} | ||
}); | ||
} | ||
}); | ||
return hasTextInputControlField.get(); | ||
} | ||
|
||
private static boolean holdEditorTextField(CompilationUnit compilationUnit) { | ||
AtomicBoolean hasEditorTextField = new AtomicBoolean(false); | ||
compilationUnit.findAll(MethodCallExpr.class).stream() | ||
.filter(methodCallExpr -> "establishBinding".equals(methodCallExpr.getNameAsString())) | ||
.findFirst() | ||
.ifPresent(establishBindingCall -> { | ||
String firstArg = establishBindingCall.getArgument(0).toString(); | ||
compilationUnit.findAll(FieldDeclaration.class).stream() | ||
.filter(field -> field.getVariable(0).getNameAsString().equals(firstArg)) | ||
.findFirst() | ||
.ifPresent(fieldDeclaration -> { | ||
String fieldType = fieldDeclaration.getElementType().asString(); | ||
if ("EditorTextField".equals(fieldType)) { | ||
hasEditorTextField.set(true); | ||
} | ||
}); | ||
}); | ||
return hasEditorTextField.get(); | ||
} | ||
} |