Skip to content

Commit

Permalink
test(junit4@test): add testcase for junit 4 annotation replacement (#8)
Browse files Browse the repository at this point in the history
— add testcase for junit 4 annotation replacement
— improve code formatting for method call chains
  • Loading branch information
MartinWitt committed Jan 1, 2022
1 parent 5398c88 commit f15cd01
Show file tree
Hide file tree
Showing 18 changed files with 252 additions and 62 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,4 @@ gradle-app.setting
# JDT-specific (Eclipse Java Development Tools)
.classpath

.vscode/settings.json
2 changes: 2 additions & 0 deletions code-transformation/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ dependencies {
implementation 'com.google.flogger:flogger:0.7.4'
implementation 'com.google.flogger:flogger-system-backend:0.7.4'
implementation group: 'ch.qos.logback', name: 'logback-core', version: '1.3.0-alpha12'
testImplementation "com.google.truth:truth:1.1.3"

}

application {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,8 @@
public class TransformationEngine {

private static final FluentLogger LOGGER = FluentLogger.forEnclosingClass();
private TransformationEngine() {

}

public static Changelog applyToGivenPath(String path) {
public Changelog applyToGivenPath(String path) {
LOGGER.atInfo().log("Applying transformations to %s", path);
Launcher launcher = new Launcher();
Environment environment = setEnvironmentOptions(launcher);
Expand All @@ -56,14 +53,14 @@ public static Changelog applyToGivenPath(String path) {
return listener.getChangelog();
}

private static void addProcessors(ProcessingManager pm, ChangeListener listener) {
protected void addProcessors(ProcessingManager pm, ChangeListener listener) {
pm.addProcessor(new AssertThatTransformation(listener));
pm.addProcessor(new TestAnnotation(listener));
pm.addProcessor(new ExpectedExceptionRemoval(listener));
pm.addProcessor(new AssertionsTransformation(listener));
}

public static Changelog applyToGivenPath(String path, String typeName) {
public Changelog applyToGivenPath(String path, String typeName) {
LOGGER.atInfo().log("Applying transformations to %s", path);
Launcher launcher = new Launcher();
Environment environment = setEnvironmentOptions(launcher);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ public static void refactorRepo(String path, String sourceFiles, String file,
git.checkout().setName(config.getGitDefaultBranchName()).call();
git.checkout().setForced(true).setCreateBranch(true).setName(config.getGitBranchPrefix() + file).call();
Changelog changelog = transformation.apply(sourceFiles, file);
git.commit().setAll(true).setAuthor(config.getGitAuthor(), config.getGitEmail()).setMessage(
"refactor(" + file + "): \n " + getRelevantChangeLog(file, changelog)).call();
git.commit()
.setAll(true)
.setAuthor(config.getGitAuthor(), config.getGitEmail())
.setMessage("refactor(" + file + "): \n " + getRelevantChangeLog(file, changelog))
.call();
git.close();
}
catch (IOException | GitAPIException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ public class Context {
if (pckg != null) {
this.packageQName = pckg.getReference().getQualifiedName();
}
this.typeRefQNames = cu.getDeclaredTypeReferences().stream().map(CtTypeReference::getQualifiedName).collect(
Collectors.toSet());
this.typeRefQNames = cu.getDeclaredTypeReferences()
.stream()
.map(CtTypeReference::getQualifiedName)
.collect(Collectors.toSet());
computedImports = new HashMap<>();
}

Expand Down Expand Up @@ -229,8 +231,10 @@ else if (ref instanceof CtTypeReference<?>) {
}

private boolean isReferencePresentInImports(CtReference ref) {
return compilationUnit.getImports().stream().anyMatch(ctImport -> ctImport.getReference() != null
&& isEqualAfterSkippingRole(ctImport.getReference(), ref, CtRole.TYPE_ARGUMENT));
return compilationUnit.getImports()
.stream()
.anyMatch(ctImport -> ctImport.getReference() != null
&& isEqualAfterSkippingRole(ctImport.getReference(), ref, CtRole.TYPE_ARGUMENT));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@

package xyz.keksdose.spoon.code_solver.transformations;

import java.util.ArrayList;
import java.util.List;
import spoon.experimental.CtUnresolvedImport;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtImport;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtAbstractImportVisitor;

public class ImportHelper {
Expand All @@ -25,6 +27,22 @@ public static void addImport(String importString, boolean isStatic, CtCompilatio

public static void removeImport(String importString, boolean isStatic, CtCompilationUnit unit) {
List<CtImport> imports = unit.getImports();
List<CtImport> removalableImports = new ArrayList<>();
for (CtImport ctImport : imports) {
if (ctImport.getReference() instanceof CtTypeReference) {
CtTypeReference<?> typeReference = (CtTypeReference<?>) ctImport.getReference();
if (typeReference.getQualifiedName().equals(importString)) {
removalableImports.add(ctImport);
}
}
}
removalableImports.forEach(imports::remove);
removalableImports.forEach(CtImport::delete);
imports.clear();
unit.setImports(imports);
ImportVisitor visitor = new ImportVisitor(importString);
imports.forEach(i -> i.accept(visitor));
imports.remove(visitor.getResult());
imports.remove(createImport(importString, isStatic, unit));
}

Expand All @@ -51,7 +69,7 @@ public ImportVisitor(String importString) {
}

private String importString;
private CtUnresolvedImport result;
private CtImport result;

// all junit imports are unresolved imports
@Override
Expand All @@ -61,7 +79,7 @@ public <T> void visitUnresolvedImport(CtUnresolvedImport unresolvedImport) {
}
}

public CtUnresolvedImport getResult() {
public CtImport getResult() {
return result;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,40 +24,46 @@ public AssertionsTransformation(ChangeListener listener) {
@Override
public void process(CtMethod<?> method) {
if (JunitHelper.isJunit5TestMethod(method)) {
var junit4Asserts = method.getElements(new TypeFilter<>(CtInvocation.class)).stream().filter(
v -> v.getTarget() != null).filter(v -> v.getTarget().getType() != null).filter(
v -> CtTypeAccess.class.isInstance(v.getTarget())).filter(
v -> ((CtTypeAccess<?>) v.getTarget()).getAccessedType() != null).filter(
v -> ((CtTypeAccess<?>) v.getTarget()).getAccessedType().getQualifiedName().equals(
"org.junit.Assert")).toList();
var junit4Asserts = method.getElements(new TypeFilter<>(CtInvocation.class))
.stream()
.filter(v -> v.getTarget() != null)
.filter(v -> v.getTarget().getType() != null)
.filter(v -> CtTypeAccess.class.isInstance(v.getTarget()))
.filter(v -> ((CtTypeAccess<?>) v.getTarget()).getAccessedType() != null)
.filter(v -> ((CtTypeAccess<?>) v.getTarget()).getAccessedType()
.getQualifiedName()
.equals("org.junit.Assert"))
.toList();
if (!junit4Asserts.isEmpty()) {
junit4Asserts.forEach(v -> v.getExecutable().setDeclaringType(
getFactory().Type().createReference("org.junit.jupiter.api.Assertions")));
CtTypeAccess<?> access = getFactory().createTypeAccess(
getFactory().Type().createReference("org.junit.jupiter.api.Assertions"));
junit4Asserts.forEach(v -> v.getExecutable()
.setDeclaringType(getFactory().Type().createReference("org.junit.jupiter.api.Assertions")));
CtTypeAccess<?> access = getFactory()
.createTypeAccess(getFactory().Type().createReference("org.junit.jupiter.api.Assertions"));
for (CtInvocation<?> junit4Assert : junit4Asserts) {
junit4Assert.setTarget(null);
}
List<CtImport> imports = new ArrayList<>();
List<CtImport> newImports = new ArrayList<>();
method.getPosition().getCompilationUnit().getImports().forEach(
v -> v.accept(new CtAbstractImportVisitor() {
method.getPosition()
.getCompilationUnit()
.getImports()
.forEach(v -> v.accept(new CtAbstractImportVisitor() {

@Override
public <T> void visitUnresolvedImport(CtUnresolvedImport ctUnresolvedImport) {
if (ctUnresolvedImport.getUnresolvedReference().startsWith("org.junit.Assert.")) {
imports.add(ctUnresolvedImport);
newImports.add(getFactory().createUnresolvedImport(
ctUnresolvedImport.getUnresolvedReference().replace("org.junit.Assert.",
"org.junit.jupiter.api.Assertions."),
true));
@Override
public <T> void visitUnresolvedImport(CtUnresolvedImport ctUnresolvedImport) {
if (ctUnresolvedImport.getUnresolvedReference().startsWith("org.junit.Assert.")) {
imports.add(ctUnresolvedImport);
newImports.add(getFactory().createUnresolvedImport(
ctUnresolvedImport.getUnresolvedReference()
.replace("org.junit.Assert.", "org.junit.jupiter.api.Assertions."),
true));
}
}
}
}));
}));
imports.forEach(CtElement::delete);
newImports.stream().filter(
v -> !method.getPosition().getCompilationUnit().getImports().contains(v)).forEach(
method.getPosition().getCompilationUnit().getImports()::add);
newImports.stream()
.filter(v -> !method.getPosition().getCompilationUnit().getImports().contains(v))
.forEach(method.getPosition().getCompilationUnit().getImports()::add);

setChanged(method.getDeclaringType(),
new Change(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,23 @@ public void process(CtMethod<?> method) {
}

private void removeExpectedValue(Optional<CtAnnotation<?>> testAnnotation) {
testAnnotation.get().setValues(
testAnnotation.get().getValues().entrySet().stream().filter(v -> !v.getKey().equals("expected")).collect(
Collectors.toMap(Entry::getKey, Entry::getValue)));
testAnnotation.get()
.setValues(testAnnotation.get()
.getValues()
.entrySet()
.stream()
.filter(v -> !v.getKey().equals("expected"))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue)));
}

private CtInvocation<?> createAssertThrows(CtExpression<?> exceptionClass, CtStatement body) {
CtTypeReference<?> typeRef = getFactory().Type().createReference("org.junit.jupiter.api.Assertions");
CtTypeReference<?> clazzRef = getFactory().Type().createReference("java.lang.Class");
CtTypeReference<?> executableJunit = getFactory().Type().createReference(
"org.junit.jupiter.api.function.Executable");
CtExecutableReference<?> assertThrows = getFactory().Executable().createReference(typeRef,
getFactory().Type().voidType(), "assertThrows", List.of(clazzRef, executableJunit));
CtTypeReference<?> executableJunit = getFactory().Type()
.createReference("org.junit.jupiter.api.function.Executable");
CtExecutableReference<?> assertThrows = getFactory().Executable()
.createReference(typeRef, getFactory().Type().voidType(), "assertThrows",
List.of(clazzRef, executableJunit));
CtLambda<?> lambda = getFactory().createLambda();
lambda.setType((CtTypeReference) getFactory().Type().voidType());
lambda.setBody(body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ public void process(CtAnnotation<?> annotation) {
CtElement element = annotation.getAnnotatedElement();
refactorTimeoutAnnotation(annotation, element);
adjustImports(annotation, element);
annotation.setType(getFactory().createReference("org.junit.jupiter.api.Test"));
annotation.setAnnotationType(getFactory().createReference("org.junit.jupiter.api.Test"));
CtType<?> type = annotation.getParent(CtType.class);
type.getReferencedTypes();
setChanged(type,
new Change(
String.format("Replaced junit 4 test annotation with junit 5 test annotation in %s",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,17 @@ public boolean isApplicable(CtBinaryOperator<?> element) {
private boolean leftHandIsSizeCheck(CtBinaryOperator<?> element) {
CtExpression<?> leftHand = element.getLeftHandOperand();
List<CtInvocation<?>> innvocation = leftHand.getElements(new TypeFilter<>(CtInvocation.class));
innvocation.stream().filter(
outer -> innvocation.stream().anyMatch(inner -> outer.hasParent(inner))).toList().forEach(
innvocation::remove);
innvocation.stream()
.filter(outer -> innvocation.stream().anyMatch(inner -> outer.hasParent(inner)))
.toList()
.forEach(innvocation::remove);
if (innvocation.isEmpty()) {
return false;
}
CtInvocation<?> invocation = innvocation.get(innvocation.size() - 1);
if (invocation.getTarget() != null && invocation.getTarget().getType().isSubtypeOf(
getFactory().Type().createReference(Collection.class))) {
if (invocation.getTarget() != null && invocation.getTarget()
.getType()
.isSubtypeOf(getFactory().Type().createReference(Collection.class))) {
if (invocation.getExecutable().getSimpleName().equals("size")) {
if (invocation.getArguments().isEmpty()) {
return true;
Expand All @@ -57,9 +59,9 @@ public void process(CtBinaryOperator<?> element) {
ref.setDeclaringType(getFactory().createCtTypeReference(Collection.class));
setChanged(element.getParent(CtType.class),
new Change("EmptyCollectionCheck", "EmptyCollectionCheck", element.getParent(CtType.class)));
CtInvocation<Boolean> innvocation = getFactory().Code().createInvocation(
element.getElements(new TypeFilter<>(CtInvocation.class)).get(0).getTarget(), ref,
new ArrayList<CtExpression<?>>());
CtInvocation<Boolean> innvocation = getFactory().Code()
.createInvocation(element.getElements(new TypeFilter<>(CtInvocation.class)).get(0).getTarget(), ref,
new ArrayList<CtExpression<?>>());
element.replace(innvocation);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ public MissingOverride(ChangeListener listener) {
public void process(CtMethod<?> element) {
if (element.getTopDefinitions().size() == 1) {
if (element.getAnnotations().stream().noneMatch(v -> v.getName().equals("Override"))) {
if (element.getTopDefinitions().stream().filter(v -> v.getBody() != null).anyMatch(
v -> !v.getBody().getStatements().isEmpty() || !v.isAbstract())) {
CtAnnotation<?> overrideAnnotation = getFactory().createAnnotation(
getFactory().Type().createReference(Override.class));
if (element.getTopDefinitions()
.stream()
.filter(v -> v.getBody() != null)
.anyMatch(v -> !v.getBody().getStatements().isEmpty() || !v.isAbstract())) {
CtAnnotation<?> overrideAnnotation = getFactory()
.createAnnotation(getFactory().Type().createReference(Override.class));
overrideAnnotation.setComments(element.getComments());
overrideAnnotation.setDocComment(element.getDocComment());
element.setComments(List.of());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ public void process(CtType<?> element) {
return;
}
Set<CtTypeReference<?>> superClass = element.getSuperInterfaces();
superClass.stream().filter(
intrface -> superClass.stream().filter(inner -> inner.isSubtypeOf(intrface)).count() > 1).forEach(
intrface -> {
superClass.stream()
.filter(intrface -> superClass.stream().filter(inner -> inner.isSubtypeOf(intrface)).count() > 1)
.forEach(intrface -> {
element.removeSuperInterface(intrface);
setChanged(element,
new Change(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@
<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" value="insert"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" value="false"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="0"/>
<setting id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" value="80"/>
<setting id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" value="false"/>
<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
<setting id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized" value="do not insert"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

package xyz.keksdose.spoon.code_solver;

import static com.google.common.truth.Truth.assertThat;
import static xyz.keksdose.spoon.code_solver.TestHelper.createProcessorSupplier;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import xyz.keksdose.spoon.code_solver.transformations.junit.TestAnnotation;

class JunitTests {

@Test
void replaceJunit4WithJunit5TestAnnotation(@TempDir File tempRoot) throws IOException {
String fileName = "TestAnnotation.java";
String resourcePath = "projects/junittests/TestAnnotation.java";
File copy = TestHelper.createCopy(tempRoot, resourcePath, fileName);
List<TransformationCreator> transformations = createProcessorSupplier(v -> new TestAnnotation(v));
new TransformationHelper.Builder().path(tempRoot.getAbsolutePath())
.className("TestAnnotation")
.processors(transformations)
.apply();
String result = Files.readString(copy.toPath());
assertThat(result).contains("@Test");
assertThat(result).contains("import org.junit.jupiter.api.Test;");
assertThat(result).doesNotContain("import org.junit.Test;");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@

package xyz.keksdose.spoon.code_solver;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import com.google.common.io.Files;
import xyz.keksdose.spoon.code_solver.history.ChangeListener;
import xyz.keksdose.spoon.code_solver.transformations.TransformationProcessor;

public class TestHelper {

public static void copyFile(File source, File target) throws IOException {
Files.copy(source, target);
}

public static File getResouces(Path path) {
return Path.of("src/test/resources/").resolve(path).toFile();
}

public static File createCopy(File root, String resourcePath, String filename) throws IOException {
File copy = new File(root, "filename");
File source = TestHelper.getResouces(Path.of(resourcePath));
TestHelper.copyFile(source, copy);
return copy;
}

@SafeVarargs
public static List<TransformationCreator> createProcessorSupplier(TransformationCreator... processors) {
List<TransformationCreator> transformations = new ArrayList<>();
for (TransformationCreator processor : processors) {
transformations.add(v -> processor.apply(v));
}
return transformations;
}
}
Loading

0 comments on commit f15cd01

Please sign in to comment.