diff --git a/src/main/java/org/philzen/oss/testng/MigrateMismatchedAssertions.java b/src/main/java/org/philzen/oss/testng/MigrateMismatchedAssertions.java new file mode 100644 index 00000000..abd274d6 --- /dev/null +++ b/src/main/java/org/philzen/oss/testng/MigrateMismatchedAssertions.java @@ -0,0 +1,100 @@ +package org.philzen.oss.testng; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.lang.NonNullApi; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.JavaVisitor; +import org.openrewrite.java.search.UsesMethod; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.J; +import org.philzen.oss.utils.JupiterJavaParser; + +import java.util.function.Function; + +@NonNullApi +public class MigrateMismatchedAssertions extends Recipe { + + @Override + public String getDisplayName() { + return "Replace `Assert#assertEquals(actual[], expected[], delta [, message])` for float and double inputs"; + } + + @Override + public String getDescription() { + return "Replaces `org.testng.Assert#assertEquals(actual[], expected[], delta [, message])` with custom `org.junit.jupiter.api.Assertions#assertAll(() -> {})`."; + } + + @Override + public TreeVisitor getVisitor() { + JavaVisitor javaVisitor = new JavaVisitor() { + + final Function before = (type) -> JavaTemplate + .builder("org.testng.Assert.assertEquals(#{actual:anyArray(%s)}, #{expected:anyArray(%s)}, #{delta:any(%s)});".replace("%s", type)) + .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())) + .build(); + + final Function beforeWithMsg = (type) -> JavaTemplate + .builder("org.testng.Assert.assertEquals(#{actual:anyArray(%s)}, #{expected:anyArray(%s)}, #{delta:any(%s)}, #{message:any(java.lang.String)});".replace("%s", type)) + .javaParser(JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath())) + .build(); + + final JavaTemplate after = JavaTemplate + .builder("Assertions.assertAll(()->{\n Assertions.assertEquals(#{expected:anyArray(float)}.length, #{actual:anyArray(float)}.length, \"Arrays don't have the same size.\");\n for (int i = 0; i < #{actual}.length; i++) {\n Assertions.assertEquals(#{expected}[i], #{actual}[i], #{delta:any(float)});\n }\n});") + .imports("org.junit.jupiter.api.Assertions") + .javaParser(JupiterJavaParser.get()).build(); + + final JavaTemplate afterWithMsg = JavaTemplate + .builder("Assertions.assertAll(()->{\n Assertions.assertEquals(#{expected:anyArray(float)}.length, #{actual:anyArray(float)}.length, \"Arrays don't have the same size.\");\n for (int i = 0; i < #{actual}.length; i++) {\n Assertions.assertEquals(#{expected}[i], #{actual}[i], #{delta:any(float)}, #{message:any(String)});\n }\n});") + .imports("org.junit.jupiter.api.Assertions") + .javaParser(JupiterJavaParser.get()).build(); + + @Override + public J visitMethodInvocation(J.MethodInvocation elem, ExecutionContext ctx) { + JavaTemplate.Matcher matcher; + if ((matcher = before.apply("float").matcher(getCursor())).find() + || (matcher = before.apply("double").matcher(getCursor())).find()) + { + imports(); + return after.apply( + getCursor(), + elem.getCoordinates().replace(), + matcher.parameter(1), + matcher.parameter(0), + matcher.parameter(2) + ); + } else if ((matcher = beforeWithMsg.apply("float").matcher(getCursor())).find() + || (matcher = beforeWithMsg.apply("double").matcher(getCursor())).find()) + { + imports(); + return afterWithMsg.apply( + getCursor(), + elem.getCoordinates().replace(), + matcher.parameter(1), + matcher.parameter(0), + matcher.parameter(2), + matcher.parameter(3) + ); + } + + return super.visitMethodInvocation(elem, ctx); + } + + private void imports() { + maybeRemoveImport("org.testng.Assert"); + maybeAddImport("org.junit.jupiter.api.Assertions"); + } + }; + + return Preconditions.check( + Preconditions.and( + new UsesType<>("org.testng.Assert", true), + new UsesMethod<>("org.testng.Assert assertEquals(..)") + ), + javaVisitor + ); + } +} diff --git a/src/main/resources/META-INF/rewrite/rewrite.yml b/src/main/resources/META-INF/rewrite/rewrite.yml index ffad76b3..fed10d41 100644 --- a/src/main/resources/META-INF/rewrite/rewrite.yml +++ b/src/main/resources/META-INF/rewrite/rewrite.yml @@ -9,5 +9,6 @@ preconditions: filePattern: "**/*.java" recipeList: - org.philzen.oss.testng.UpdateTestAnnotationToJunit5 +- org.philzen.oss.testng.MigrateMismatchedAssertions - org.openrewrite.java.testing.junit5.AddMissingNested - io.github.mboegers.openrewrite.testngtojupiter.MigrateAssertionsRecipes diff --git a/src/test/java/org/philzen/oss/research/ApiComparisonTest.java b/src/test/java/org/philzen/oss/research/ApiComparisonTest.java index 9b575a79..97f615c4 100644 --- a/src/test/java/org/philzen/oss/research/ApiComparisonTest.java +++ b/src/test/java/org/philzen/oss/research/ApiComparisonTest.java @@ -55,6 +55,31 @@ class ApiComparisonTest { thisWillFail(() -> Assertions.assertEquals(2d, actual, .999d)); } + @Tag("missing") + @Test void doubleArrayDelta() { + final double[] expected = new double[] {0d, 10d}; + final double[] actual = new double[] {1d, 9d}; + thisWillPass(() -> Assert.assertEquals(actual, expected, 1d)); + // there is no equivalent in Jupiter :/ + + thisWillFail(() -> Assert.assertEquals(actual, expected, .999d)); + // there is no equivalent in Jupiter :/ + + // possible migration equivalent + thisWillPass(() -> Assertions.assertAll(() -> { + Assertions.assertEquals(expected.length, actual.length, "Arrays don't have the same size."); + for (int i = 0; i < actual.length; i++) { + Assertions.assertEquals(expected[i], actual[i], 1d); + } + })); + thisWillFail(() -> Assertions.assertAll(() -> { + Assertions.assertEquals(expected.length, actual.length, "Arrays don't have the same size."); + for (int i = 0; i < actual.length; i++) { + Assertions.assertEquals(expected[i], actual[i], .999d); + } + })); + } + @Test void floatDelta() { final float actual = 1f; @@ -64,6 +89,31 @@ class ApiComparisonTest { thisWillFail(() -> Assert.assertEquals(actual, 2f, .999f)); thisWillFail(() -> Assertions.assertEquals(2f, actual, .999f)); } + + @Tag("missing") + @Test void floatArrayDelta() { + final double[] expected = new double[] {0d, 10f}; + final double[] actual = new double[] {1f, 9f}; + thisWillPass(() -> Assert.assertEquals(actual, expected, 1f)); + // there is no equivalent in Jupiter :/ + + thisWillFail(() -> Assert.assertEquals(actual, expected, .999f)); + // there is no equivalent in Jupiter :/ + + // possible migration equivalent + thisWillPass(() -> Assertions.assertAll(() -> { + Assertions.assertEquals(expected.length, actual.length, "Arrays don't have the same size."); + for (int i = 0; i < actual.length; i++) { + Assertions.assertEquals(expected[i], actual[i], 1f); + } + })); + thisWillFail(() -> Assertions.assertAll(() -> { + Assertions.assertEquals(expected.length, actual.length, "Arrays don't have the same size."); + for (int i = 0; i < actual.length; i++) { + Assertions.assertEquals(expected[i], actual[i], .999f); + } + })); + } } @Nested class assertNotEquals { diff --git a/src/test/java/org/philzen/oss/testng/MigrateMismatchedAssertionsTest.java b/src/test/java/org/philzen/oss/testng/MigrateMismatchedAssertionsTest.java new file mode 100644 index 00000000..627e4f9e --- /dev/null +++ b/src/test/java/org/philzen/oss/testng/MigrateMismatchedAssertionsTest.java @@ -0,0 +1,89 @@ +package org.philzen.oss.testng; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class MigrateMismatchedAssertionsTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new MigrateMismatchedAssertions()); + } + + @ValueSource(strings = {"float[]", "double[]"}) + @ParameterizedTest + void deltaFunctionForArraysIsMigrated(String type) { + //language=java + rewriteRun(java( + """ + import org.testng.Assert; + + class MyTest { + void testMethod() { + %s actual; + %s expected; + + Assert.assertEquals(actual, expected, %s); + } + } + """.formatted(type, type, type.equals("float[]") ? "0.1f" : "0.2d"), + """ + import org.junit.jupiter.api.Assertions; + + class MyTest { + void testMethod() { + %s actual; + %s expected; + + Assertions.assertAll(() -> { + Assertions.assertEquals(expected.length, actual.length, "Arrays don't have the same size."); + for (int i = 0; i < actual.length; i++) { + Assertions.assertEquals(expected[i], actual[i], %s); + } + }); + } + } + """.formatted(type, type, type.equals("float[]") ? "0.1f" : "0.2d") + )); + } + + @ValueSource(strings = {"float[]", "double[]"}) + @ParameterizedTest + void deltaFunctionForArraysIsMigratedWithMessage(String type) { + //language=java + rewriteRun(java( + """ + import org.testng.Assert; + + class MyTest { + void testMethod() { + %s actual; + %s expected; + + Assert.assertEquals(actual, expected, %s, "Those values are way off."); + } + } + """.formatted(type, type, type.equals("float[]") ? "0.1f" : "0.2d"), + """ + import org.junit.jupiter.api.Assertions; + + class MyTest { + void testMethod() { + %s actual; + %s expected; + + Assertions.assertAll(() -> { + Assertions.assertEquals(expected.length, actual.length, "Arrays don't have the same size."); + for (int i = 0; i < actual.length; i++) { + Assertions.assertEquals(expected[i], actual[i], %s, "Those values are way off."); + } + }); + } + } + """.formatted(type, type, type.equals("float[]") ? "0.1f" : "0.2d") + )); + } +}