Skip to content

Commit

Permalink
Merge pull request #10 from MBoegers/1-add-recipe-to-migrate-dataprov…
Browse files Browse the repository at this point in the history
…ider-annotation

1 add recipe to migrate dataprovider annotation
  • Loading branch information
MBoegers authored Apr 26, 2024
2 parents 4c31ebc + 4c9936c commit b803103
Show file tree
Hide file tree
Showing 13 changed files with 930 additions and 101 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

<dependencyManagement>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package io.github.mboegers.openrewrite.testngtojupiter;

import lombok.EqualsAndHashCode;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package io.github.mboegers.openrewrite.testngtojupiter;

import io.github.mboegers.openrewrite.testngtojupiter.helper.AnnotationArguments;
import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotatedMethods;
import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotation;
import org.openrewrite.*;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.java.*;
import org.openrewrite.java.tree.J;
import org.openrewrite.java.tree.JavaCoordinates;
import org.openrewrite.java.tree.JavaType;

import java.util.Optional;
import java.util.Set;

import static java.util.Objects.requireNonNull;

public class MigrateDataProvider extends Recipe {

private static final String DATA_PROVIDER = "org.testng.annotations.DataProvider";
private static final AnnotationMatcher DATA_PROVIDER_MATCHER = new AnnotationMatcher("@" + DATA_PROVIDER);

@Override
public String getDisplayName() {
return "Migrate @DataProvider utilities";
}

@Override
public String getDescription() {
return "Wrap `@DataProvider` methods into a Jupiter parameterized test with MethodSource.";
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new TreeVisitor<>() {
@Override
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx, Cursor parent) {
tree = super.visit(tree, ctx, parent);
// wrap methods
tree = new WrapDataProviderMethod().visit(tree, ctx, parent);
// remove @DataProvider
tree = new RemoveAnnotationVisitor(DATA_PROVIDER_MATCHER).visit(tree, ctx, parent);
// use @MethodeSource and @ParameterizedTest
tree = new UseParameterizedTest().visit(tree, ctx, parent);
tree = new UseMethodSource().visit(tree, ctx, parent);
// remove dataProviderName and dataProviderClass arguments
tree = new RemoveAnnotationAttribute("org.testng.annotations.Test", "dataProvider")
.getVisitor().visit(tree, ctx);
tree = new RemoveAnnotationAttribute("org.testng.annotations.Test", "dataProviderClass")
.getVisitor().visit(tree, ctx);
return tree;
}
};
}

private class WrapDataProviderMethod extends JavaIsoVisitor<ExecutionContext> {

private static final JavaTemplate methodeSourceTemplate = JavaTemplate.builder("""
public static Stream<Arguments> #{}() {
return Arrays.stream(#{}()).map(Arguments::of);
}
""")
.imports("org.junit.jupiter.params.provider.Arguments", "java.util.Arrays", "java.util.stream.Stream")
.contextSensitive()
.javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params"))
.build();

@Override
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, org.openrewrite.ExecutionContext ctx) {
classDecl = super.visitClassDeclaration(classDecl, ctx);

Set<J.MethodDeclaration> dataProviders = FindAnnotatedMethods.find(classDecl, DATA_PROVIDER_MATCHER);

// for each add a Wrapper that translates to Jupiter method source
for (J.MethodDeclaration provider : dataProviders) {
String providerMethodName = provider.getSimpleName();
String providerName = FindAnnotation.find(provider, DATA_PROVIDER_MATCHER).stream().findAny()
.flatMap(j -> AnnotationArguments.extractLiteral(j, "name", String.class))
.orElse(providerMethodName);

classDecl = classDecl.withBody(methodeSourceTemplate.apply(
new Cursor(getCursor(), classDecl.getBody()), classDecl.getBody().getCoordinates().lastStatement(),
providerName, providerMethodName));
}

// add new imports
maybeAddImport("org.junit.jupiter.params.provider.Arguments");
maybeAddImport("java.util.Arrays");
maybeAddImport("java.util.stream.Stream");

return classDecl;
}
}

private class UseParameterizedTest extends JavaIsoVisitor<ExecutionContext> {
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
method = super.visitMethodDeclaration(method, ctx);

// if @ParameterizedTest is used, skip
Optional<J.Annotation> paraeterizedTestAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.ParameterizedTest"));
if (paraeterizedTestAnnotation.isPresent()) {
return method;
}

// if no TestNG @Test present, skip
Optional<J.Annotation> testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test"));
if (testNgAnnotation.isEmpty()) {
return method;
}

// determine if a parameterized test is applicable
Optional<String> dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class);
if (dataProviderMethodName.isEmpty()) {
return method;
}

JavaCoordinates addAnnotationCoordinate = method.getCoordinates().addAnnotation((a, b) -> 1);

method = JavaTemplate
.builder("@ParameterizedTest")
.javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params"))
.imports("org.junit.jupiter.params.ParameterizedTest")
.build()
.apply(getCursor(), addAnnotationCoordinate);

maybeAddImport("org.junit.jupiter.params.ParameterizedTest");

return method;
}
}

class UseMethodSource extends JavaIsoVisitor<ExecutionContext> {
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
method = super.visitMethodDeclaration(method, ctx);

// if @MethodSource is used, skip
Optional<J.Annotation> methodSourceAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.junit.jupiter.params.provider.MethodSource"));
if (methodSourceAnnotation.isPresent()) {
return method;
}

// if no testng annotation is present, skip
Optional<J.Annotation> testNgAnnotation = FindAnnotation.findFirst(method, new AnnotationMatcher("@org.testng.annotations.Test"));
if (testNgAnnotation.isEmpty()) {
return method;
}

// determine Provider name, if not present skip!
Optional<String> dataProviderMethodName = AnnotationArguments.extractLiteral(testNgAnnotation.get(), "dataProvider", String.class);
if (dataProviderMethodName.isEmpty()) {
return method;
}

// determin provider class or use current class as default
String dataProviderClass = AnnotationArguments.extractAssignments(testNgAnnotation.get(), "dataProviderClass").stream()
.findAny()
.map(J.FieldAccess.class::cast)
.map(J.FieldAccess::getTarget)
.map(e -> e.unwrap().getType())
.filter(JavaType.Class.class::isInstance)
.map(JavaType.Class.class::cast)
.map(JavaType.Class::getFullyQualifiedName)
.orElse(requireNonNull(getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class).getType()).getFullyQualifiedName());

// add MethodSource annotation
JavaCoordinates addAnnotationCoordinate = method.getCoordinates().addAnnotation((a, b) -> 1);
method = JavaTemplate
.builder("@MethodSource(\"#{}##{}\")")
.javaParser(JavaParser.fromJavaVersion().classpath("junit-jupiter-params"))
.imports("org.junit.jupiter.params.provider.MethodSource")
.build()
.apply(getCursor(), addAnnotationCoordinate, dataProviderClass, dataProviderMethodName.get());

maybeAddImport("org.junit.jupiter.params.provider.MethodSource");
maybeRemoveImport(dataProviderClass);

return method;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
/*
* Copyright 2015-2024 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package io.github.mboegers.openrewrite.testngtojupiter;

import io.github.mboegers.openrewrite.testngtojupiter.helper.AnnotationArguments;
import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotation;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.*;
import org.openrewrite.java.tree.Expression;
import org.openrewrite.java.tree.J;

import java.time.Duration;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;

@Value
@EqualsAndHashCode(callSuper = false)
Expand Down Expand Up @@ -43,27 +54,11 @@ static class MigrateEnabledArgumentVisitor extends JavaIsoVisitor<ExecutionConte
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
method = super.visitMethodDeclaration(method, ctx);

// return early if not @Test annotation with argument absent present
var testNgAnnotation = method.getLeadingAnnotations().stream()
.filter(TESTNG_TEST_MATCHER::matches)
.findAny();
if (testNgAnnotation.isEmpty()) {
return method;
}

var enabledArgument = testNgAnnotation
.map(J.Annotation::getArguments).orElse(List.of())
.stream()
.filter(this::isEnabledExpression)
.map(J.Assignment.class::cast)
.findAny();
if (enabledArgument.isEmpty()) {
return method;
}

// add @Disables if enabled=false
Boolean isEnabled = (Boolean) ((J.Literal) enabledArgument.get().getAssignment().unwrap()).getValue();
if (Boolean.FALSE.equals(isEnabled)) {
Optional<Boolean> isEnabled = FindAnnotation.find(method, TESTNG_TEST_MATCHER).stream().findAny()
.flatMap(j -> AnnotationArguments.extractLiteral(j, "enabled", Boolean.class));

if (isEnabled.isPresent() && !isEnabled.get()) {
var addAnnotationCoordinate = method.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName));
method = JavaTemplate
.builder("@Disabled")
Expand All @@ -79,10 +74,5 @@ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, Ex

return method;
}

private boolean isEnabledExpression(Expression expr) {
return expr instanceof J.Assignment &&
"enabled".equals(((J.Identifier) ((J.Assignment) expr).getVariable()).getSimpleName());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

package io.github.mboegers.openrewrite.testngtojupiter;

import io.github.mboegers.openrewrite.testngtojupiter.helper.AnnotationArguments;
import io.github.mboegers.openrewrite.testngtojupiter.helper.FindAnnotation;
import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
Expand Down Expand Up @@ -55,20 +57,14 @@ class ReplaceTestAnnotationVisitor extends JavaIsoVisitor<ExecutionContext> {
@Override
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
method = super.visitMethodDeclaration(method, ctx);
var methodAnnotations = method.getLeadingAnnotations();

//return early if no TestNG used or still has arguments
var testNgAnnotation = methodAnnotations.stream()
.filter(TESTNG_TEST_MATCHER::matches)
.findAny();
var testNgAnnotation = FindAnnotation.findFirst(method, TESTNG_TEST_MATCHER);
if (testNgAnnotation.isEmpty()) {
return method;
}

boolean hasArguments = testNgAnnotation
.map(J.Annotation::getArguments)
.map(as -> !as.isEmpty() && as.stream().noneMatch(J.Empty.class::isInstance))
.orElse(false);
boolean hasArguments = AnnotationArguments.hasAny(testNgAnnotation.get());
if (hasArguments) {
return method;
}
Expand Down
Loading

0 comments on commit b803103

Please sign in to comment.