("compileJava") {
+ options.release.set(8)
+}
+
dependencies {
// The bom version can also be set to a specific version
// https://github.com/openrewrite/rewrite-recipe-bom/releases
@@ -29,6 +33,7 @@ dependencies {
implementation("org.openrewrite.recipe:rewrite-java-dependencies")
implementation("org.openrewrite:rewrite-yaml")
implementation("org.assertj:assertj-core:3.24.2")
+ runtimeOnly("org.openrewrite:rewrite-java-8")
runtimeOnly("org.openrewrite:rewrite-java-17")
// Refaster style recipes need the rewrite-templating annotation processor and dependency for generated recipes
@@ -44,12 +49,23 @@ dependencies {
runtimeOnly("ch.qos.logback:logback-classic:1.2.+")
// Our recipe converts Guava's `Lists` type
- testRuntimeOnly("com.google.guava:guava:latest.release")
+ testImplementation("com.google.guava:guava:latest.release")
testRuntimeOnly("org.apache.commons:commons-lang3:latest.release")
testRuntimeOnly("org.springframework:spring-core:latest.release")
// Contains the OpenRewriteBestPractices recipe, which you can apply to your recipes
rewrite("org.openrewrite.recipe:rewrite-recommendations:latest.release")
+
+ // ↓ Classpath resource for MigrateTestNg* recipes
+ implementation("org.testng:testng:7.5.1") // 7.5.x is the last Java 8 compatible version: https://github.com/testng-team/testng/issues/2775
+
+ // ↓ also make jupiter available for refaster compilation
+ compileOnly("org.junit.jupiter:junit-jupiter-api:latest.release")
+
+ // ↓ to allow using testing recipes in our recipe list
+ testRuntimeOnly("org.openrewrite.recipe:rewrite-testing-frameworks:latest.release") {
+ exclude("org.testcontainers", "testcontainers")
+ }
}
signing {
diff --git a/docs/NOT-IMPLEMENTED.md b/docs/NOT-IMPLEMENTED.md
new file mode 100644
index 00000000..feaf91de
--- /dev/null
+++ b/docs/NOT-IMPLEMENTED.md
@@ -0,0 +1,157 @@
+The following is a list of `@org.testng.annotations.Test` annotation attributes for which
+there is no clear plan on how to migrate them yet.
+
+Any contributions or suggestions for equivalents in a JUnit5 test setup are welcome.
+
+- ```java
+ /**
+ * The list of groups this method depends on. Every method member of one of these groups is
+ * guaranteed to have been invoked before this method. Furthermore, if any of these methods was
+ * not a SUCCESS, this test method will not be run and will be flagged as a SKIP.
+ *
+ * @return the value
+ */
+ String[] dependsOnGroups() default {};
+ ```
+
+- ```java
+ /**
+ * The list of methods this method depends on. There is no guarantee on the order on which the
+ * methods depended upon will be run, but you are guaranteed that all these methods will be run
+ * before the test method that contains this annotation is run. Furthermore, if any of these
+ * methods was not a SUCCESS, this test method will not be run and will be flagged as a SKIP.
+ *
+ * If some of these methods have been overloaded, all the overloaded versions will be run.
+ *
+ * @return the value
+ */
+ String[] dependsOnMethods() default {};
+ ```
+
+- ```java
+ /**
+ * The maximum number of milliseconds that the total number of invocations on this test method
+ * should take. This annotation will be ignored if the attribute invocationCount is not specified
+ * on this method. If it hasn't returned after this time, it will be marked as a FAIL.
+ *
+ * @return the value (default 0)
+ */
+ long invocationTimeOut() default 0;
+ ```
+
+- ```java
+ /**
+ * The number of times this method should be invoked.
+ *
+ * @return the value (default 1)
+ */
+ int invocationCount() default 1;
+ ```
+
+- ```java
+ /**
+ * The size of the thread pool for this method. The method will be invoked from multiple threads
+ * as specified by invocationCount. Note: this attribute is ignored if invocationCount is not
+ * specified
+ *
+ * @return the value (default 0)
+ */
+ int threadPoolSize() default 0;
+ ```
+
+- ```java
+ /**
+ * The percentage of success expected from this method.
+ *
+ * @return the value (default 100)
+ */
+ int successPercentage() default 100;
+ ```
+
+- ```java
+ /**
+ * If set to true, this test method will always be run even if it depends on a method that failed.
+ * This attribute will be ignored if this test doesn't depend on any method or group.
+ *
+ * @return the value (default false)
+ */
+ boolean alwaysRun() default false;
+ ```
+
+- ```java
+ /**
+ * The name of the suite this test class should be placed in. This attribute is ignore if @Test is
+ * not at the class level.
+ *
+ * @return the value (default empty)
+ */
+ String suiteName() default "";
+ ```
+
+- ```java
+ /**
+ * The name of the test this test class should be placed in. This attribute is ignore if @Test is
+ * not at the class level.
+ *
+ * @return the value (default empty)
+ */
+ String testName() default "";
+ ```
+
+- ```java
+ /**
+ * If set to true, all the methods on this test class are guaranteed to run in the same thread,
+ * even if the tests are currently being run with parallel="true".
+ *
+ *
This attribute can only be used at the class level and will be ignored if used at the method
+ * level.
+ *
+ * @return true if single threaded (default false)
+ */
+ boolean singleThreaded() default false;
+ ```
+
+- ```java
+ /**
+ * The name of the class that should be called to test if the test should be retried.
+ *
+ * @return String The name of the class that will test if a test method should be retried.
+ */
+ Class extends IRetryAnalyzer> retryAnalyzer() default DisabledRetryAnalyzer.class;
+ ```
+
+- ```java
+ /**
+ * If true and invocationCount is specified with a value > 1, then all invocations after a
+ * failure will be marked as a SKIP instead of a FAIL.
+ *
+ * @return the value (default false)
+ */
+ boolean skipFailedInvocations() default false;
+ ```
+
+- ```java
+ /**
+ * If set to true, this test will run even if the methods it depends on are missing or excluded.
+ *
+ * @return the value (default false)
+ */
+ boolean ignoreMissingDependencies() default false;
+ ```
+
+- ```java
+ /**
+ * The scheduling priority. Lower priorities will be scheduled first.
+ *
+ * @return the value (default 0)
+ */
+ int priority() default 0;
+ ```
+
+- ```java
+ /**
+ * @return - An array of {@link CustomAttribute} that represents a set of custom attributes for a
+ * test method.
+ */
+ CustomAttribute[] attributes() default {};
+ ```
diff --git a/pom.xml b/pom.xml
index 27f2b448..ea8c1dbe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,12 +1,16 @@
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
4.0.0
org.philzen.oss
rewrite-testng-to-junit5
0.1-SNAPSHOT
+
+ UTF-8
+
+
org.projectlombok
@@ -49,16 +53,34 @@
rewrite-java-dependencies
+
+ org.openrewrite
+ rewrite-java-8
+ runtime
+
+
org.openrewrite
rewrite-test
test
+
+ org.openrewrite.recipe
+ rewrite-testing-frameworks
+
+
+ org.testcontainers
+ testcontainers
+
+
+ runtime
+
+
org.junit.jupiter
junit-jupiter
- test
+ compile
@@ -88,6 +110,16 @@
6.1.8
test
+
+
+
+ org.testng
+ testng
+
+ 7.5.1
+ compile
+
+
@@ -147,4 +179,39 @@
+
+
+
+ coverage
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.12
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+
+ report
+
+
+ **/MigrateAssertions.*
+
+ XML
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateAssertions.java b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateAssertions.java
new file mode 100644
index 00000000..3822d188
--- /dev/null
+++ b/src/main/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateAssertions.java
@@ -0,0 +1,755 @@
+package io.github.mboegers.openrewrite.testngtojupiter;
+/*
+ * 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
+ */
+
+import com.google.errorprone.refaster.annotation.AfterTemplate;
+import com.google.errorprone.refaster.annotation.BeforeTemplate;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.function.Executable;
+import org.openrewrite.java.template.RecipeDescriptor;
+import org.testng.Assert;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Spliterators;
+import java.util.stream.StreamSupport;
+
+@RecipeDescriptor(
+ name = "Migrate TestNG Asserts to Jupiter",
+ description = "Migrate all TestNG Assertions to JUnit Jupiter Assertions."
+)
+public class MigrateAssertions {
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEquals(?, ?)` for array parameters",
+ description = "Replace `org.testng.Assert#assertEquals(?, ?)` with `org.junit.jupiter.api.Assertions#assertArrayEquals(?, ?)`."
+ )
+ public static class MigrateAssertEqualsArray {
+
+ @BeforeTemplate void before(Object[] actual, Object[] expected) {
+ Assert.assertEquals(actual, expected);
+ }
+
+ @AfterTemplate void after(Object[] actual, Object[] expected) {
+ Assertions.assertArrayEquals(expected, actual);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEquals(?, ?, String)` for Set, Object",
+ description = "Replace `org.testng.Assert#assertEquals(?, ?, String)` with `org.junit.jupiter.api.Assertions#assertEquals(?, ?, String)`."
+ )
+ public static class MigrateAssertEqualsArrayWithMsg {
+
+ @BeforeTemplate void before(Object[] actual, Object[] expected, String msg) {
+ Assert.assertEquals(actual, expected, msg);
+ }
+
+ @AfterTemplate void after(Object[] actual, Object[] expected, String msg) {
+ Assertions.assertArrayEquals(expected, actual, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertEquals(double, double, double)`",
+ description = "Replace `org.testng.Assert#assertEquals(double, double, double)` with `org.junit.jupiter.api.Assertions#assertEquals(double, double, double)`."
+ )
+ public static class MigrateAssertEqualsDoubleDelta {
+
+ @BeforeTemplate void before(double actual, double expected, double delta) {
+ Assert.assertEquals(actual, expected, delta);
+ }
+
+ @AfterTemplate
+ void after(double actual, double expected, double delta) {
+ Assertions.assertEquals(expected, actual, delta);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertEquals(double, double, double, String)`",
+ description = "Replace `org.testng.Assert#assertEquals(double, double, double, String)` with `org.junit.jupiter.api.Assertions#assertEquals(double, double, double, String)`."
+ )
+ public static class MigrateAssertEqualsDoubleDeltaWithMsg {
+
+ @BeforeTemplate void before(double actual, double expected, double delta, String msg) {
+ Assert.assertEquals(actual, expected, delta, msg);
+ }
+
+ @AfterTemplate
+ void after(double actual, double expected, double delta, String msg) {
+ Assertions.assertEquals(expected, actual, delta, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertEquals(float, float, float)`",
+ description = "Replace `org.testng.Assert#assertEquals(float, float, float)` with `org.junit.jupiter.api.Assertions#assertEquals(float, float, float)`."
+ )
+ public static class MigrateAssertEqualsFloatDelta {
+
+ @BeforeTemplate void before(float actual, float expected, float delta) {
+ Assert.assertEquals(actual, expected, delta);
+ }
+
+ @AfterTemplate
+ void after(float actual, float expected, float delta) {
+ Assertions.assertEquals(expected, actual, delta);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertEquals(float, float, float, String)`",
+ description = "Replace `org.testng.Assert#assertEquals(float, float, float, String)` with `org.junit.jupiter.api.Assertions#assertEquals(float, float, float, String)`."
+ )
+ public static class MigrateAssertEqualsFloatDeltaWithMsg {
+
+ @BeforeTemplate void before(float actual, float expected, float delta, String msg) {
+ Assert.assertEquals(actual, expected, delta, msg);
+ }
+
+ @AfterTemplate
+ void after(float actual, float expected, float delta, String msg) {
+ Assertions.assertEquals(expected, actual, delta, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEquals(Iterator>, Iterator>)`",
+ description = "Migrates `org.testng.Assert#assertEquals(Iterator>, Iterator>)` " +
+ "to `org.junit.jupiter.api.Assertions#assertArrayEquals(Object[], Object[])`."
+ )
+ public static class MigrateAssertEqualsIterator {
+
+ @BeforeTemplate void before(Iterator> actual, Iterator> expected) {
+ Assert.assertEquals(actual, expected);
+ }
+
+ @AfterTemplate void after(Iterator> actual, Iterator> expected) {
+ Assertions.assertArrayEquals(
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).toArray(),
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).toArray()
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEquals(Iterator>, Iterator>, String)`",
+ description = "Migrates `org.testng.Assert#assertEquals(Iterator>, Iterator>, String)` " +
+ "to `org.junit.jupiter.api.Assertions#assertArrayEquals(Object[], Object[], String)`."
+ )
+ public static class MigrateAssertEqualsIteratorWithMsg {
+
+ @BeforeTemplate void before(Iterator> actual, Iterator> expected, String msg) {
+ Assert.assertEquals(actual, expected, msg);
+ }
+
+ @AfterTemplate void after(Iterator> actual, Iterator> expected, String msg) {
+ Assertions.assertArrayEquals(
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).toArray(),
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).toArray(),
+ msg
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertEquals(?, ?)` for primitive values, boxed types and other non-array objects",
+ description = "Replace `org.testng.Assert#assertEquals(?, ?)` with `org.junit.jupiter.api.Assertions#assertEquals(?, ?)`."
+ + "Always run *after* `MigrateAssertEqualsArrayRecipe` and `MigrateAssertEqualsIteratorRecipe`."
+ )
+ public static class MigrateAssertEquals {
+
+ @BeforeTemplate void before(Object actual, Object expected) {
+ Assert.assertEquals(actual, expected);
+ }
+
+ @AfterTemplate void after(Object actual, Object expected) {
+ Assertions.assertEquals(expected, actual);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertEquals(?, ?, String)` for primitive values, boxed types and other non-array objects",
+ description = "Replace `org.testng.Assert#assertEquals(?, ?, String)` with `org.junit.jupiter.api.Assertions#assertEquals(?, ?, String)`."
+ + "Always run *after* `MigrateAssertEqualsArrayRecipe` and `MigrateAssertEqualsIteratorRecipe`."
+ )
+ public static class MigrateAssertEqualsWithMsg {
+
+ @BeforeTemplate void before(Object actual, Object expected, String msg) {
+ Assert.assertEquals(actual, expected, msg);
+ }
+
+ @AfterTemplate
+ void after(Object actual, Object expected, String msg) {
+ Assertions.assertEquals(expected, actual, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEqualsNoOrder(Collection, Collection)`",
+ description = "Migrate `org.testng.Assert#assertEqualsNoOrder(Collection, Collection)` " +
+ "to `org.junit.jupiter.api.Assertions#assertArrayEquals(Object[], Object[])`, " +
+ "sorting the collection before applying the assertion."
+ )
+ public static class MigrateAssertEqualsNoOrderCollection {
+
+ @BeforeTemplate void before(Collection> actual, Collection> expected) {
+ Assert.assertEqualsNoOrder(actual, expected);
+ }
+
+ @AfterTemplate void after(Collection> actual, Collection> expected) {
+ Assertions.assertArrayEquals(
+ expected.stream().sorted().toArray(),
+ actual.stream().sorted().toArray()
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEqualsNoOrder(Collection, Collection, String)`",
+ description = "Migrate `org.testng.Assert#assertEqualsNoOrder(Collection, Collection, String)` " +
+ "to `org.junit.jupiter.api.Assertions#assertArrayEquals(Object[], Object[], String)`, " +
+ "sorting the collection before applying the assertion."
+ )
+ public static class MigrateAssertEqualsNoOrderCollectionWithMessage {
+
+ @BeforeTemplate void before(Collection> actual, Collection> expected, String message) {
+ Assert.assertEqualsNoOrder(actual, expected, message);
+ }
+
+ @AfterTemplate void after(Collection> actual, Collection> expected, String message) {
+ Assertions.assertArrayEquals(
+ expected.stream().sorted().toArray(),
+ actual.stream().sorted().toArray(),
+ message
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEqualsNoOrder(Object[], Object[])`",
+ description = "Migrate `org.testng.Assert#assertEqualsNoOrder(Object[], Object[])` " +
+ "to `org.junit.jupiter.api.Assertions#assertArrayEquals(Object[], Object[])`, " +
+ "sorting the arrays before applying the assertion."
+ )
+ public static class MigrateAssertEqualsNoOrderArray {
+
+ @BeforeTemplate void before(Object[] actual, Object[] expected) {
+ Assert.assertEqualsNoOrder(actual, expected);
+ }
+
+ @AfterTemplate void after(Object[] actual, Object[] expected) {
+ Assertions.assertArrayEquals(
+ Arrays.stream(expected).sorted().toArray(),
+ Arrays.stream(actual).sorted().toArray()
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEqualsNoOrder(Object[], Object[], String)`",
+ description = "Migrate `org.testng.Assert#assertEqualsNoOrder(Object[], Object[], String)` " +
+ "to `org.junit.jupiter.api.Assertions#assertArrayEquals(Object[], Object[], String)`, " +
+ "sorting the collection before applying the assertion."
+ )
+ public static class MigrateAssertEqualsNoOrderArrayWithMessage {
+
+ @BeforeTemplate void before(Object[] actual, Object[] expected, String message) {
+ Assert.assertEqualsNoOrder(actual, expected, message);
+ }
+
+ @AfterTemplate void after(Object[] actual, Object[] expected, String message) {
+ Assertions.assertArrayEquals(
+ Arrays.stream(expected).sorted().toArray(),
+ Arrays.stream(actual).sorted().toArray(),
+ message
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEqualsNoOrder(Iterator>, Iterator>)`",
+ description = "Migrate `org.testng.Assert#assertEqualsNoOrder(Iterator>, Iterator>)` " +
+ "to `org.junit.jupiter.api.Assertions#assertArrayEquals(Object[], Object[])`, " +
+ "sorting the arrays before applying the assertion."
+ )
+ public static class MigrateAssertEqualsNoOrderIterator {
+
+ @BeforeTemplate void before(Iterator> actual, Iterator> expected) {
+ Assert.assertEqualsNoOrder(actual, expected);
+ }
+
+ @AfterTemplate void after(Iterator> actual, Iterator> expected) {
+ Assertions.assertArrayEquals(
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).sorted().toArray(),
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).sorted().toArray()
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEqualsNoOrder(Iterator>, Iterator>, String)`",
+ description = "Migrate `org.testng.Assert#assertEqualsNoOrder(Iterator>, Iterator>, String)` " +
+ "to `org.junit.jupiter.api.Assertions#assertArrayEquals(Object[], Object[], String)`, " +
+ "sorting the arrays before applying the assertion."
+ )
+ public static class MigrateAssertEqualsNoOrderIteratorWithMessage {
+
+ @BeforeTemplate void before(Iterator> actual, Iterator> expected, String message) {
+ Assert.assertEqualsNoOrder(actual, expected, message);
+ }
+
+ @AfterTemplate void after(Iterator> actual, Iterator> expected, String message) {
+ Assertions.assertArrayEquals(
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).sorted().toArray(),
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).sorted().toArray(),
+ message
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotEquals(Object[], Object[])`",
+ description = "Replace `org.testng.Assert#assertNotEquals(Object[], Object[])` with `org.junit.jupiter.api.Assertions#assertNotEquals(Arrays.toString(Object[], Arrays.toString(Object[]))`."
+ )
+ public static class MigrateAssertNotEqualsArray {
+
+ @BeforeTemplate void before(Object[] actual, Object[] expected) {
+ Assert.assertNotEquals(actual, expected);
+ }
+
+ @AfterTemplate void after(Object[] actual, Object[] expected) {
+ Assertions.assertNotEquals(Arrays.toString(expected), Arrays.toString(actual));
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotEquals(Object[], Object[], String)`",
+ description = "Replace `org.testng.Assert#assertNotEquals(Object[], Object[])` with `org.junit.jupiter.api.Assertions#assertNotEquals(Arrays.toString(Object[], Arrays.toString(Object[]), String)`."
+ )
+ public static class MigrateAssertNotEqualsArrayWithMsg {
+
+ @BeforeTemplate void before(Object[] actual, Object[] expected, String message) {
+ Assert.assertNotEquals(actual, expected, message);
+ }
+
+ @AfterTemplate void after(Object[] actual, Object[] expected, String message) {
+ Assertions.assertNotEquals(Arrays.toString(expected), Arrays.toString(actual), message);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotEquals(double, double, double)`",
+ description = "Replace `org.testng.Assert#assertNotEquals(double, double, double)` with `org.junit.jupiter.api.Assertions#assertNotEquals(double, double, double)`."
+ )
+ public static class MigrateAssertNotEqualsDoubleDelta {
+
+ @BeforeTemplate void before(double actual, double expected, double delta) {
+ Assert.assertNotEquals(actual, expected, delta);
+ }
+
+ @AfterTemplate
+ void after(double actual, double expected, double delta) {
+ Assertions.assertNotEquals(expected, actual, delta);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotEquals(double, double, double, String)`",
+ description = "Replace `org.testng.Assert#assertNotEquals(double, double, double, String)` with `org.junit.jupiter.api.Assertions#assertNotEquals(double, double, double, String)`."
+ )
+ public static class MigrateAssertNotEqualsDoubleDeltaWithMsg {
+
+ @BeforeTemplate void before(double actual, double expected, double delta, String msg) {
+ Assert.assertNotEquals(actual, expected, delta, msg);
+ }
+
+ @AfterTemplate
+ void after(double actual, double expected, double delta, String msg) {
+ Assertions.assertNotEquals(expected, actual, delta, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotEquals(float, float, float)`",
+ description = "Replace `org.testng.Assert#assertNotEquals(float, float, float)` with `org.junit.jupiter.api.Assertions#assertNotEquals(float, float, float)`."
+ )
+ public static class MigrateAssertNotEqualsFloatDelta {
+
+ @BeforeTemplate void before(float actual, float expected, float delta) {
+ Assert.assertNotEquals(actual, expected, delta);
+ }
+
+ @AfterTemplate
+ void after(float actual, float expected, float delta) {
+ Assertions.assertNotEquals(expected, actual, delta);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotEquals(float, float, float, String)`",
+ description = "Replace `org.testng.Assert#assertNotEquals(float, float, float, String)` with `org.junit.jupiter.api.Assertions#assertNotEquals(float, float, float, String)`."
+ )
+ public static class MigrateAssertNotEqualsFloatDeltaWithMsg {
+
+ @BeforeTemplate void before(float actual, float expected, float delta, String msg) {
+ Assert.assertNotEquals(actual, expected, delta, msg);
+ }
+
+ @AfterTemplate
+ void after(float actual, float expected, float delta, String msg) {
+ Assertions.assertNotEquals(expected, actual, delta, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertNotEquals(Iterator>, Iterator>)`",
+ description = "Migrates `org.testng.Assert#assertNotEquals(Iterator>, Iterator>)` " +
+ "to `org.junit.jupiter.api.Assertions#assertNotEquals(String, String)` using `Arrays.toString()`."
+ )
+ public static class MigrateAssertNotEqualsIterator {
+
+ @BeforeTemplate void before(Iterator> actual, Iterator> expected) {
+ Assert.assertNotEquals(actual, expected);
+ }
+
+ @AfterTemplate void after(Iterator> actual, Iterator> expected) {
+ Assertions.assertNotEquals(
+ Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).toArray()),
+ Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).toArray())
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertEquals(Iterator>, Iterator>, String)`",
+ description = "Migrates `org.testng.Assert#assertEquals(Iterator>, Iterator>, String)` " +
+ "to `org.junit.jupiter.api.Assertions#assertArrayEquals(Object[], Object[], String)` using `Arrays.toString()`."
+ )
+ public static class MigrateAssertNotEqualsIteratorWithMsg {
+
+ @BeforeTemplate void before(Iterator> actual, Iterator> expected, String msg) {
+ Assert.assertNotEquals(actual, expected, msg);
+ }
+
+ @AfterTemplate void after(Iterator> actual, Iterator> expected, String msg) {
+ Assertions.assertNotEquals(
+ Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).toArray()),
+ Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).toArray()),
+ msg
+ );
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotEquals(?, ?)`",
+ description = "Replace `org.testng.Assert#assertNotEquals(?, ?)` with `org.junit.jupiter.api.Assertions#assertNotEquals(?, ?)`."
+ + "Always run *after* `MigrateAssertNotEqualsArrayRecipe` and `MigrateAssertNotEqualsIteratorRecipe`."
+ )
+ public static class MigrateAssertNotEquals {
+
+ @BeforeTemplate void before(Object actual, Object expected) {
+ Assert.assertNotEquals(actual, expected);
+ }
+
+ @AfterTemplate void after(Object actual, Object expected) {
+ Assertions.assertNotEquals(expected, actual);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotEquals(?, ?, String)`",
+ description = "Replace `org.testng.Assert#assertNotEquals(?, ?, String)` with `org.junit.jupiter.api.Assertions#assertNotEquals(?, ?, String)`."
+ + "Always run *after* `MigrateAssertNotEqualsArrayWithMsgRecipe` and `MigrateAssertNotEqualsIteratorWithMsgRecipe`."
+ )
+ public static class MigrateAssertNotEqualsWithMsg {
+
+ @BeforeTemplate void before(Object actual, Object expected, String msg) {
+ Assert.assertNotEquals(actual, expected, msg);
+ }
+
+ @AfterTemplate void after(Object actual, Object expected, String msg) {
+ Assertions.assertNotEquals(expected, actual, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertNotSame(Object, Object)`",
+ description = "Migrates `org.testng.Assert#assertNotSame(Object, Object)` to `org.junit.jupiter.api.Assertions#assertNotSame(Object, Object)`."
+ )
+ public static class MigrateAssertNotSame {
+
+ @BeforeTemplate void before(Object actual, Object expected) {
+ Assert.assertNotSame(actual, expected);
+ }
+
+ @AfterTemplate void after(Object actual, Object expected) {
+ Assertions.assertNotSame(expected, actual);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertNotSame(Object, Object, String)`",
+ description = "Migrates `org.testng.Assert#assertNotSame(Object, Object, String)` to `org.junit.jupiter.api.Assertions#assertNotSame(Object, Object, String)`."
+ )
+ public static class MigrateAssertNotSameWithMsg {
+
+ @BeforeTemplate void before(Object actual, Object expected, String msg) {
+ Assert.assertNotSame(actual, expected, msg);
+ }
+
+ @AfterTemplate void after(Object actual, Object expected, String msg) {
+ Assertions.assertNotSame(expected, actual, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertFalse(boolean)`",
+ description = "Replace `org.testng.Assert#assertFalse(boolean)` with `org.junit.jupiter.api.Assertions#assertFalse(boolean)`."
+ )
+ public static class MigrateAssertFalse {
+
+ @BeforeTemplate
+ void before(boolean expr) {
+ Assert.assertFalse(expr);
+ }
+
+ @AfterTemplate
+ void after(boolean expr) {
+ Assertions.assertFalse(expr);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertFalse(boolean, String)`",
+ description = "Replace `org.testng.Assert#assertFalse(boolean, String)` with `org.junit.jupiter.api.Assertions#assertFalse(boolean, String)`."
+ )
+ public static class MigrateAssertFalseWithMsg {
+
+ @BeforeTemplate
+ void before(boolean expr, String msg) {
+ Assert.assertFalse(expr, msg);
+ }
+
+ @AfterTemplate
+ void after(boolean expr, String msg) {
+ Assertions.assertFalse(expr, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertTrue(boolean)`",
+ description = "Replace `org.testng.Assert#assertTrue(boolean)` with `org.junit.jupiter.api.Assertions#assertTrue(boolean)`."
+ )
+ public static class MigrateAssertTrue {
+
+ @BeforeTemplate
+ void before(boolean expr) {
+ Assert.assertTrue(expr);
+ }
+
+ @AfterTemplate
+ void after(boolean expr) {
+ Assertions.assertTrue(expr);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertTrue(boolean, String)`",
+ description = "Replace `org.testng.Assert#assertTrue(boolean, String)` with `org.junit.jupiter.api.Assertions#assertTrue(boolean, String)`."
+ )
+ public static class MigrateAssertTrueWithMsg {
+
+ @BeforeTemplate
+ void before(boolean expr, String msg) {
+ Assert.assertTrue(expr, msg);
+ }
+
+ @AfterTemplate
+ void after(boolean expr, String msg) {
+ Assertions.assertTrue(expr, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNull(Object)`",
+ description = "Replace `org.testng.Assert#assertNull(Object)` with `org.junit.jupiter.api.Assertions#assertNull(Object)`."
+ )
+ public static class MigrateAssertNull {
+
+ @BeforeTemplate void before(Object expr) {
+ Assert.assertNull(expr);
+ }
+
+ @AfterTemplate void after(Object expr) {
+ Assertions.assertNull(expr);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNull(Object, String)`",
+ description = "Replace `org.testng.Assert#assertNull(Object, String)` with `org.junit.jupiter.api.Assertions#assertNull(Object, String)`."
+ )
+ public static class MigrateAssertNullWithMsg {
+
+ @BeforeTemplate void before(Object expr, String msg) {
+ Assert.assertNull(expr, msg);
+ }
+
+ @AfterTemplate void after(Object expr, String msg) {
+ Assertions.assertNull(expr, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotNull(Object)`",
+ description = "Replace `org.testng.Assert#assertNotNull(Object)` with `org.junit.jupiter.api.Assertions#assertNotNull(Object)`."
+ )
+ public static class MigrateAssertNotNull {
+
+ @BeforeTemplate void before(Object expr) {
+ Assert.assertNotNull(expr);
+ }
+
+ @AfterTemplate void after(Object expr) {
+ Assertions.assertNotNull(expr);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#assertNotNull(Object, String)`",
+ description = "Replace `org.testng.Assert#assertNotNull(Object, String)` with `org.junit.jupiter.api.Assertions#assertNotNull(Object, String)`."
+ )
+ public static class MigrateAssertNotNullWithMsg {
+
+ @BeforeTemplate void before(Object expr, String msg) {
+ Assert.assertNotNull(expr, msg);
+ }
+
+ @AfterTemplate void after(Object expr, String msg) {
+ Assertions.assertNotNull(expr, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertSame(Object, Object)`",
+ description = "Migrates `org.testng.Assert#assertSame(Object, Object)` to `org.junit.jupiter.api.Assertions#assertSame(Object, Object)`."
+ )
+ public static class MigrateAssertSame {
+
+ @BeforeTemplate void before(Object actual, Object expected) {
+ Assert.assertSame(actual, expected);
+ }
+
+ @AfterTemplate void after(Object actual, Object expected) {
+ Assertions.assertSame(expected, actual);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertSame(Object, Object, String)`",
+ description = "Migrates `org.testng.Assert#assertSame(Object, Object, String)` to `org.junit.jupiter.api.Assertions#assertSame(Object, Object, String)`."
+ )
+ public static class MigrateAssertSameWithMsg {
+
+ @BeforeTemplate void before(Object actual, Object expected, String msg) {
+ Assert.assertSame(actual, expected, msg);
+ }
+
+ @AfterTemplate void after(Object actual, Object expected, String msg) {
+ Assertions.assertSame(expected, actual, msg);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertThrows(Assert.ThrowingRunnable)",
+ description = "Migrates `org.testng.Assert#assertThrows(Assert.ThrowingRunnable)` to "
+ + "`org.junit.jupiter.api.Assertions#Assertions.assertThrows(Throwable.class, org.junit.jupiter.api.function.Executable)`."
+ )
+ public static class MigrateAssertThrows {
+
+ @BeforeTemplate void before(Assert.ThrowingRunnable runnable) {
+ Assert.assertThrows(runnable);
+ }
+
+ @AfterTemplate void after(Executable executable) {
+ Assertions.assertThrows(Throwable.class, executable);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Migrate `Assert#assertThrows(Class extends Throwable>, Assert.ThrowingRunnable) "
+ + "and `Assert#expectThrows(Class extends Throwable>, Assert.ThrowingRunnable)",
+ description = "Migrates `org.testng.Assert#assertThrows(Class extends Throwable>, Assert.ThrowingRunnable)` "
+ + "and `Assert#expectThrows(Class extends Throwable>, Assert.ThrowingRunnable) "
+ + "to `org.junit.jupiter.api.Assertions#Assertions.assertThrows(Class, org.junit.jupiter.api.function.Executable)`."
+ )
+ public static class MigrateAssertThrowsWithExpectedThrowableType {
+
+ @BeforeTemplate void assertThrows(Class extends Throwable> throwableClass, Assert.ThrowingRunnable runnable) {
+ Assert.assertThrows(throwableClass, runnable);
+ }
+
+ @BeforeTemplate void expectThrows(Class extends Throwable> throwableClass, Assert.ThrowingRunnable runnable) {
+ Assert.expectThrows(throwableClass, runnable);
+ }
+
+ @AfterTemplate void after(Class extends Throwable> throwableClass, Executable executable) {
+ Assertions.assertThrows(throwableClass, executable);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#fail()`",
+ description = "Replace `org.testng.Assert#fail()` with `org.junit.jupiter.api.Assertions#fail()`."
+ )
+ public static class MigrateFailNoArgs {
+
+ @BeforeTemplate void before() {
+ Assert.fail();
+ }
+
+ @AfterTemplate void after() {
+ Assertions.fail();
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#fail(String)`",
+ description = "Replace `org.testng.Assert#fail(String)` with `org.junit.jupiter.api.Assertions#fail(String)`."
+ )
+ public static class MigrateFailWithMessage {
+
+ @BeforeTemplate void before(String message) {
+ Assert.fail(message);
+ }
+
+ @AfterTemplate void after(String message) {
+ Assertions.fail(message);
+ }
+ }
+
+ @RecipeDescriptor(
+ name = "Replace `Assert#fail(String)`",
+ description = "Replace `org.testng.Assert#fail(String)` with `org.junit.jupiter.api.Assertions#fail(String)`."
+ )
+ public static class MigrateFailWithMessageAndCause {
+
+ @BeforeTemplate void before(String message, Throwable cause) {
+ Assert.fail(message, cause);
+ }
+
+ @AfterTemplate void after(String message, Throwable cause) {
+ Assertions.fail(message, cause);
+ }
+ }
+}
diff --git a/src/main/java/org/philzen/oss/AppendToReleaseNotes.java b/src/main/java/org/philzen/oss/AppendToReleaseNotes.java
deleted file mode 100644
index 623d4e1a..00000000
--- a/src/main/java/org/philzen/oss/AppendToReleaseNotes.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import lombok.EqualsAndHashCode;
-import lombok.Value;
-import org.openrewrite.*;
-import org.openrewrite.internal.lang.Nullable;
-import org.openrewrite.text.PlainText;
-import org.openrewrite.text.PlainTextParser;
-import org.openrewrite.text.PlainTextVisitor;
-
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.stream.Collectors;
-
-@Value
-@EqualsAndHashCode(callSuper = false)
-public class AppendToReleaseNotes extends ScanningRecipe {
-
- @Override
- public String getDisplayName() {
- return "Append to release notes";
- }
-
- @Override
- public String getDescription() {
- return "Adds the specified line to RELEASE.md.";
- }
-
- @Option(displayName = "Message",
- description = "Message to append to the bottom of RELEASE.md.",
- example = "## 1.0.0\n\n- New feature")
- String message;
-
- // The shared state between the scanner and the visitor. The custom class ensures we can easily extend the recipe.
- public static class Accumulator {
- boolean found;
- }
-
- @Override
- public Accumulator getInitialValue(ExecutionContext ctx) {
- return new Accumulator();
- }
-
- @Override
- public TreeVisitor, ExecutionContext> getScanner(Accumulator acc) {
- return new TreeVisitor() {
- @Override
- public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
- if (tree instanceof SourceFile) {
- Path sourcePath = ((SourceFile) tree).getSourcePath();
- acc.found |= "RELEASE.md".equals(sourcePath.toString());
- }
- return tree;
- }
- };
- }
-
- @Override
- public Collection extends SourceFile> generate(Accumulator acc, ExecutionContext ctx) {
- if (acc.found) {
- return Collections.emptyList();
- }
- // If the file was not found, create it
- return PlainTextParser.builder().build()
- // We start with an empty string that we then append to in the visitor
- .parse("")
- // Be sure to set the source path for any generated file, so that the visitor can find it
- .map(it -> (SourceFile) it.withSourcePath(Paths.get("RELEASE.md")))
- .collect(Collectors.toList());
- }
-
- @Override
- public TreeVisitor, ExecutionContext> getVisitor(Accumulator acc) {
- return new PlainTextVisitor() {
- @Override
- public PlainText visitText(PlainText text, ExecutionContext ctx) {
- PlainText t = super.visitText(text, ctx);
- // If the file is not RELEASE.md, don't modify it
- if (!"RELEASE.md".equals(t.getSourcePath().toString())) {
- return t;
- }
- // If the file already contains the message, don't append it again
- if (t.getText().contains(message)) {
- return t;
- }
- // Append the message to the end of the file
- return t.withText(t.getText() + "\n" + message);
- }
- };
- }
-}
diff --git a/src/main/java/org/philzen/oss/AssertEqualsToAssertThat.java b/src/main/java/org/philzen/oss/AssertEqualsToAssertThat.java
deleted file mode 100644
index bae3df57..00000000
--- a/src/main/java/org/philzen/oss/AssertEqualsToAssertThat.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import lombok.EqualsAndHashCode;
-import lombok.Value;
-import org.openrewrite.ExecutionContext;
-import org.openrewrite.Preconditions;
-import org.openrewrite.Recipe;
-import org.openrewrite.TreeVisitor;
-import org.openrewrite.java.JavaIsoVisitor;
-import org.openrewrite.java.JavaParser;
-import org.openrewrite.java.JavaTemplate;
-import org.openrewrite.java.MethodMatcher;
-import org.openrewrite.java.search.UsesType;
-import org.openrewrite.java.tree.Expression;
-import org.openrewrite.java.tree.J;
-
-import java.util.List;
-
-@Value
-@EqualsAndHashCode(callSuper = false)
-public class AssertEqualsToAssertThat extends Recipe {
- @Override
- public String getDisplayName() {
- // language=markdown
- return "JUnit `assertEquals()` to Assertj `assertThat()`";
- }
-
- @Override
- public String getDescription() {
- return "Use AssertJ assertThat instead of JUnit assertEquals().";
- }
-
- private static MethodMatcher MATCHER = new MethodMatcher("org.junit.jupiter.api.Assertions assertEquals(..)");
-
- @Override
- public TreeVisitor, ExecutionContext> getVisitor() {
- return Preconditions.check(new UsesType<>("org.junit.jupiter.api.Assertions", null),
- new JavaIsoVisitor() {
- @Override
- public J.MethodInvocation visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
- J.MethodInvocation m = super.visitMethodInvocation(method, ctx);
- if (!MATCHER.matches(m)) {
- return m;
- }
- List arguments = m.getArguments();
- maybeAddImport("org.assertj.core.api.Assertions");
- maybeRemoveImport("org.junit.jupiter.api.Assertions");
- if (arguments.size() == 2) {
- Expression expected = arguments.get(0);
- Expression actual = arguments.get(1);
-
- m = JavaTemplate.builder("Assertions.assertThat(#{any()}).isEqualTo(#{any()})")
- .imports("org.assertj.core.api.Assertions")
- .javaParser(JavaParser.fromJavaVersion()
- .classpath("assertj-core"))
- .build()
- .apply(getCursor(), m.getCoordinates().replace(), actual, expected);
- } else if (arguments.size() == 3) {
- Expression expected = arguments.get(0);
- Expression actual = arguments.get(1);
- Expression description = arguments.get(2);
-
- m = JavaTemplate.builder("Assertions.assertThat(#{any()}).as(#{any()}).isEqualTo(#{any()})")
- .imports("org.assertj.core.api.Assertions")
- .javaParser(JavaParser.fromJavaVersion()
- .classpath("assertj-core"))
- .build()
- .apply(getCursor(), m.getCoordinates().replace(), actual, description, expected);
- }
- return m;
- }
- });
- }
-}
diff --git a/src/main/java/org/philzen/oss/ClassHierarchy.java b/src/main/java/org/philzen/oss/ClassHierarchy.java
deleted file mode 100644
index bcb540e3..00000000
--- a/src/main/java/org/philzen/oss/ClassHierarchy.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.philzen.oss.table.ClassHierarchyReport;
-import lombok.EqualsAndHashCode;
-import lombok.Value;
-import org.openrewrite.ExecutionContext;
-import org.openrewrite.Recipe;
-import org.openrewrite.TreeVisitor;
-import org.openrewrite.java.JavaIsoVisitor;
-import org.openrewrite.java.tree.J;
-import org.openrewrite.java.tree.JavaType;
-
-@Value
-@EqualsAndHashCode(callSuper = false)
-public class ClassHierarchy extends Recipe {
-
- transient ClassHierarchyReport report = new ClassHierarchyReport(this);
-
- @Override
- public String getDisplayName() {
- return "Class hierarchy";
- }
-
- @Override
- public String getDescription() {
- return "Produces a data table showing inheritance relationships between classes.";
- }
-
- @Override
- public TreeVisitor, ExecutionContext> getVisitor() {
- return new JavaIsoVisitor() {
-
- @Override
- public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
- JavaType.FullyQualified type = classDecl.getType();
- // Capture all classes, which all extend java.lang.Object
- if (type instanceof JavaType.Class && type.getSupertype() != null) {
- JavaType.FullyQualified supertype = type.getSupertype();
- // Capture the direct superclass
- report.insertRow(ctx, new ClassHierarchyReport.Row(
- type.getFullyQualifiedName(),
- ClassHierarchyReport.Relationship.EXTENDS,
- supertype.getFullyQualifiedName()));
-
- // Capture all interfaces
- for (JavaType.FullyQualified anInterface : type.getInterfaces()) {
- report.insertRow(ctx, new ClassHierarchyReport.Row(
- type.getFullyQualifiedName(),
- ClassHierarchyReport.Relationship.IMPLEMENTS,
- anInterface.getFullyQualifiedName()
- ));
- }
- }
- return super.visitClassDeclaration(classDecl, ctx);
- }
- };
- }
-}
diff --git a/src/main/java/org/philzen/oss/NoGuavaListsNewArrayList.java b/src/main/java/org/philzen/oss/NoGuavaListsNewArrayList.java
deleted file mode 100644
index d243c6e9..00000000
--- a/src/main/java/org/philzen/oss/NoGuavaListsNewArrayList.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import lombok.EqualsAndHashCode;
-import lombok.Value;
-import org.openrewrite.ExecutionContext;
-import org.openrewrite.Preconditions;
-import org.openrewrite.Recipe;
-import org.openrewrite.TreeVisitor;
-import org.openrewrite.java.JavaTemplate;
-import org.openrewrite.java.JavaVisitor;
-import org.openrewrite.java.MethodMatcher;
-import org.openrewrite.java.TreeVisitingPrinter;
-import org.openrewrite.java.search.UsesMethod;
-import org.openrewrite.java.tree.J;
-
-@Value
-@EqualsAndHashCode(callSuper = false)
-public class NoGuavaListsNewArrayList extends Recipe {
- // These matchers use a syntax described on https://docs.openrewrite.org/reference/method-patterns
- private static final MethodMatcher NEW_ARRAY_LIST = new MethodMatcher("com.google.common.collect.Lists newArrayList()");
- private static final MethodMatcher NEW_ARRAY_LIST_ITERABLE = new MethodMatcher("com.google.common.collect.Lists newArrayList(java.lang.Iterable)");
- private static final MethodMatcher NEW_ARRAY_LIST_CAPACITY = new MethodMatcher("com.google.common.collect.Lists newArrayListWithCapacity(int)");
-
- @Override
- public String getDisplayName() {
- //language=markdown
- return "Use `new ArrayList<>()` instead of Guava";
- }
-
- @Override
- public String getDescription() {
- //language=markdown
- return "Prefer the Java standard library over third-party usage of Guava in simple cases like this.";
- }
-
- @Override
- public TreeVisitor, ExecutionContext> getVisitor() {
- return Preconditions.check(
- // Any change to the AST made by the preconditions check will lead to the visitor returned by Recipe
- // .getVisitor() being applied
- // No changes made by the preconditions check will be kept
- Preconditions.or(new UsesMethod<>(NEW_ARRAY_LIST),
- new UsesMethod<>(NEW_ARRAY_LIST_ITERABLE),
- new UsesMethod<>(NEW_ARRAY_LIST_CAPACITY)),
- // To avoid stale state persisting between cycles, getVisitor() should always return a new instance of
- // its visitor
- new JavaVisitor() {
- private final JavaTemplate newArrayList = JavaTemplate.builder("new ArrayList<>()")
- .imports("java.util.ArrayList")
- .build();
-
- private final JavaTemplate newArrayListIterable =
- JavaTemplate.builder("new ArrayList<>(#{any(java.util.Collection)})")
- .imports("java.util.ArrayList")
- .build();
-
- private final JavaTemplate newArrayListCapacity =
- JavaTemplate.builder("new ArrayList<>(#{any(int)})")
- .imports("java.util.ArrayList")
- .build();
-
- // This method override is only here to show how to print the AST for debugging purposes.
- // You can remove this method if you don't need it.
- @Override
- public J visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
- // This is a useful debugging tool if you're ever unsure what the visitor is visiting
- String printed = TreeVisitingPrinter.printTree(cu);
- System.out.printf(printed);
- // You must always delegate to the super method to ensure the visitor continues to visit deeper
- return super.visitCompilationUnit(cu, ctx);
- }
-
- // Visit any method invocation, and replace matches with the new ArrayList instantiation.
- @Override
- public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
- if (NEW_ARRAY_LIST.matches(method)) {
- maybeRemoveImport("com.google.common.collect.Lists");
- maybeAddImport("java.util.ArrayList");
- return newArrayList.apply(getCursor(), method.getCoordinates().replace());
- } else if (NEW_ARRAY_LIST_ITERABLE.matches(method)) {
- maybeRemoveImport("com.google.common.collect.Lists");
- maybeAddImport("java.util.ArrayList");
- return newArrayListIterable.apply(getCursor(), method.getCoordinates().replace(),
- method.getArguments().get(0));
- } else if (NEW_ARRAY_LIST_CAPACITY.matches(method)) {
- maybeRemoveImport("com.google.common.collect.Lists");
- maybeAddImport("java.util.ArrayList");
- return newArrayListCapacity.apply(getCursor(), method.getCoordinates().replace(),
- method.getArguments().get(0));
- }
- return super.visitMethodInvocation(method, ctx);
- }
- }
- );
- }
-}
diff --git a/src/main/java/org/philzen/oss/SimplifyTernary.java b/src/main/java/org/philzen/oss/SimplifyTernary.java
deleted file mode 100644
index d609b305..00000000
--- a/src/main/java/org/philzen/oss/SimplifyTernary.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import com.google.errorprone.refaster.annotation.AfterTemplate;
-import com.google.errorprone.refaster.annotation.BeforeTemplate;
-import org.openrewrite.java.template.RecipeDescriptor;
-
-@SuppressWarnings({"SimplifiableConditionalExpression", "unused"})
-@RecipeDescriptor(
- name = "Simplify ternary expressions",
- description = "Simplifies various types of ternary expressions to improve code readability."
-)
-public class SimplifyTernary {
-
- @RecipeDescriptor(
- name = "Replace `booleanExpression ? true : false` with `booleanExpression`",
- description = "Replace ternary expressions like `booleanExpression ? true : false` with `booleanExpression`."
- )
- public static class SimplifyTernaryTrueFalse {
-
- @BeforeTemplate
- boolean before(boolean expr) {
- return expr ? true : false;
- }
-
- @AfterTemplate
- boolean after(boolean expr) {
- return expr;
- }
- }
-
- @RecipeDescriptor(
- name = "Replace `booleanExpression ? false : true` with `!booleanExpression`",
- description = "Replace ternary expressions like `booleanExpression ? false : true` with `!booleanExpression`."
- )
- public static class SimplifyTernaryFalseTrue {
-
- @BeforeTemplate
- boolean before(boolean expr) {
- return expr ? false : true;
- }
-
- @AfterTemplate
- boolean after(boolean expr) {
- // We wrap the expression in parentheses as the input expression might be a complex expression
- return !(expr);
- }
- }
-}
diff --git a/src/main/java/org/philzen/oss/StringIsEmpty.java b/src/main/java/org/philzen/oss/StringIsEmpty.java
deleted file mode 100644
index a5659acd..00000000
--- a/src/main/java/org/philzen/oss/StringIsEmpty.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-// TODO - This is a placeholder for a Refaster recipe. Implement the recipe by adding before and after annotated methods.
-// The rule should replace calls to `String.length() == 0` with `String.isEmpty()`, as well as similar variants.
-// You're done when all the tests in `StringIsEmptyTest` passes.
-public class StringIsEmpty {
-}
diff --git a/src/main/java/org/philzen/oss/UpdateConcoursePipeline.java b/src/main/java/org/philzen/oss/UpdateConcoursePipeline.java
deleted file mode 100644
index 5290f84f..00000000
--- a/src/main/java/org/philzen/oss/UpdateConcoursePipeline.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import lombok.EqualsAndHashCode;
-import lombok.Value;
-import org.openrewrite.*;
-import org.openrewrite.yaml.ChangePropertyValue;
-import org.openrewrite.yaml.YamlIsoVisitor;
-import org.openrewrite.yaml.tree.Yaml;
-
-@Value
-@EqualsAndHashCode(callSuper = false)
-public class UpdateConcoursePipeline extends Recipe {
- @Override
- public String getDisplayName() {
- return "Update concourse pipeline";
- }
-
- @Override
- public String getDescription() {
- return "Update the tag filter on concourse pipelines.";
- }
-
- @Option(displayName = "New tag filter version",
- description = "tag filter version.",
- example = "8.2.0")
- String version;
-
- @Override
- public TreeVisitor, ExecutionContext> getVisitor() {
- return Preconditions.check(
- Preconditions.or(
- new FindSourceFiles("ci/pipeline*.yml").getVisitor(),
- new FindSourceFiles("ci/pipeline*.yaml").getVisitor()),
- new YamlIsoVisitor() {
-
- @Override
- public Yaml.Mapping.Entry visitMappingEntry(Yaml.Mapping.Entry entry, ExecutionContext ctx) {
- Yaml.Mapping.Entry e = super.visitMappingEntry(entry, ctx);
- if ("source".equals(e.getKey().getValue())) {
- Yaml.Block value = e.getValue();
- if (!(value instanceof Yaml.Mapping)) {
- return e;
- }
- Yaml.Mapping mapping = (Yaml.Mapping) value;
- Yaml.Mapping.Entry uriEntry = null;
- Yaml.Mapping.Entry tagFilter = null;
- for (Yaml.Mapping.Entry mappingEntry : mapping.getEntries()) {
- if ("uri".equals(mappingEntry.getKey().getValue())) {
- uriEntry = mappingEntry;
- } else if ("tag_filter".equals(mappingEntry.getKey().getValue())) {
- tagFilter = mappingEntry;
- }
- }
- if (uriEntry == null || tagFilter == null) {
- return e;
- }
- if (!(uriEntry.getValue() instanceof Yaml.Scalar) || !(tagFilter.getValue() instanceof Yaml.Scalar)) {
- return e;
- }
- Yaml.Scalar uriValue = (Yaml.Scalar) uriEntry.getValue();
- if (!uriValue.getValue().contains(".git")) {
- return e;
- }
- Yaml.Scalar tagFilterValue = (Yaml.Scalar) tagFilter.getValue();
- if (version.equals(tagFilterValue.getValue())) {
- return e;
- }
- return (Yaml.Mapping.Entry) new ChangePropertyValue("source.tag_filter", version, null, null, null)
- .getVisitor()
- .visitNonNull(e, ctx);
- }
- return e;
- }
- }
- );
- }
-}
diff --git a/src/main/java/org/philzen/oss/table/ClassHierarchyReport.java b/src/main/java/org/philzen/oss/table/ClassHierarchyReport.java
deleted file mode 100644
index 55bef165..00000000
--- a/src/main/java/org/philzen/oss/table/ClassHierarchyReport.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss.table;
-
-import lombok.Value;
-import org.openrewrite.Column;
-import org.openrewrite.DataTable;
-import org.openrewrite.Recipe;
-
-public class ClassHierarchyReport extends DataTable {
-
- public ClassHierarchyReport(Recipe recipe) {
- super(recipe,
- "Class hierarchy report",
- "Records inheritance relationships between classes.");
- }
-
- @Value
- public static class Row {
- @Column(displayName = "Class name",
- description = "Fully qualified name of the class.")
- String className;
-
- @Column(displayName = "Relationship",
- description = "Whether the class implements a super interface or extends a superclass.")
- Relationship relationship;
-
- @Column(displayName = "Super class name",
- description = "Fully qualified name of the superclass.")
- String superClassName;
- }
-
- public enum Relationship {
- EXTENDS,
- IMPLEMENTS
- }
-}
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..40f42731
--- /dev/null
+++ b/src/main/java/org/philzen/oss/testng/MigrateMismatchedAssertions.java
@@ -0,0 +1,97 @@
+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.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.Parser;
+
+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, ExecutionContext> 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(Parser.runtime()).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(Parser.runtime()).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(Parser.jupiter()).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(Parser.jupiter()).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/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java
new file mode 100644
index 00000000..588f5d5d
--- /dev/null
+++ b/src/main/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5.java
@@ -0,0 +1,237 @@
+package org.philzen.oss.testng;
+
+import lombok.EqualsAndHashCode;
+import lombok.Value;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.Preconditions;
+import org.openrewrite.Recipe;
+import org.openrewrite.TreeVisitor;
+import org.openrewrite.internal.ListUtils;
+import org.openrewrite.internal.lang.NonNullApi;
+import org.openrewrite.internal.lang.Nullable;
+import org.openrewrite.java.AnnotationMatcher;
+import org.openrewrite.java.ChangeType;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.JavaTemplate;
+import org.openrewrite.java.search.FindImports;
+import org.openrewrite.java.search.UsesType;
+import org.openrewrite.java.tree.Expression;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.TypeUtils;
+import org.philzen.oss.utils.Class;
+import org.philzen.oss.utils.*;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+@Value
+@NonNullApi
+@EqualsAndHashCode(callSuper = true)
+public class UpdateTestAnnotationToJunit5 extends Recipe {
+
+ @Override
+ public String getDisplayName() {
+ return "Migrate TestNG `@Test` annotations to JUnit 5";
+ }
+
+ @Override
+ public String getDescription() {
+ return String.format(
+ "Update usages of TestNG's `@%s` annotation to JUnit 5's `@%s` annotation.", TESTNG_TYPE, JUPITER_TYPE
+ );
+ }
+
+ @Override
+ public TreeVisitor, ExecutionContext> getVisitor() {
+ return Preconditions.check(Preconditions.or(
+ new UsesType<>(TESTNG_TYPE, false),
+ new FindImports(TESTNG_TYPE, null).getVisitor()
+ ), new UpdateTestAnnotationToJunit5Visitor());
+ }
+
+ public static final String TESTNG_TYPE = "org.testng.annotations.Test";
+ public static final String JUPITER_API_NAMESPACE = "org.junit.jupiter.api";
+ public static final String JUPITER_TYPE = JUPITER_API_NAMESPACE + ".Test";
+ public static final String JUPITER_ASSERTIONS_TYPE = JUPITER_API_NAMESPACE + ".Assertions";
+
+ // inspired by https://github.com/openrewrite/rewrite-testing-frameworks/blob/4e8ba68b2a28a180f84de7bab9eb12b4643e342e/src/main/java/org/openrewrite/java/testing/junit5/UpdateTestAnnotation.java#
+ private static class UpdateTestAnnotationToJunit5Visitor extends JavaIsoVisitor {
+
+ private static final AnnotationMatcher TESTNG_TEST = new AnnotationMatcher("@org.testng.annotations.Test");
+
+ private final JavaTemplate displayNameAnnotation = JavaTemplate
+ .builder("@DisplayName(#{any(java.lang.String)})")
+ .imports(JUPITER_API_NAMESPACE + ".DisplayName")
+ .javaParser(Parser.jupiter()).build();
+
+ private final JavaTemplate disabledAnnotation = JavaTemplate
+ .builder("@Disabled")
+ .imports(JUPITER_API_NAMESPACE + ".Disabled")
+ .javaParser(Parser.jupiter()).build();
+
+ private final JavaTemplate junitExecutable = JavaTemplate
+ .builder(JUPITER_API_NAMESPACE + ".function.Executable o = () -> #{};")
+ .javaParser(Parser.jupiter()).build();
+
+ private final JavaTemplate tagAnnotation = JavaTemplate
+ .builder("@Tag(#{any(java.lang.String)})")
+ .imports(JUPITER_API_NAMESPACE + ".Tag")
+ .javaParser(Parser.jupiter()).build();
+
+ private final JavaTemplate timeoutAnnotation = JavaTemplate
+ .builder("@Timeout(value = #{any(long)}, unit = TimeUnit.MILLISECONDS)")
+ .imports(JUPITER_API_NAMESPACE + ".Timeout", "java.util.concurrent.TimeUnit")
+ .javaParser(Parser.jupiter()).build();
+
+ @Override
+ public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext ctx) {
+ J.CompilationUnit c = super.visitCompilationUnit(cu, ctx);
+ if (!c.findType(TESTNG_TYPE).isEmpty()) {
+ // Update other references like `Test.class`.
+ c = (J.CompilationUnit) new ChangeType(TESTNG_TYPE, JUPITER_TYPE, true)
+ .getVisitor().visitNonNull(c, ctx);
+ maybeRemoveImport(TESTNG_TYPE);
+ }
+
+ return c;
+ }
+
+ @Override
+ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl,
+ ExecutionContext executionContext) {
+
+ final J.Annotation testAnnotation = Class.getAnnotation(classDecl, TESTNG_TEST);
+ if (testAnnotation != null) {
+ classDecl = Cleanup.removeAnnotation(classDecl, testAnnotation);
+
+ getCursor().putMessage(
+ // don't know a good way to determine if annotation is fully qualified, therefore determining
+ // it from the toString() method and passing on a code template for the JavaTemplate.Builder
+ "ADD_TO_ALL_METHODS", "@" + (testAnnotation.toString().contains(".") ? JUPITER_TYPE : "Test")
+ );
+ }
+
+ return super.visitClassDeclaration(classDecl, executionContext);
+ }
+
+ @Override
+ public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) {
+ final ProcessAnnotationAttributes cta = new ProcessAnnotationAttributes();
+ J.MethodDeclaration m = (J.MethodDeclaration) cta.visitNonNull(method, ctx, getCursor().getParentOrThrow());
+
+ // method identity changes when `@Test` annotation was found and migrated by ChangeTestAnnotation
+ if (m == method) {
+ final String neededOnAllMethods = getCursor().getNearestMessage("ADD_TO_ALL_METHODS");
+ final boolean isContainedInInnerClass = Boolean.TRUE.equals(Method.isContainedInInnerClass(m));
+ if (neededOnAllMethods == null || !Method.isPublic(m) || isContainedInInnerClass) {
+ return m;
+ }
+
+ return JavaTemplate.builder(neededOnAllMethods).javaParser(Parser.jupiter())
+ .imports(JUPITER_TYPE).build()
+ .apply(getCursor(), m.getCoordinates().addAnnotation(Sort.BELOW));
+ }
+
+ if (cta.description != null && !J.Literal.isLiteralValue(cta.description, "")) {
+ maybeAddImport(JUPITER_API_NAMESPACE + ".DisplayName");
+ m = displayNameAnnotation.apply(
+ updateCursor(m), m.getCoordinates().addAnnotation(Sort.BELOW), cta.description
+ );
+ }
+
+ if (J.Literal.isLiteralValue(cta.enabled, Boolean.FALSE)) {
+ maybeAddImport(JUPITER_API_NAMESPACE + ".Disabled");
+ m = disabledAnnotation.apply(updateCursor(m), m.getCoordinates().addAnnotation(Sort.BELOW));
+ }
+
+ if (cta.expectedException instanceof J.FieldAccess
+ // TestNG actually allows any type of Class here, however anything but a Throwable doesn't make sense
+ && TypeUtils.isAssignableTo("java.lang.Throwable", ((J.FieldAccess) cta.expectedException).getTarget().getType()))
+ {
+ m = junitExecutable.apply(updateCursor(m), m.getCoordinates().replaceBody(), m.getBody());
+
+ maybeAddImport(JUPITER_ASSERTIONS_TYPE);
+ final List parameters = Arrays.asList(cta.expectedException, Method.getFirstStatementLambdaAssignment(m));
+ final String code = "Assertions.assertThrows(#{any(java.lang.Class)}, #{any(org.junit.jupiter.api.function.Executable)});";
+ if (!(cta.expectedExceptionMessageRegExp instanceof J.Literal)) {
+ m = JavaTemplate.builder(code).javaParser(Parser.jupiter())
+ .imports(JUPITER_ASSERTIONS_TYPE).build()
+ .apply(updateCursor(m), m.getCoordinates().replaceBody(), parameters.toArray());
+ } else {
+ m = JavaTemplate.builder(
+ "final Throwable thrown = " + code + System.lineSeparator()
+ + "Assertions.assertTrue(thrown.getMessage().matches(#{any(java.lang.String)}));"
+ ).javaParser(Parser.jupiter()).imports(JUPITER_ASSERTIONS_TYPE).build()
+ .apply(
+ updateCursor(m),
+ m.getCoordinates().replaceBody(),
+ ListUtils.concat(parameters, cta.expectedExceptionMessageRegExp).toArray()
+ );
+ }
+ }
+
+ if (cta.groups != null) {
+ maybeAddImport(JUPITER_API_NAMESPACE + ".Tag");
+ if (cta.groups instanceof J.Literal && !J.Literal.isLiteralValue(cta.groups, "")) {
+ m = tagAnnotation.apply(updateCursor(m), m.getCoordinates().addAnnotation(Sort.BELOW), cta.groups);
+ } else if (cta.groups instanceof J.NewArray && ((J.NewArray) cta.groups).getInitializer() != null) {
+ final List groups = ((J.NewArray) cta.groups).getInitializer();
+ for (Expression group : groups) {
+ if (group instanceof J.Empty) continue;
+ m = tagAnnotation.apply(updateCursor(m), m.getCoordinates().addAnnotation(Sort.BELOW), group);
+ }
+ }
+ }
+
+ if (cta.timeout != null) {
+ maybeAddImport("java.util.concurrent.TimeUnit");
+ maybeAddImport(JUPITER_API_NAMESPACE + ".Timeout");
+ m = timeoutAnnotation.apply(updateCursor(m), m.getCoordinates().addAnnotation(Sort.ABOVE), cta.timeout);
+ }
+
+ return m;
+ }
+
+ /**
+ * Parses all annotation arguments, retains all that are migratable
+ * and removes them from the visited @Test
-annotation
+ */
+ private static class ProcessAnnotationAttributes extends JavaIsoVisitor {
+
+ @Nullable
+ Expression description, enabled, expectedException, expectedExceptionMessageRegExp, groups, timeout;
+
+ @Override
+ public J.Annotation visitAnnotation(J.Annotation a, ExecutionContext ctx) {
+ if (a.getArguments() == null || !TESTNG_TEST.matches(a)) {
+ return a;
+ }
+
+ for (Expression arg : a.getArguments()) {
+ final J.Assignment assign = (J.Assignment) arg;
+ final String assignParamName = ((J.Identifier) assign.getVariable()).getSimpleName();
+ final Expression e = assign.getAssignment();
+ if ("description".equals(assignParamName)) {
+ description = e;
+ } else if ("enabled".equals(assignParamName)) {
+ enabled = e;
+ } else if ("expectedExceptions".equals(assignParamName)) {
+ // if attribute was given in { array form }, pick the first element (null is not allowed)
+ expectedException = !(e instanceof J.NewArray)
+ ? e : Objects.requireNonNull(((J.NewArray) e).getInitializer()).get(0);
+ } else if ("expectedExceptionsMessageRegExp".equals(assignParamName)) {
+ expectedExceptionMessageRegExp = e;
+ } else if ("groups".equals(assignParamName)) {
+ groups = e;
+ } else if ("timeOut".equals(assignParamName)) {
+ timeout = e;
+ }
+ }
+
+ // remove all attribute arguments (JUnit 5 @Test annotation doesn't allow any)
+ return a.withArguments(null);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/philzen/oss/utils/Class.java b/src/main/java/org/philzen/oss/utils/Class.java
new file mode 100644
index 00000000..2601a057
--- /dev/null
+++ b/src/main/java/org/philzen/oss/utils/Class.java
@@ -0,0 +1,20 @@
+package org.philzen.oss.utils;
+
+import org.openrewrite.internal.lang.NonNullApi;
+import org.openrewrite.internal.lang.Nullable;
+import org.openrewrite.java.AnnotationMatcher;
+import org.openrewrite.java.tree.J;
+
+import java.util.Optional;
+
+@NonNullApi
+public enum Class {;
+
+ @Nullable
+ public static J.Annotation getAnnotation(J.ClassDeclaration classDeclaration, AnnotationMatcher annotation) {
+ final Optional maybeAnnotation = classDeclaration.getLeadingAnnotations()
+ .stream().filter(annotation::matches).findFirst();
+
+ return maybeAnnotation.orElse(null);
+ }
+}
diff --git a/src/main/java/org/philzen/oss/utils/Cleanup.java b/src/main/java/org/philzen/oss/utils/Cleanup.java
new file mode 100644
index 00000000..1d3d19d3
--- /dev/null
+++ b/src/main/java/org/philzen/oss/utils/Cleanup.java
@@ -0,0 +1,44 @@
+package org.philzen.oss.utils;
+
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JContainer;
+import org.openrewrite.java.tree.Space;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public enum Cleanup {;
+
+ /**
+ * Removes an annotation and cleans the space that it occupied.
+ * Same could be achieved with {@link org.openrewrite.java.RemoveAnnotationVisitor}, however
+ * that would also traverse the whole LST underneath the class, yielding suboptimal performance.
+ *
+ * Space cleaning algorithm borrowed from {@link org.openrewrite.java.RemoveAnnotationVisitor#visitClassDeclaration(J.ClassDeclaration, ExecutionContext)}
+ */
+ public static J.ClassDeclaration removeAnnotation(J.ClassDeclaration classDeclaration, J.Annotation a) {
+
+ classDeclaration.getLeadingAnnotations().remove(a);
+ if (!classDeclaration.getLeadingAnnotations().isEmpty()) {
+ final List newLeadingAnnotations = new ArrayList<>();
+ for (final J.Annotation other : classDeclaration.getLeadingAnnotations()) {
+ newLeadingAnnotations.add(other.withPrefix(other.getPrefix().withWhitespace("")));
+ }
+ return classDeclaration.withLeadingAnnotations(newLeadingAnnotations);
+ }
+
+ final List modifiers = classDeclaration.getModifiers();
+ if (!modifiers.isEmpty()) {
+ return classDeclaration.withModifiers(Space.formatFirstPrefix(modifiers, Space.firstPrefix(modifiers).withWhitespace("")));
+ }
+
+ final JContainer typeParameters = classDeclaration.getPadding().getTypeParameters();
+ if (typeParameters != null) {
+ return classDeclaration.getPadding().withTypeParameters(typeParameters.withBefore(typeParameters.getBefore().withWhitespace("")));
+ }
+
+ final J.ClassDeclaration.Padding padding = classDeclaration.getPadding();
+ return padding.withKind(padding.getKind().withPrefix(padding.getKind().getPrefix().withWhitespace("")));
+ }
+}
diff --git a/src/main/java/org/philzen/oss/utils/Method.java b/src/main/java/org/philzen/oss/utils/Method.java
new file mode 100644
index 00000000..42f0c5d3
--- /dev/null
+++ b/src/main/java/org/philzen/oss/utils/Method.java
@@ -0,0 +1,50 @@
+package org.philzen.oss.utils;
+
+import org.openrewrite.internal.lang.NonNullApi;
+import org.openrewrite.internal.lang.Nullable;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.java.tree.JavaType;
+
+@NonNullApi
+public enum Method {;
+
+ /**
+ * Whether this method is declared in a nested class and not on the main class scope
+ * @return Returns null
in the fringe case that {@link J.MethodDeclaration#getMethodType()} is
+ * null
as it's not possible to query the parent scope information then (respectively
+ * it's not clear what {@link J.MethodDeclaration#getMethodType()} == null
means)
+ */
+ @Nullable
+ public static Boolean isContainedInInnerClass(J.MethodDeclaration method) {
+ final JavaType.Method methodType = method.getMethodType();
+ if (methodType == null) {
+ return null;
+ }
+
+ return methodType.getDeclaringType().getOwningClass() != null;
+ }
+
+ public static boolean isPublic(J.MethodDeclaration method) {
+ return method.getModifiers().stream().anyMatch(mod -> mod.toString().equals("public"));
+ }
+
+ /**
+ * Suppose you have a method that looks like this:
+ *
+ * void method() {
+ * Supplier x = () -> { return "x" };
+ * }
+ *
+ * Then this method will return the {@link J.Lambda} that represents () -> { return "x" }
.
+ * @return The lambda or null
, if no such expression exists on the first statement of the method body
+ */
+ @Nullable
+ public static J.Lambda getFirstStatementLambdaAssignment(J.MethodDeclaration method) {
+ final J.Block body = method.getBody();
+ if (body == null) {
+ return null;
+ }
+
+ return (J.Lambda) ((J.VariableDeclarations) body.getStatements().get(0)).getVariables().get(0).getInitializer();
+ }
+}
diff --git a/src/main/java/org/philzen/oss/utils/Parser.java b/src/main/java/org/philzen/oss/utils/Parser.java
new file mode 100644
index 00000000..5c78336e
--- /dev/null
+++ b/src/main/java/org/philzen/oss/utils/Parser.java
@@ -0,0 +1,28 @@
+package org.philzen.oss.utils;
+
+import org.openrewrite.java.JavaParser;
+
+public enum Parser {;
+
+ private static final class JavaParserHolder {
+ static final JavaParser.Builder, ?> jupiter =
+ JavaParser.fromJavaVersion().classpath("junit-jupiter-api");
+
+ static final JavaParser.Builder, ?> runtimeClasspath =
+ JavaParser.fromJavaVersion().classpath(JavaParser.runtimeClasspath());
+ }
+
+ /**
+ * Get a {@link JavaParser.Builder} with junit-jupiter-api added to the classpath
+ */
+ public static JavaParser.Builder, ?> jupiter() {
+ return JavaParserHolder.jupiter;
+ }
+
+ /**
+ * Get a {@link JavaParser.Builder} for the full runtime classpath
+ */
+ public static JavaParser.Builder, ?> runtime() {
+ return JavaParserHolder.runtimeClasspath;
+ }
+}
diff --git a/src/main/java/org/philzen/oss/utils/Sort.java b/src/main/java/org/philzen/oss/utils/Sort.java
new file mode 100644
index 00000000..be28ae4d
--- /dev/null
+++ b/src/main/java/org/philzen/oss/utils/Sort.java
@@ -0,0 +1,10 @@
+package org.philzen.oss.utils;
+
+import org.openrewrite.java.tree.J;
+
+import java.util.Comparator;
+
+public enum Sort {;
+ public static final Comparator ABOVE = java.util.Comparator.comparing(J.Annotation::getSimpleName);
+ public static final Comparator BELOW = java.util.Comparator.comparing(J.Annotation::getSimpleName).reversed();
+}
diff --git a/src/main/resources/META-INF/rewrite/rewrite.yml b/src/main/resources/META-INF/rewrite/rewrite.yml
index 9bf81b0c..fed10d41 100644
--- a/src/main/resources/META-INF/rewrite/rewrite.yml
+++ b/src/main/resources/META-INF/rewrite/rewrite.yml
@@ -1,32 +1,14 @@
-#
-# Copyright 2021 the original author or authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-# Include any Declarative YAML format recipes here, as per:
-# https://docs.openrewrite.org/reference/yaml-format-reference
-# These are most easily composed through the Yaml recipe builder at:
-# https://app.moderne.io/recipes/builder
-
-# Notice how we can have multiple recipes in the same file, separated by `---`
-# You can also have multiple files in `src/main/resources/META-INF/rewrite`, each containing one or more recipes.
----
type: specs.openrewrite.org/v1beta/recipe
-name: org.philzen.oss.UseOpenRewriteNullable
-displayName: Prefer OpenRewrite Nullable
-description: Replaces JetBrains Nullable with OpenRewrite Nullable.
+name: org.philzen.oss.testng.MigrateToJunit5
+tags: [TestNG, JUnit5, Jupiter, JUnit]
+displayName: JUnit Jupiter migration from TestNG
+description: Migrates TestNG tests to JUnit Jupiter.
+estimatedEffortPerOccurrence: PT20S
+preconditions:
+- org.openrewrite.FindSourceFiles:
+ filePattern: "**/*.java"
recipeList:
- - org.openrewrite.java.ChangeType:
- oldFullyQualifiedTypeName: org.jetbrains.annotations.Nullable
- newFullyQualifiedTypeName: org.openrewrite.internal.lang.Nullable
+- 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/main/resources/META-INF/rewrite/stringutils.yml b/src/main/resources/META-INF/rewrite/stringutils.yml
deleted file mode 100644
index 4b98316c..00000000
--- a/src/main/resources/META-INF/rewrite/stringutils.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-#
-# Copyright 2024 the original author or authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
----
-type: specs.openrewrite.org/v1beta/recipe
-name: org.philzen.oss.UseApacheStringUtils
-displayName: Use Apache `StringUtils`
-description: Replace Spring string utilities with Apache string utilities
-recipeList:
- - org.openrewrite.java.dependencies.AddDependency:
- groupId: org.apache.commons
- artifactId: commons-lang3
- version: latest.release
- onlyIfUsing: org.springframework.util.StringUtils
- configuration: implementation
- - org.openrewrite.java.ChangeType:
- oldFullyQualifiedTypeName: org.springframework.util.StringUtils
- newFullyQualifiedTypeName: org.apache.commons.lang3.StringUtils
diff --git a/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateAssertionsTests.java b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateAssertionsTests.java
new file mode 100644
index 00000000..f8a488bc
--- /dev/null
+++ b/src/test/java/io/github/mboegers/openrewrite/testngtojupiter/MigrateAssertionsTests.java
@@ -0,0 +1,1377 @@
+/*
+ * 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 org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.openrewrite.java.JavaParser;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.params.provider.Arguments.arguments;
+import static org.openrewrite.java.Assertions.java;
+
+class MigrateAssertionsTests implements RewriteTest {
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.parser(JavaParser.fromJavaVersion()
+ .logCompilationWarningsAndErrors(true)
+ .classpath("junit-jupiter-api", "testng"))
+ .recipe(new MigrateAssertionsRecipes());
+ }
+
+ static Supplier> assertEqualsWithMessageArgumentStream = () -> Stream.of(
+ arguments("boolean", "boolean"),
+ arguments("boolean", "java.lang.Boolean"),
+ arguments("java.lang.Boolean", "boolean"),
+ arguments("java.lang.Boolean", "java.lang.Boolean"),
+
+ arguments("byte", "byte"),
+ arguments("byte", "java.lang.Byte"),
+ arguments("java.lang.Byte", "byte"),
+ arguments("java.lang.Byte", "java.lang.Byte"),
+
+ arguments("char", "char"),
+ arguments("char", "java.lang.Character"),
+ arguments("java.lang.Character", "char"),
+ arguments("java.lang.Character", "java.lang.Character"),
+
+ arguments("double", "double"),
+ arguments("double", "java.lang.Double"),
+ arguments("java.lang.Double", "double"),
+ arguments("java.lang.Double", "java.lang.Double"),
+
+ arguments("float", "float"),
+ arguments("float", "java.lang.Float"),
+ arguments("java.lang.Float", "float"),
+ arguments("java.lang.Float", "java.lang.Float"),
+
+ arguments("java.lang.Short", "java.lang.Short"),
+ arguments("java.lang.Short", "short"),
+ arguments("short", "java.lang.Short"),
+ arguments("short", "short"),
+
+ arguments("int", "int"),
+ arguments("int", "java.lang.Integer"),
+ arguments("java.lang.Integer", "int"),
+ arguments("java.lang.Integer", "java.lang.Integer"),
+
+ arguments("java.lang.Long", "java.lang.Long"),
+ arguments("java.lang.Long", "long"),
+ arguments("long", "long"),
+
+ arguments("java.lang.String", "java.lang.String")
+ );
+
+ static Supplier> assertArrayArgumentStream = () -> Stream.of(
+ arguments("boolean[]", "boolean[]"),
+ arguments("byte[]", "byte[]"),
+ arguments("char[]", "char[]"),
+ arguments("double[]", "double[]"),
+ arguments("float[]", "float[]"),
+ arguments("short[]", "short[]"),
+ arguments("int[]", "int[]"),
+ arguments("long[]", "long[]"),
+ arguments("Object[]", "Object[]")
+ );
+
+ static Stream assertEqualsArgumentsWithMessage() {
+ return assertEqualsWithMessageArgumentStream.get();
+ }
+
+ static Stream assertEqualsArgumentsWithoutMessage() {
+ return Stream.concat(
+ assertEqualsWithMessageArgumentStream.get(),
+ // ↓ there is no overload for this one with a message argument in TestNG
+ Stream.of(arguments("long", "java.lang.Long"))
+ );
+ }
+
+ static Stream toAssertArguments_shorterList() {
+ return Stream.of(
+ arguments("java.lang.Boolean", "java.lang.Boolean"),
+ arguments("java.lang.Character", "java.lang.Character"),
+ arguments("double", "double"),
+ arguments("java.lang.Short", "short"),
+ arguments("long", "java.lang.Long"),
+ arguments("java.lang.String", "java.lang.String")
+ );
+ }
+
+ static Stream toAssertArrayArguments() {
+ return assertArrayArgumentStream.get();
+ }
+
+ @Nested class MigrateAssertEquals {
+
+ @Nested class WithErrorMessage {
+
+ @MethodSource("io.github.mboegers.openrewrite.testngtojupiter.MigrateAssertionsTests#assertEqualsArgumentsWithMessage")
+ @ParameterizedTest void becomesAssertEquals_forPrimitiveAndBoxedArguments(String actual, String expected) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertEquals(actual, expected, "Test failed badly");
+ }
+ }
+ """.formatted(actual, expected),
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assertions.assertEquals(expected, actual, "Test failed badly");
+ }
+ }
+ """.formatted(actual, expected)
+ ));
+ }
+
+ @MethodSource("io.github.mboegers.openrewrite.testngtojupiter.MigrateAssertionsTests#toAssertArrayArguments")
+ @ParameterizedTest void becomesAssertArrayEquals_forArrays(String actual, String expected) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertEquals(actual, expected, "Test failed badly");
+ }
+ }
+ """.formatted(actual, expected),
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assertions.assertArrayEquals(expected, actual, "Test failed badly");
+ }
+ }
+ """.formatted(actual, expected)
+ ));
+ }
+
+ @Test void becomesSpecialAssertArrayEquals_forIterators() {
+ // language=java
+ rewriteRun(java(
+ """
+ import java.util.Iterator;
+ import java.util.List;
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assert.assertEquals(actual, expected, "Kaboom.");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Spliterators;
+ import java.util.stream.StreamSupport;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assertions.assertArrayEquals(StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).toArray(), StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).toArray(), "Kaboom.");
+ }
+ }
+ """
+ ));
+ }
+
+ @ValueSource(strings = {"float", "double"})
+ @ParameterizedTest void deltaFunctionIsMigrated(String type) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertEquals(actual, expected, %s, "Test failed badly");
+ }
+ }
+ """.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.assertEquals(expected, actual, %s, "Test failed badly");
+ }
+ }
+ """.formatted(type, type, type.equals("float") ? "0.1f" : "0.2d")
+ ));
+ }
+ }
+
+ @Nested class WithoutErrorMessage {
+
+ @MethodSource("io.github.mboegers.openrewrite.testngtojupiter.MigrateAssertionsTests#assertEqualsArgumentsWithoutMessage")
+ @ParameterizedTest void becomesAssertEquals_forPrimitiveAndBoxedArguments(String actual, String expected) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertEquals(actual, expected);
+ }
+ }
+ """.formatted(actual, expected),
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assertions.assertEquals(expected, actual);
+ }
+ }
+ """.formatted(actual, expected)
+ ));
+ }
+
+ @MethodSource("io.github.mboegers.openrewrite.testngtojupiter.MigrateAssertionsTests#toAssertArrayArguments")
+ @ParameterizedTest void becomesAssertArrayEquals_forArrays(String actual, String expected) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertEquals(actual, expected);
+ }
+ }
+ """.formatted(actual, expected),
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assertions.assertArrayEquals(expected, actual);
+ }
+ }
+ """.formatted(actual, expected)
+ ));
+ }
+
+ @Test void becomesSpecialAssertArrayEquals_forIterators() {
+ // language=java
+ rewriteRun(java(
+ """
+ import java.util.Iterator;
+ import java.util.List;
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assert.assertEquals(actual, expected);
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Spliterators;
+ import java.util.stream.StreamSupport;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assertions.assertArrayEquals(StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).toArray(), StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).toArray());
+ }
+ }
+ """
+ ));
+ }
+
+ @ValueSource(strings = {"float", "double"})
+ @ParameterizedTest void deltaFunctionIsMigrated(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.assertEquals(expected, actual, %s);
+ }
+ }
+ """.formatted(type, type, type.equals("float") ? "0.1f" : "0.2d")
+ ));
+ }
+ }
+ }
+
+ @Nested class MigrateAssertEqualsNoOrder {
+
+ @SuppressWarnings("SimplifyStreamApiCallChains")
+ @Nested class WithErrorMessage {
+
+ @Test void migratesCollectionToAssertArrayEquals() {
+ // language=java
+ rewriteRun(java(
+ """
+ import java.util.Arrays;
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertEqualsNoOrder(Arrays.asList("a", "b"), Arrays.asList("b", "a"), "Should contain the same elements");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Arrays;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertArrayEquals(Arrays.asList("b", "a").stream().sorted().toArray(), Arrays.asList("a", "b").stream().sorted().toArray(), "Should contain the same elements");
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void migratesArrayToAssertArrayEquals() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertEqualsNoOrder(new String[] {"actual"}, new String[] {"expected"}, "Should contain the same elements");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Arrays;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertArrayEquals(Arrays.stream(new String[]{"expected"}).sorted().toArray(), Arrays.stream(new String[]{"actual"}).sorted().toArray(), "Should contain the same elements");
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void migratesIteratorToAssertArrayEquals() {
+ // language=java
+ rewriteRun(java(
+ """
+ import java.util.Iterator;
+ import java.util.List;
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assert.assertEqualsNoOrder(actual, expected, "Should contain the same elements");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Spliterators;
+ import java.util.stream.StreamSupport;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assertions.assertArrayEquals(StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).sorted().toArray(), StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).sorted().toArray(), "Should contain the same elements");
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @SuppressWarnings("SimplifyStreamApiCallChains")
+ @Nested class WithoutErrorMessage {
+
+ @Test void migratesCollectionToAssertArrayEquals() {
+ // language=java
+ rewriteRun(java(
+ """
+ import java.util.Arrays;
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertEqualsNoOrder(Arrays.asList("a", "b"), Arrays.asList("b", "a"));
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Arrays;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertArrayEquals(Arrays.asList("b", "a").stream().sorted().toArray(), Arrays.asList("a", "b").stream().sorted().toArray());
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void migratesArrayToAssertArrayEquals() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertEqualsNoOrder(new String[] {"actual"}, new String[] {"expected"});
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Arrays;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertArrayEquals(Arrays.stream(new String[]{"expected"}).sorted().toArray(), Arrays.stream(new String[]{"actual"}).sorted().toArray());
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void migratesIteratorToAssertArrayEquals() {
+ // language=java
+ rewriteRun(java(
+ """
+ import java.util.Iterator;
+ import java.util.List;
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assert.assertEqualsNoOrder(actual, expected);
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Spliterators;
+ import java.util.stream.StreamSupport;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assertions.assertArrayEquals(StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).sorted().toArray(), StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).sorted().toArray());
+ }
+ }
+ """
+ ));
+ }
+ }
+ }
+
+ @Nested class MigrateAssertNotEquals {
+
+ @Nested class WithErrorMessage {
+
+ @MethodSource("io.github.mboegers.openrewrite.testngtojupiter.MigrateAssertionsTests#toAssertArguments_shorterList")
+ @ParameterizedTest void isMigratedToJupiterEquivalent(String actual, String expected) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertNotEquals(actual, expected, "Test failed badly");
+ }
+ }
+ """.formatted(actual, expected), """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assertions.assertNotEquals(expected, actual, "Test failed badly");
+ }
+ }
+ """.formatted(actual, expected)
+ ));
+ }
+
+ @MethodSource("io.github.mboegers.openrewrite.testngtojupiter.MigrateAssertionsTests#toAssertArrayArguments")
+ @ParameterizedTest void isMigratedToJupiterEquivalent_wrappedInArrayToString_forArrays(String actual, String expected) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertNotEquals(actual, expected, "Test failed badly");
+ }
+ }
+ """.formatted(actual, expected),
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Arrays;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assertions.assertNotEquals(Arrays.toString(expected), Arrays.toString(actual), "Test failed badly");
+ }
+ }
+ """.formatted(actual, expected)
+ ));
+ }
+
+ @Test void becomesSpecialAssertNotEquals_forIterators() {
+ // language=java
+ rewriteRun(java(
+ """
+ import java.util.Iterator;
+ import java.util.List;
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assert.assertNotEquals(actual, expected, "Kaboom.");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Arrays;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Spliterators;
+ import java.util.stream.StreamSupport;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assertions.assertNotEquals(Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).toArray()), Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).toArray()), "Kaboom.");
+ }
+ }
+ """
+ ));
+ }
+
+ @ValueSource(strings = {"float", "double"})
+ @ParameterizedTest void deltaFunctionIsMigrated(String type) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertNotEquals(actual, expected, %s, "Test failed badly");
+ }
+ }
+ """.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.assertNotEquals(expected, actual, %s, "Test failed badly");
+ }
+ }
+ """.formatted(type, type, type.equals("float") ? "0.1f" : "0.2d")
+ ));
+ }
+ }
+
+ @Nested class WithoutErrorMessage {
+
+ @MethodSource("io.github.mboegers.openrewrite.testngtojupiter.MigrateAssertionsTests#toAssertArguments_shorterList")
+ @ParameterizedTest void withoutErrorMessage(String actual, String expected) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertNotEquals(actual, expected);
+ }
+ }
+ """.formatted(actual, expected),
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assertions.assertNotEquals(expected, actual);
+ }
+ }
+ """.formatted(actual, expected)
+ ));
+ }
+
+ @MethodSource("io.github.mboegers.openrewrite.testngtojupiter.MigrateAssertionsTests#toAssertArrayArguments")
+ @ParameterizedTest void isMigratedToJupiterEquivalent_wrappedInArrayToString_forArrays(String actual, String expected) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertNotEquals(actual, expected);
+ }
+ }
+ """.formatted(actual, expected),
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Arrays;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assertions.assertNotEquals(Arrays.toString(expected), Arrays.toString(actual));
+ }
+ }
+ """.formatted(actual, expected)
+ ));
+ }
+
+ @Test void becomesSpecialAssertNotEquals_forIterators() {
+ // language=java
+ rewriteRun(java(
+ """
+ import java.util.Iterator;
+ import java.util.List;
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assert.assertNotEquals(actual, expected);
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ import java.util.Arrays;
+ import java.util.Iterator;
+ import java.util.List;
+ import java.util.Spliterators;
+ import java.util.stream.StreamSupport;
+
+ class MyTest {
+ void testMethod() {
+ Iterator actual = List.of("a", "b").iterator();
+ Iterator expected = List.of("b", "a").iterator();
+
+ Assertions.assertNotEquals(Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(expected, 0), false).toArray()), Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(actual, 0), false).toArray()));
+ }
+ }
+ """
+ ));
+ }
+
+ @ValueSource(strings = {"float", "double"})
+ @ParameterizedTest void deltaFunctionIsMigrated(String type) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertNotEquals(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.assertNotEquals(expected, actual, %s);
+ }
+ }
+ """.formatted(type, type, type.equals("float") ? "0.1f" : "0.2d")
+ ));
+ }
+ }
+ }
+
+ @Nested class SkipAssertEqualsDeep {
+
+ @ValueSource(strings = {"java.util.Map,?>", "java.util.Set>"})
+ @ParameterizedTest void withErrorMessage(String type) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s actual;
+ %s expected;
+
+ Assert.assertEqualsDeep(actual, expected, "Test failed badly");
+ }
+ }
+ """.formatted(type, type)
+ ));
+ }
+
+ @Test
+ void withoutErrorMessage() {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ java.util.Map,?> actual;
+ java.util.Map,?> expected;
+
+ Assert.assertEqualsDeep(actual, expected);
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @Nested class MigrateAssertFalse {
+
+ @ValueSource(strings = {"boolean", "Boolean"})
+ @ParameterizedTest void withErrorMessage(String type) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s expr;
+
+ Assert.assertFalse(expr, "Test failed badly");
+ }
+ }
+ """.formatted(type), """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s expr;
+
+ Assertions.assertFalse(expr, "Test failed badly");
+ }
+ }
+ """.formatted(type)
+ ));
+ }
+
+ @ValueSource(strings = {"boolean", "Boolean"})
+ @ParameterizedTest void withoutErrorMessage(String type) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s expr;
+
+ Assert.assertFalse(expr);
+ }
+ }
+ """.formatted(type), """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s expr;
+
+ Assertions.assertFalse(expr);
+ }
+ }
+ """.formatted(type)
+ ));
+ }
+ }
+
+ @Nested class MigrateAssertTrue {
+
+ @ValueSource(strings = {"boolean", "Boolean"})
+ @ParameterizedTest void withErrorMessage(String type) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s expr;
+
+ Assert.assertTrue(expr, "Test failed badly");
+ }
+ }
+ """.formatted(type), """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s expr;
+
+ Assertions.assertTrue(expr, "Test failed badly");
+ }
+ }
+ """.formatted(type)
+ ));
+ }
+
+ @ValueSource(strings = {"boolean", "Boolean"})
+ @ParameterizedTest void withoutErrorMessage(String type) {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ %s expr;
+
+ Assert.assertTrue(expr);
+ }
+ }
+ """.formatted(type), """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ %s expr;
+
+ Assertions.assertTrue(expr);
+ }
+ }
+ """.formatted(type)
+ ));
+ }
+ }
+
+ @SuppressWarnings({"DataFlowIssue", "ObviousNullCheck"})
+ @Nested class MigrateAssertNull {
+
+ @Test void withErrorMessage() {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertNull(null, "Near-missed the billion dollar mistake.");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertNull(null, "Near-missed the billion dollar mistake.");
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void withoutErrorMessage() {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertNull("Not null");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertNull("Not null");
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @SuppressWarnings({"DataFlowIssue", "ObviousNullCheck"})
+ @Nested class MigrateAssertNotNull {
+
+ @Test void withErrorMessage() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertNotNull(null, "The billion dollar mistake hit again.");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertNotNull(null, "The billion dollar mistake hit again.");
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void withoutErrorMessage() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertNotNull("Not null");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertNotNull("Not null");
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @Nested class MigrateAssertNotSame {
+
+ @Test void withMessage() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertNotSame(MyTest.class, Object.class, "Should not be the same object instance");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertNotSame(Object.class, MyTest.class, "Should not be the same object instance");
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void withoutMessage() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertNotSame(MyTest.class, Object.class);
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertNotSame(Object.class, MyTest.class);
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @SuppressWarnings("EqualsWithItself")
+ @Nested class MigrateAssertSame {
+
+ @Test void withMessage() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Class> actual = MyTest.class;
+ Assert.assertSame(MyTest.class, actual, "Should be the same object instance");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Class> actual = MyTest.class;
+ Assertions.assertSame(actual, MyTest.class, "Should be the same object instance");
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void withoutMessage() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertSame(MyTest.class, MyTest.class);
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertSame(MyTest.class, MyTest.class);
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @Nested class MigrateAssertThrows {
+
+ @Test void runnableWithNoExpectedType() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertThrows(() -> { throw new RuntimeException(); });
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertThrows(Throwable.class, () -> {
+ throw new RuntimeException();
+ });
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void runnableWithNoExpectedThrowableType() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.assertThrows(Exception.class, () -> { throw new RuntimeException(); });
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertThrows(Exception.class, () -> {
+ throw new RuntimeException();
+ });
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @Test void migrateExpectThrows() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.expectThrows(Exception.class, () -> { throw new RuntimeException(); });
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.assertThrows(Exception.class, () -> {
+ throw new RuntimeException();
+ });
+ }
+ }
+ """
+ ));
+ }
+
+ @Nested class MigrateFail {
+
+ @Test void withNoArguments() {
+ //language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.fail();
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.fail();
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void withMessage() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.fail("Boom!");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.fail("Boom!");
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void withMessageAndException() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import org.testng.Assert;
+
+ class MyTest {
+ void testMethod() {
+ Assert.fail("Boom!", new Exception("Halted and caught fire."));
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+
+ class MyTest {
+ void testMethod() {
+ Assertions.fail("Boom!", new Exception("Halted and caught fire."));
+ }
+ }
+ """
+ ));
+ }
+ }
+}
diff --git a/src/test/java/org/philzen/oss/AppendToReleaseNotesTest.java b/src/test/java/org/philzen/oss/AppendToReleaseNotesTest.java
deleted file mode 100644
index e66ade6b..00000000
--- a/src/test/java/org/philzen/oss/AppendToReleaseNotesTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.junit.jupiter.api.Test;
-import org.openrewrite.DocumentExample;
-import org.openrewrite.test.RecipeSpec;
-import org.openrewrite.test.RewriteTest;
-
-import java.nio.file.Paths;
-
-import static org.openrewrite.test.SourceSpecs.text;
-
-class AppendToReleaseNotesTest implements RewriteTest {
- @Override
- public void defaults(RecipeSpec spec) {
- spec.recipe(new AppendToReleaseNotes("Hello world"));
- }
-
- @Test
- void createNewReleaseNotes() {
- // Notice how the before text is null, indicating that the file does not exist yet.
- // The after text is the content of the file after the recipe is applied.
- rewriteRun(
- text(
- null,
- """
- Hello world
- """,
- spec -> spec.path(Paths.get("RELEASE.md")
- )
- )
- );
- }
-
- @DocumentExample
- @Test
- void editExistingReleaseNotes() {
- // When the file does already exist, we assert the content is modified as expected.
- rewriteRun(
- text(
- """
- You say goodbye, I say
- """,
- """
- You say goodbye, I say
- Hello world
- """,
- spec -> spec.path(Paths.get("RELEASE.md")
- )
- )
- );
- }
-}
diff --git a/src/test/java/org/philzen/oss/AssertEqualsToAssertThatTest.java b/src/test/java/org/philzen/oss/AssertEqualsToAssertThatTest.java
deleted file mode 100644
index 53fb16e3..00000000
--- a/src/test/java/org/philzen/oss/AssertEqualsToAssertThatTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.junit.jupiter.api.Test;
-import org.openrewrite.DocumentExample;
-import org.openrewrite.java.JavaParser;
-import org.openrewrite.test.RecipeSpec;
-import org.openrewrite.test.RewriteTest;
-
-import static org.openrewrite.java.Assertions.java;
-
-class AssertEqualsToAssertThatTest implements RewriteTest {
-
- @Override
- public void defaults(RecipeSpec spec) {
- spec.recipe(new AssertEqualsToAssertThat())
- .parser(JavaParser.fromJavaVersion()
- .classpath("junit-jupiter-api"));
- }
-
- @DocumentExample
- @Test
- void twoArgument() {
- rewriteRun(
- //language=java
- java(
- """
- import org.junit.jupiter.api.Assertions;
-
- class A {
- void foo() {
- Assertions.assertEquals(1, 2);
- }
- }
- """,
- """
- import org.assertj.core.api.Assertions;
-
- class A {
- void foo() {
- Assertions.assertThat(2).isEqualTo(1);
- }
- }
- """
- )
- );
- }
-
- @Test
- void withDescription() {
- rewriteRun(
- //language=java
- java(
- """
- import org.junit.jupiter.api.Assertions;
-
- class A {
- void foo() {
- Assertions.assertEquals(1, 2, "one equals two, everyone knows that");
- }
- }
- """,
- """
- import org.assertj.core.api.Assertions;
-
- class A {
- void foo() {
- Assertions.assertThat(2).as("one equals two, everyone knows that").isEqualTo(1);
- }
- }
- """
- )
- );
- }
-}
diff --git a/src/test/java/org/philzen/oss/ClassHierarchyTest.java b/src/test/java/org/philzen/oss/ClassHierarchyTest.java
deleted file mode 100644
index 0c3b124e..00000000
--- a/src/test/java/org/philzen/oss/ClassHierarchyTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.philzen.oss.table.ClassHierarchyReport;
-import org.junit.jupiter.api.Test;
-import org.openrewrite.test.RecipeSpec;
-import org.openrewrite.test.RewriteTest;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.openrewrite.java.Assertions.java;
-
-class ClassHierarchyTest implements RewriteTest {
-
- @Override
- public void defaults(RecipeSpec spec) {
- spec.recipe(new ClassHierarchy());
- }
-
- @Test
- void basic() {
- rewriteRun(
- spec -> spec.dataTable(ClassHierarchyReport.Row.class, rows -> {
- assertThat(rows).containsExactly(new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.EXTENDS, "java.lang.Object"));
- }),
- //language=java
- java(
- """
- class A {}
- """
- )
- );
- }
-
- @Test
- void bExtendsA() {
- rewriteRun(
- spec -> spec.dataTable(ClassHierarchyReport.Row.class, rows -> {
- assertThat(rows).containsExactly(
- new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.EXTENDS, "java.lang.Object"),
- new ClassHierarchyReport.Row("B", ClassHierarchyReport.Relationship.EXTENDS, "A"));
- }),
- //language=java
- java(
- """
- class A {}
- """
- ),
- //language=java
- java(
- """
- class B extends A {}
- """
- )
- );
- }
-
- @Test
- void interfaceRelationship() {
- rewriteRun(
- spec -> spec.dataTable(ClassHierarchyReport.Row.class, rows -> {
- assertThat(rows).containsExactly(
- new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.EXTENDS, "java.lang.Object"),
- new ClassHierarchyReport.Row("A", ClassHierarchyReport.Relationship.IMPLEMENTS, "java.io.Serializable"));
- }),
- // language=java
- java(
- """
- import java.io.Serializable;
- class A implements Serializable {}
- """
- )
- );
- }
-}
diff --git a/src/test/java/org/philzen/oss/NoGuavaListsNewArrayListTest.java b/src/test/java/org/philzen/oss/NoGuavaListsNewArrayListTest.java
deleted file mode 100644
index 71d34b3b..00000000
--- a/src/test/java/org/philzen/oss/NoGuavaListsNewArrayListTest.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright 2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.junit.jupiter.api.Test;
-import org.openrewrite.DocumentExample;
-import org.openrewrite.java.JavaParser;
-import org.openrewrite.test.RecipeSpec;
-import org.openrewrite.test.RewriteTest;
-
-import static org.openrewrite.java.Assertions.java;
-
-// This is a test for the NoGuavaListsNewArrayList recipe, as an example of how to write a test for an imperative recipe.
-class NoGuavaListsNewArrayListTest implements RewriteTest {
-
- // Note, you can define defaults for the RecipeSpec and these defaults will be used for all tests.
- // In this case, the recipe and the parser are common. See below, on how the defaults can be overridden
- // per test.
- @Override
- public void defaults(RecipeSpec spec) {
- // Note how we directly instantiate the recipe class here
- spec.recipe(new NoGuavaListsNewArrayList())
- .parser(JavaParser.fromJavaVersion()
- .logCompilationWarningsAndErrors(true)
- // The before/after examples are using Guava classes, so we need to add the Guava library to the classpath
- .classpath("guava"));
- }
-
- @DocumentExample
- @Test
- void replaceWithNewArrayList() {
- rewriteRun(
- // There is an overloaded version or rewriteRun that allows the RecipeSpec to be customized specifically
- // for a given test. In this case, the parser for this test is configured to not log compilation warnings.
- spec -> spec
- .parser(JavaParser.fromJavaVersion()
- .logCompilationWarningsAndErrors(false)
- .classpath("guava")),
- // language=java
- java(
- """
- import com.google.common.collect.*;
-
- import java.util.List;
-
- class Test {
- List cardinalsWorldSeries = Lists.newArrayList();
- }
- """,
- """
- import java.util.ArrayList;
- import java.util.List;
-
- class Test {
- List cardinalsWorldSeries = new ArrayList<>();
- }
- """
- )
- );
- }
-
- @Test
- void replaceWithNewArrayListIterable() {
- rewriteRun(
- // language=java
- java(
- """
- import com.google.common.collect.*;
-
- import java.util.Collections;
- import java.util.List;
-
- class Test {
- List l = Collections.emptyList();
- List cardinalsWorldSeries = Lists.newArrayList(l);
- }
- """,
- """
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
-
- class Test {
- List l = Collections.emptyList();
- List cardinalsWorldSeries = new ArrayList<>(l);
- }
- """
- )
- );
- }
-
- @Test
- void replaceWithNewArrayListWithCapacity() {
- rewriteRun(
- // language=java
- java(
- """
- import com.google.common.collect.*;
-
- import java.util.ArrayList;
- import java.util.List;
-
- class Test {
- List cardinalsWorldSeries = Lists.newArrayListWithCapacity(2);
- }
- """,
- """
- import java.util.ArrayList;
- import java.util.List;
-
- class Test {
- List cardinalsWorldSeries = new ArrayList<>(2);
- }
- """)
- );
- }
-
- // This test is to show that the `super.visitMethodInvocation` is needed to ensure that nested method invocations are visited.
- @Test
- void showNeedForSuperVisitMethodInvocation() {
- rewriteRun(
- //language=java
- java(
- """
- import com.google.common.collect.*;
-
- import java.util.Collections;
- import java.util.List;
-
- class Test {
- List cardinalsWorldSeries = Collections.unmodifiableList(Lists.newArrayList());
- }
- """,
- """
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
-
- class Test {
- List cardinalsWorldSeries = Collections.unmodifiableList(new ArrayList<>());
- }
- """
- )
- );
- }
-
- // Often you want to make sure no changes are made when the target state is already achieved.
- // To do so only passs in a before state and no after state to the rewriteRun method SourceSpecs.
- @Test
- void noChangeNecessary() {
- rewriteRun(
- //language=java
- java(
- """
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.List;
-
- class Test {
- List cardinalsWorldSeries = Collections.unmodifiableList(new ArrayList<>());
- }
- """
- )
- );
- }
-}
diff --git a/src/test/java/org/philzen/oss/SimplifyTernaryTest.java b/src/test/java/org/philzen/oss/SimplifyTernaryTest.java
deleted file mode 100644
index 024cd88f..00000000
--- a/src/test/java/org/philzen/oss/SimplifyTernaryTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.junit.jupiter.api.Test;
-import org.openrewrite.DocumentExample;
-import org.openrewrite.test.RecipeSpec;
-import org.openrewrite.test.RewriteTest;
-
-import static org.openrewrite.java.Assertions.java;
-
-// This is a test for the SimplifyTernary recipe, as an example of how to write a test for a Refaster style recipe.
-class SimplifyTernaryTest implements RewriteTest {
-
- @Override
- public void defaults(RecipeSpec spec) {
- // Note that we instantiate a generated class here, with `Recipes` appended to the Refaster class name
- spec.recipe(new SimplifyTernaryRecipes());
- }
-
- @Test
- @DocumentExample
- void simplified() {
- rewriteRun(
- //language=java
- java(
- """
- class Test {
- boolean trueCondition1 = true ? true : false;
- boolean trueCondition2 = false ? false : true;
- boolean trueCondition3 = booleanExpression() ? true : false;
- boolean trueCondition4 = trueCondition1 && trueCondition2 ? true : false;
- boolean trueCondition5 = !true ? false : true;
- boolean trueCondition6 = !false ? true : false;
-
- boolean falseCondition1 = true ? false : true;
- boolean falseCondition2 = !false ? false : true;
- boolean falseCondition3 = booleanExpression() ? false : true;
- boolean falseCondition4 = trueCondition1 && trueCondition2 ? false : true;
- boolean falseCondition5 = !false ? false : true;
- boolean falseCondition6 = !true ? true : false;
-
- boolean binary1 = booleanExpression() && booleanExpression() ? true : false;
- boolean binary2 = booleanExpression() && booleanExpression() ? false : true;
- boolean binary3 = booleanExpression() || booleanExpression() ? true : false;
- boolean binary4 = booleanExpression() || booleanExpression() ? false : true;
-
- boolean booleanExpression() {
- return true;
- }
- }
- """,
- """
- class Test {
- boolean trueCondition1 = true;
- boolean trueCondition2 = true;
- boolean trueCondition3 = booleanExpression();
- boolean trueCondition4 = trueCondition1 && trueCondition2;
- boolean trueCondition5 = true;
- boolean trueCondition6 = true;
-
- boolean falseCondition1 = false;
- boolean falseCondition2 = false;
- boolean falseCondition3 = !booleanExpression();
- boolean falseCondition4 = !(trueCondition1 && trueCondition2);
- boolean falseCondition5 = false;
- boolean falseCondition6 = false;
-
- boolean binary1 = booleanExpression() && booleanExpression();
- boolean binary2 = !(booleanExpression() && booleanExpression());
- boolean binary3 = booleanExpression() || booleanExpression();
- boolean binary4 = !(booleanExpression() || booleanExpression());
-
- boolean booleanExpression() {
- return true;
- }
- }
- """
- )
- );
- }
-
- // It's good practice to also include a test that verifies that the recipe doesn't change anything when it shouldn't.
- @Test
- void unchanged() {
- rewriteRun(
- //language=java
- java(
- """
- class Test {
- boolean unchanged1 = booleanExpression() ? booleanExpression() : !booleanExpression();
- boolean unchanged2 = booleanExpression() ? true : !booleanExpression();
- boolean unchanged3 = booleanExpression() ? booleanExpression() : false;
-
- boolean booleanExpression() {
- return true;
- }
- }
- """
- )
- );
- }
-}
diff --git a/src/test/java/org/philzen/oss/StringIsEmptyTest.java b/src/test/java/org/philzen/oss/StringIsEmptyTest.java
deleted file mode 100644
index 823bda6e..00000000
--- a/src/test/java/org/philzen/oss/StringIsEmptyTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-import org.openrewrite.DocumentExample;
-import org.openrewrite.Recipe;
-import org.openrewrite.test.RecipeSpec;
-import org.openrewrite.test.RewriteTest;
-
-import static org.openrewrite.java.Assertions.java;
-
-@Disabled("Remove this annotation to run the tests once you implement the recipe")
-class StringIsEmptyTest implements RewriteTest {
-
- @Override
- public void defaults(RecipeSpec spec) {
- // Note that we instantiate a generated class here, with `Recipes` appended to the Refaster class name
- // You might need to trigger an explicit build of your project to generate this class with Ctrl + F9
-
- // TODO: Uncomment the line below once you have implemented the recipe
- //spec.recipe(new StringIsEmptyRecipe());
- }
-
- @DocumentExample
- @Test
- void standardizeStringIsEmpty() {
- // Notice how we pass in both the "before" and "after" code snippets
- // This indicates that we expect the recipe to transform the "before" code snippet into the "after" code snippet
- // If the recipe does not do this, the test will fail, and a diff will be shown
- rewriteRun(
- //language=java
- java(
- """
- class A {
- void test(String s, boolean b) {
- b = s.length() == 0;
- b = 0 == s.length();
- b = s.length() < 1;
- b = 1 > s.length();
- b = s.equals("");
- b = "".equals(s);
- b = s.isEmpty();
- }
- }
- """,
- """
- class A {
- void test(String s, boolean b) {
- b = s.isEmpty();
- b = s.isEmpty();
- b = s.isEmpty();
- b = s.isEmpty();
- b = s.isEmpty();
- b = s.isEmpty();
- b = s.isEmpty();
- }
- }
- """
- )
- );
- }
-
- @Test
- void showStringTypeMatchAndSimplification() {
- // Notice how the recipe will match anything that is of type String, not just local variables
- // Take a closer look at the last two replacements to `true` and `false`.
- // Open up the generated recipe and see if you can work out why those are replaced with booleans!
- rewriteRun(
- //language=java
- java(
- """
- class A {
- String field;
-
- String methodCall() {
- return "Hello World";
- }
-
- void test(String argument) {
- boolean bool1 = field.length() == 0;
- boolean bool2 = methodCall().length() == 0;
- boolean bool3 = argument.length() == 0;
- boolean bool4 = "".length() == 0;
- boolean bool5 = "literal".length() == 0;
- }
- }
- """,
- """
- class A {
- String field;
-
- String methodCall() {
- return "Hello World";
- }
-
- void test(String argument) {
- boolean bool1 = field.isEmpty();
- boolean bool2 = methodCall().isEmpty();
- boolean bool3 = argument.isEmpty();
- boolean bool4 = true;
- boolean bool5 = false;
- }
- }
- """
- )
- );
- }
-
- @Test
- void doNothingForStringIsEmpty() {
- // Notice how we only pass in the "before" code snippet, and not the "after" code snippet
- // That indicates that we expect the recipe to do nothing in this case, and will fail if it does anything
- rewriteRun(
- //language=java
- java(
- """
- class A {
- void test(String s, boolean b) {
- b = s.isEmpty();
- }
- }
- """
- )
- );
- }
-
- @Test
- void doNothingForCharSequence() {
- // When a different type is used, the recipe should do nothing
- // See if you can modify the recipe to handle CharSequence as well, or create a separate recipe for it
- rewriteRun(
- //language=java
- java(
- """
- class A {
- void test(CharSequence s, boolean b) {
- b = s.length() == 0;
- }
- }
- """
- )
- );
- }
-
- @Test
- void recipeDocumentation() {
- // This is a test to validate the correctness of the documentation in the recipe
- // By default you get generated documentation, but you can customize it through the RecipeDescriptor annotation
- Recipe recipe = null; // TODO: = new StringIsEmptyRecipe();
- String displayName = recipe.getDisplayName();
- String description = recipe.getDescription();
- assert "Standardize empty String checks".equals(displayName) : displayName;
- assert "Replace calls to `String.length() == 0` with `String.isEmpty()`.".equals(description) : description;
- }
-}
diff --git a/src/test/java/org/philzen/oss/UpdateConcoursePipelineTest.java b/src/test/java/org/philzen/oss/UpdateConcoursePipelineTest.java
deleted file mode 100644
index 7e07d4ce..00000000
--- a/src/test/java/org/philzen/oss/UpdateConcoursePipelineTest.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.junit.jupiter.api.Test;
-import org.openrewrite.DocumentExample;
-import org.openrewrite.test.RewriteTest;
-
-import java.nio.file.Paths;
-
-import static org.openrewrite.yaml.Assertions.yaml;
-
-class UpdateConcoursePipelineTest implements RewriteTest {
-
- @DocumentExample
- @Test
- void updateTagFilter() {
- rewriteRun(
- spec -> spec.recipe(new UpdateConcoursePipeline("8.2.0")),
- //language=yaml
- yaml(
- """
- ---
- resources:
- - name: tasks
- type: git
- source:
- uri: git@github.com:Example/concourse-tasks.git
- tag_filter: 8.1.0
- """,
- """
- ---
- resources:
- - name: tasks
- type: git
- source:
- uri: git@github.com:Example/concourse-tasks.git
- tag_filter: 8.2.0
- """,
- spec -> spec.path(Paths.get("ci/pipeline.yml"))
- )
- );
- }
-}
diff --git a/src/test/java/org/philzen/oss/UseApacheStringUtilsTest.java b/src/test/java/org/philzen/oss/UseApacheStringUtilsTest.java
deleted file mode 100644
index d144163b..00000000
--- a/src/test/java/org/philzen/oss/UseApacheStringUtilsTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.junit.jupiter.api.Test;
-import org.openrewrite.DocumentExample;
-import org.openrewrite.java.JavaParser;
-import org.openrewrite.test.RecipeSpec;
-import org.openrewrite.test.RewriteTest;
-
-import static org.openrewrite.java.Assertions.java;
-
-class UseApacheStringUtilsTest implements RewriteTest {
- @Override
- public void defaults(RecipeSpec spec) {
- spec.recipeFromResources("org.philzen.oss.UseApacheStringUtils")
- .parser(JavaParser.fromJavaVersion().classpath("commons-lang3", "spring-core"));
- }
-
- @DocumentExample
- @Test
- void replacesStringEquals() {
- rewriteRun(
- //language=java
- java(
- """
- import org.springframework.util.StringUtils;
-
- class A {
- boolean test(String s) {
- return StringUtils.containsWhitespace(s);
- }
- }
- """,
- """
- import org.apache.commons.lang3.StringUtils;
-
- class A {
- boolean test(String s) {
- return StringUtils.containsWhitespace(s);
- }
- }
- """
- )
- );
- }
-}
diff --git a/src/test/java/org/philzen/oss/UseOpenRewriteNullableTest.java b/src/test/java/org/philzen/oss/UseOpenRewriteNullableTest.java
deleted file mode 100644
index 154beada..00000000
--- a/src/test/java/org/philzen/oss/UseOpenRewriteNullableTest.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright 2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.philzen.oss;
-
-import org.junit.jupiter.api.Test;
-import org.openrewrite.DocumentExample;
-import org.openrewrite.java.JavaParser;
-import org.openrewrite.test.RecipeSpec;
-import org.openrewrite.test.RewriteTest;
-
-import static org.openrewrite.java.Assertions.java;
-
-// This is a test for the UseOpenRewriteNullable recipe, as an example of how to write a test for a declarative recipe.
-class UseOpenRewriteNullableTest implements RewriteTest {
- @Override
- public void defaults(RecipeSpec spec) {
- spec
- // Use the fully qualified class name of the recipe defined in src/main/resources/META-INF/rewrite/rewrite.yml
- .recipeFromResources("org.philzen.oss.UseOpenRewriteNullable")
- // The before and after text blocks contain references to annotations from these two classpath entries
- .parser(JavaParser.fromJavaVersion().classpath("annotations", "rewrite-core"));
- }
-
- @DocumentExample
- @Test
- void replacesNullableAnnotation() {
- rewriteRun(
- // Composite recipes are a hierarchy of recipes that can be applied in a single pass.
- // To view what the composite recipe does, you can use the RecipePrinter to print the recipe to the console.
- spec -> spec.printRecipe(() -> System.out::println),
- //language=java
- java(
- """
- import org.jetbrains.annotations.Nullable;
-
- class A {
- @Nullable
- String s;
- }
- """,
- """
- import org.openrewrite.internal.lang.Nullable;
-
- class A {
- @Nullable
- String s;
- }
- """
- )
- );
- }
-}
diff --git a/src/test/java/org/philzen/oss/research/AssertionsComparisonTest.java b/src/test/java/org/philzen/oss/research/AssertionsComparisonTest.java
new file mode 100644
index 00000000..ae3e99c4
--- /dev/null
+++ b/src/test/java/org/philzen/oss/research/AssertionsComparisonTest.java
@@ -0,0 +1,405 @@
+package org.philzen.oss.research;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.assertj.core.api.ThrowableAssert;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+import org.testng.Assert;
+
+import java.util.*;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+class AssertionsComparisonTest {
+
+ static final Collection ABC_list = ImmutableList.of("a", "b", "c");
+ static final Collection CBA_list = ImmutableList.of("c", "b", "a");
+
+ static final String[] ABC_array = {"a", "b", "c"};
+ static final String[] CBA_array = {"c", "b", "a"};
+
+ static final Map ASC_numMap = ImmutableMap.of(42, "Meaning of Life", 1312, "All Computers Are Broken");
+ static final Map DESC_numMap = ImmutableMap.of(1312, "All Computers Are Broken", 42, "Meaning of Life");
+ static final Map OTHER_map = ImmutableMap.of("foo", "far", "baz", "qux");
+
+ static final Set ABC_set = ImmutableSet.of("a", "b", "c");
+ static final Set CBA_set = ImmutableSet.of("c", "b", "a");
+ static final Set XYZ_set = ImmutableSet.of("x", "y", "z");
+
+ @SuppressWarnings("AssertBetweenInconvertibleTypes")
+ @Nested class assertEquals {
+
+ @Tag("mismatch")
+ @Test void array() {
+ thisWillFail(() -> Assert.assertEquals(ABC_array, CBA_array));
+ thisWillFail(() -> Assertions.assertEquals(CBA_array, ABC_array));
+
+ thisWillPass(() -> Assert.assertEquals(ABC_array.clone(), ABC_array));
+ thisWillFail(() -> Assertions.assertEquals(ABC_array, ABC_array.clone()));
+
+ // possible migration
+ thisWillPass(() -> Assertions.assertEquals(Arrays.toString(ABC_array), Arrays.toString(ABC_array.clone())));
+ }
+
+ @Tag("mismatch")
+ @Test void iterator() {
+ thisWillFail(() -> Assert.assertEquals(ABC_list.iterator(), CBA_list.iterator()));
+ thisWillFail(() -> Assertions.assertEquals(CBA_list.iterator(), ABC_list.iterator()));
+
+ thisWillPass(() -> Assert.assertEquals(ABC_list.iterator(), ABC_list.iterator()));
+ thisWillFail(() -> Assertions.assertEquals(ABC_list.iterator(), ABC_list.iterator()));
+
+ // possible migration
+ thisWillPass(() -> Assertions.assertArrayEquals(
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(ABC_list.iterator(), 0), false).toArray(),
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(ABC_list.iterator(), 0), false).toArray()
+ ));
+
+ thisWillFail(() -> Assertions.assertArrayEquals(
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(ABC_list.iterator(), 0), false).toArray(),
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(CBA_list.iterator(), 0), false).toArray()
+ ));
+ }
+
+ @Test void collection() {
+ Collection actual = new ArrayList<>(ABC_list);
+
+ thisWillPass(() -> Assert.assertEquals(actual, new ArrayList<>(ABC_list)));
+ thisWillPass(() -> Assertions.assertEquals(new ArrayList<>(ABC_list), actual));
+
+ thisWillFail(() -> Assert.assertEquals(actual, CBA_list));
+ thisWillFail(() -> Assertions.assertEquals(CBA_list, actual));
+ }
+
+ @Test void doubleDelta() {
+ final double actual = 1d;
+
+ thisWillPass(() -> Assert.assertEquals(actual, 2d, 1d));
+ thisWillPass(() -> Assertions.assertEquals(2d, actual, 1d));
+
+ thisWillFail(() -> Assert.assertEquals(actual, 2d, .999d));
+ 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;
+
+ thisWillPass(() -> Assert.assertEquals(actual, 2f, 1f));
+ thisWillPass(() -> Assertions.assertEquals(2f, actual, 1f));
+
+ 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);
+ }
+ }));
+ }
+
+ @Test void iterable() {
+ final Iterable actual = ABC_list;
+ final Iterable expected = new ArrayList<>(ABC_list);
+
+ thisWillPass(() -> Assert.assertEquals(actual, expected));
+ thisWillPass(() -> Assertions.assertEquals(expected, actual));
+
+ thisWillFail(() -> Assert.assertEquals(actual, CBA_list));
+ thisWillFail(() -> Assertions.assertEquals(CBA_list, actual));
+ }
+
+ @Test void map() {
+ final Map actual = ASC_numMap;
+
+ thisWillPass(() -> Assert.assertEquals(actual, new LinkedHashMap<>(ASC_numMap)));
+ thisWillPass(() -> Assertions.assertEquals(new LinkedHashMap<>(ASC_numMap), actual));
+
+ // order does not matter
+ thisWillPass(() -> Assert.assertEquals(actual, DESC_numMap));
+ thisWillPass(() -> Assertions.assertEquals(DESC_numMap, actual));
+
+ thisWillFail(() -> Assert.assertEquals(actual, OTHER_map));
+ thisWillFail(() -> Assertions.assertEquals(OTHER_map, actual));
+ }
+
+ @Test void set() {
+ final Set actual = ASC_numMap.keySet();
+
+ // order does not matter
+ thisWillPass(() -> Assert.assertEquals(actual, DESC_numMap.keySet()));
+ thisWillPass(() -> Assertions.assertEquals(DESC_numMap.keySet(), actual));
+
+ thisWillFail(() -> Assert.assertEquals(actual, OTHER_map.keySet()));
+ thisWillFail(() -> Assertions.assertEquals(OTHER_map.keySet(), actual));
+ }
+ }
+
+ @Tag("missing")
+ @Nested class assertEqualsNoOrder { // there is no equivalent in Jupiter
+
+ @Test void collection() {
+ final Collection expected = ABC_list;
+
+ thisWillPass(() -> Assert.assertEqualsNoOrder(CBA_list, expected));
+ thisWillFail(() -> Assert.assertEqualsNoOrder(List.of("x", "y"), expected));
+
+ // possible migration (string only)
+ thisWillPass(() -> Assertions.assertLinesMatch(expected.stream().sorted(), CBA_list.stream().sorted()));
+ thisWillFail(() -> Assertions.assertLinesMatch(expected.stream().sorted(), Stream.of("x", "y").sorted()));
+
+ // possible migration (any type)
+ thisWillPass(() -> Assertions.assertArrayEquals(
+ expected.stream().sorted().toArray(), CBA_list.stream().sorted().toArray()
+ ));
+ thisWillFail(() -> Assertions.assertArrayEquals(
+ expected.stream().sorted().toArray(), Stream.of("x", "y").sorted().toArray()
+ ));
+ }
+
+ @Test void iterator() {
+ thisWillPass(() -> Assert.assertEqualsNoOrder(CBA_list.iterator(), ABC_list.iterator()));
+ thisWillFail(() -> Assert.assertEqualsNoOrder(List.of("x", "y").iterator(), ABC_list.iterator()));
+
+ // possible migration
+ thisWillPass(() -> Assertions.assertArrayEquals(
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(CBA_list.iterator(), 0), false).sorted().toArray(),
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(ABC_list.iterator(), 0), false).sorted().toArray()
+ ));
+ thisWillFail(() -> Assertions.assertArrayEquals(
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(CBA_list.iterator(), 0), false).sorted().toArray(),
+ StreamSupport.stream(Spliterators.spliteratorUnknownSize(List.of("x", "y").iterator(), 0), false).sorted().toArray()
+ ));
+ }
+
+ @Test void objectArray() {
+ final Object[] expected = new String[]{"a", "b", "c", "d"};
+
+ thisWillPass(() -> Assert.assertEqualsNoOrder(new String[]{"b", "a", "d", "c"}, expected));
+
+ thisWillFail(() -> Assert.assertEqualsNoOrder(new String[]{"b", "b", "a", "d", "c"}, expected));
+
+ // possible migration
+ thisWillPass(() -> Assertions.assertArrayEquals(
+ Arrays.stream(expected).sorted().toArray(),
+ Arrays.stream(new String[]{"b", "a", "d", "c"}).sorted().toArray()
+ ));
+
+ // possible migration
+ thisWillFail(() -> Assertions.assertEquals(
+ Arrays.stream(expected).sorted(),
+ Arrays.stream(new String[]{"b", "b", "a", "d", "c"}).sorted()
+ ));
+ }
+ }
+
+ @Nested class assertNotEquals {
+
+ @Tag("mismatch")
+ @Test void array() {
+ thisWillPass(() -> Assert.assertNotEquals(ABC_array, CBA_array));
+ thisWillPass(() -> Assertions.assertNotEquals(CBA_array, ABC_array));
+
+ thisWillFail(() -> Assert.assertNotEquals(ABC_array.clone(), ABC_array));
+ thisWillPass(() -> Assertions.assertNotEquals(ABC_array, ABC_array.clone()));
+
+ // possible migration
+ thisWillFail(() -> Assertions.assertNotEquals(Arrays.toString(ABC_array), Arrays.toString(ABC_array.clone())));
+ }
+
+ @Test void collection() {
+ thisWillPass(() -> Assert.assertNotEquals(ABC_list, CBA_list));
+ thisWillPass(() -> Assertions.assertNotEquals(CBA_list, ABC_list));
+
+ thisWillFail(() -> Assert.assertNotEquals(ImmutableList.copyOf(ABC_list), ABC_list));
+ thisWillFail(() -> Assertions.assertNotEquals(ABC_list, ImmutableList.copyOf(ABC_list)));
+ }
+
+ @Tag("mismatch")
+ @Test void iterator() {
+ thisWillPass(() -> Assert.assertNotEquals(ABC_list.iterator(), CBA_list.iterator()));
+ thisWillPass(() -> Assertions.assertNotEquals(CBA_list.iterator(), ABC_list.iterator()));
+
+ thisWillFail(() -> Assert.assertNotEquals(ABC_list.iterator(), ABC_list.iterator()));
+ thisWillPass(() -> Assertions.assertNotEquals(ABC_list.iterator(), ABC_list.iterator()));
+
+ // possible migration
+ thisWillPass(() -> Assertions.assertNotEquals(
+ Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(ABC_list.iterator(), 0), false).toArray()),
+ Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(CBA_list.iterator(), 0), false).toArray())
+ ));
+ thisWillFail(() -> Assertions.assertNotEquals(
+ Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(ABC_list.iterator(), 0), false).toArray()),
+ Arrays.toString(StreamSupport.stream(Spliterators.spliteratorUnknownSize(ABC_list.iterator(), 0), false).toArray())
+ ));
+ }
+
+ @Test void map() {
+ thisWillPass(() -> Assert.assertNotEquals(ASC_numMap, OTHER_map));
+ thisWillPass(() -> Assertions.assertNotEquals(OTHER_map, ASC_numMap));
+
+ // order does not matter
+ thisWillFail(() -> Assert.assertNotEquals(ASC_numMap, DESC_numMap));
+ thisWillFail(() -> Assertions.assertNotEquals(DESC_numMap, ASC_numMap));
+
+ thisWillFail(() -> Assert.assertNotEquals(ASC_numMap, ASC_numMap));
+ thisWillFail(() -> Assertions.assertNotEquals(ASC_numMap, ASC_numMap));
+ }
+
+ @Test void set() {
+ thisWillPass(() -> Assert.assertNotEquals(ABC_set, XYZ_set));
+ thisWillPass(() -> Assertions.assertNotEquals(XYZ_set, ABC_set));
+
+ // order does not matter
+ thisWillFail(() -> Assert.assertNotEquals(ABC_set, CBA_set));
+ thisWillFail(() -> Assertions.assertNotEquals(CBA_set, ABC_set));
+
+ thisWillFail(() -> Assert.assertNotEquals(ABC_set, ABC_set));
+ thisWillFail(() -> Assertions.assertNotEquals(ABC_set, ABC_set));
+ }
+
+ @Test void doubleDelta() {
+ final double actual = 1d;
+ thisWillPass(() -> Assert.assertNotEquals(actual, 2d, .999d));
+ thisWillPass(() -> Assertions.assertNotEquals(2d, actual, .999d));
+
+ thisWillFail(() -> Assert.assertNotEquals(actual, 2d, 1d));
+ thisWillFail(() -> Assertions.assertNotEquals(2d, actual, 1d));
+ }
+
+ @Test void floatDelta() {
+ final float actual = 1f;
+
+ thisWillPass(() -> Assert.assertNotEquals(actual, 2f, .999f));
+ thisWillPass(() -> Assertions.assertNotEquals(2f, actual, .999f));
+
+ thisWillFail(() -> Assert.assertNotEquals(actual, 2f, 1f));
+ thisWillFail(() -> Assertions.assertNotEquals(2f, actual, 1f));
+ }
+ }
+
+ @Test void assertNotSame() {
+ final Collection expected = ABC_list;
+
+ thisWillPass(() -> Assertions.assertNotSame(new ArrayList<>(ABC_list), expected));
+ thisWillPass(() -> Assert.assertNotSame(expected, new ArrayList<>(ABC_list)));
+
+ thisWillFail(() -> Assert.assertNotSame(ABC_list, expected));
+ thisWillFail(() -> Assertions.assertNotSame(expected, ABC_list));
+ }
+
+ @Test void assertSame() {
+ final Collection expected = ABC_list;
+
+ thisWillPass(() -> Assert.assertSame(ABC_list, expected));
+ thisWillPass(() -> Assertions.assertSame(expected, ABC_list));
+
+ thisWillFail(() -> Assert.assertSame(new ArrayList<>(ABC_list), expected));
+ thisWillFail(() -> Assertions.assertSame(expected, new ArrayList<>(ABC_list)));
+ }
+
+ @SuppressWarnings("unused")
+ @Nested class assertThrows {
+
+ @Test void onlyRunnable() {
+ thisWillPass(() -> Assert.assertThrows(() -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assert.assertThrows(() -> { final var meaningful = 42; }));
+
+ // not a 1:1 equivalent in terms of arguments, but functionally completely equivalent
+ thisWillPass(() -> Assertions.assertThrows(Throwable.class, () -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assertions.assertThrows(Throwable.class, () -> { final var meaningful = 42; }));
+ }
+
+ @Test void expectedExceptionClassCheck() {
+ thisWillPass(() -> Assert.assertThrows(RuntimeException.class, () -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assert.assertThrows(Error.class, () -> { throw new RuntimeException(); }));
+
+ thisWillPass(() -> Assertions.assertThrows(RuntimeException.class, () -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assertions.assertThrows(Error.class, () -> { throw new RuntimeException(); }));
+
+ // additional test to show that they both do NOT check for the exact type, "instance of"-truthyness is enough
+ thisWillPass(() -> Assert.assertThrows(Exception.class, () -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assert.assertThrows(Error.class, () -> { throw new RuntimeException(); }));
+
+ thisWillPass(() -> Assertions.assertThrows(Exception.class, () -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assertions.assertThrows(Error.class, () -> { throw new RuntimeException(); }));
+ }
+
+ /**
+ * This is the actual function being called by {@link Assert#assertThrows(Assert.ThrowingRunnable)}
+ * and {@link Assert#assertThrows(Class, Assert.ThrowingRunnable)}, so these migrations are identical
+ */
+ @Test void expectThrows() {
+ thisWillPass(() -> Assert.expectThrows(RuntimeException.class, () -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assert.expectThrows(Error.class, () -> { throw new RuntimeException(); }));
+
+ thisWillPass(() -> Assertions.assertThrows(RuntimeException.class, () -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assertions.assertThrows(Error.class, () -> { throw new RuntimeException(); }));
+
+ // additional test to show that they both do NOT check for the exact type, "instance of"-truthyness is enough
+ thisWillPass(() -> Assert.expectThrows(Exception.class, () -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assert.expectThrows(Error.class, () -> { throw new RuntimeException(); }));
+
+ thisWillPass(() -> Assertions.assertThrows(Exception.class, () -> { throw new RuntimeException(); }));
+ thisWillFail(() -> Assertions.assertThrows(Error.class, () -> { throw new RuntimeException(); }));
+ }
+ }
+
+ void thisWillPass(final ThrowableAssert.ThrowingCallable code) {
+ assertThatNoException().isThrownBy(code);
+ }
+
+ void thisWillFail(final ThrowableAssert.ThrowingCallable code) {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(code);
+ }
+}
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")
+ ));
+ }
+}
diff --git a/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java b/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java
new file mode 100644
index 00000000..e3d203a3
--- /dev/null
+++ b/src/test/java/org/philzen/oss/testng/UpdateTestAnnotationToJunit5Test.java
@@ -0,0 +1,1268 @@
+package org.philzen.oss.testng;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+@SuppressWarnings({"groupsTestNG", "NewClassNamingConvention"})
+class UpdateTestAnnotationToJunit5Test implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(new UpdateTestAnnotationToJunit5());
+ }
+
+ @Nested class NoAttributes {
+
+ @Nested class onClass {
+
+ @Test void isMigratedToMethods() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ @Test
+ public class BazTest {
+
+ public void shouldDoStuff() {
+ //
+ }
+
+ public void shouldDoMoreStuff() {
+ //
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ public class BazTest {
+
+ @Test
+ public void shouldDoStuff() {
+ //
+ }
+
+ @Test
+ public void shouldDoMoreStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isMigratedToMethods_preservingOtherAnnotations() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ @Deprecated @Test
+ public class BazTest {
+
+ public void shouldDoStuff() {
+ //
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ @Deprecated
+ public class BazTest {
+
+ @Test
+ public void shouldDoStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ /**
+ * Non-public method are executed only if they are explicitly annotated with `@Test`
+ */
+ @Test void isMigratedOnlyToPublicMethods() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ @Test
+ public class BazTest {
+
+ public void shouldDoStuff() {
+ //
+ }
+
+ void thisAintNoTest() {
+ //
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ public class BazTest {
+
+ @Test
+ public void shouldDoStuff() {
+ //
+ }
+
+ void thisAintNoTest() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isMigratedToMethods_whenClassIsTyped() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ @Test
+ class BazTest {
+
+ public void shouldDoStuff() {
+ //
+ }
+
+ public void shouldDoMoreStuff() {
+ //
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ class BazTest {
+
+ @Test
+ public void shouldDoStuff() {
+ //
+ }
+
+ @Test
+ public void shouldDoMoreStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isMigratedToMethods_whenFullyQualified() {
+ // language=java
+ rewriteRun(java(
+ """
+ package de.foo.bar;
+
+ @org.testng.annotations.Test
+ public class BazTest {
+
+ public void shouldDoStuff() {
+ //
+ }
+
+ public void shouldDoMoreStuff() {
+ //
+ }
+ }
+ """,
+ """
+ package de.foo.bar;
+
+ public class BazTest {
+
+ @org.junit.jupiter.api.Test
+ public void shouldDoStuff() {
+ //
+ }
+
+ @org.junit.jupiter.api.Test
+ public void shouldDoMoreStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void migrationPreservesOtherAnnotations() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ @Test
+ @Deprecated
+ class BazTest {
+
+ public void shouldDoStuff() {
+ //
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ @Deprecated
+ class BazTest {
+
+ @Test
+ public void shouldDoStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void doesNotOverwriteMethodLevelTestAnnotations() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ @Test
+ class BazTest {
+
+ public void shouldDoStuff() { }
+
+ @Test(enabled = false)
+ public void shouldDoMoreStuff() { }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Disabled;
+ import org.junit.jupiter.api.Test;
+
+ class BazTest {
+
+ @Test
+ public void shouldDoStuff() { }
+
+ @Test
+ @Disabled
+ public void shouldDoMoreStuff() { }
+ }
+ """
+ ));
+ }
+
+ /**
+ * Inner class methods are executed by the TestNG runner only when they are explicitly annotated with @Test,
+ * class-level annotation will not make them execute.
+ */
+ @Test void doesNotAnnotateInnerClassMethods() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ @Test
+ public class BazTest {
+ public void test() { }
+
+ public static class Inner {
+ public void noTest() { }
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ public class BazTest {
+ @Test
+ public void test() { }
+
+ public static class Inner {
+ public void noTest() { }
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isRemoved_WhenOnlyInnerClassMethods() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ @Test
+ public class BazTest {
+ public static class Inner {
+ public void noTest() { }
+ }
+ }
+ """,
+ """
+ public class BazTest {
+ public static class Inner {
+ public void noTest() { }
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void noChangeOnOtherAnnotations() {
+ // language=java
+ rewriteRun(java(
+ """
+ @Deprecated
+ public class BazTest {
+ }
+ """
+ ));
+ }
+ }
+
+ @Test void isMigratedToJunitTestAnnotationWithoutParameters() {
+ // language=java
+ rewriteRun(java(
+ """
+ package de.foo.bar;
+
+ import org.testng.annotations.Test;
+
+ public class BazTest {
+
+ @Test
+ public void shouldDoStuff() {
+ //
+ }
+ }
+ """,
+ """
+ package de.foo.bar;
+
+ import org.junit.jupiter.api.Test;
+
+ public class BazTest {
+
+ @Test
+ public void shouldDoStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isMigratedPreservingOtherAnnotationsAndComments() {
+ // language=java
+ rewriteRun(java(
+ """
+ package org.openrewrite;
+ public @interface Issue {
+ String value();
+ }
+ """
+ ), java(
+ """
+ import org.testng.annotations.Test;
+ import org.openrewrite.Issue;
+
+ public class MyTest {
+
+ // some comments
+ @Issue("some issue")
+ @Test
+ public void test() {
+ }
+
+ // some more comments
+ @Test
+ public void test1() {
+ }
+
+ @Test
+ // even more comments
+ public void test2() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+ import org.openrewrite.Issue;
+
+ public class MyTest {
+
+ // some comments
+ @Issue("some issue")
+ @Test
+ public void test() {
+ }
+
+ // some more comments
+ @Test
+ public void test1() {
+ }
+
+ @Test
+ // even more comments
+ public void test2() {
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isMigratedWhenReferencedAsVariable() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+ public class MyTest {
+ Object o = Test.class;
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+ Object o = Test.class;
+ }
+ """
+ ));
+ }
+
+ @Test void isMigrated_whenUsedInJavadoc() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ /** @see org.testng.annotations.Test */
+ public class MyTest {
+ @Test
+ public void test() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ /** @see org.junit.jupiter.api.Test */
+ public class MyTest {
+ @Test
+ public void test() {
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void noTestAnnotationValues_sameLine_multipleImports() {
+ // language=java
+ rewriteRun(java(
+ """
+ package de.foo.bar;
+
+ import java.util.List;
+ import org.testng.annotations.Test;
+ import static org.assertj.core.api.Assertions.assertThat;
+
+ public class Baz {
+
+ @Test public void shouldDoStuff() {
+ //
+ }
+ }
+ """,
+ """
+ package de.foo.bar;
+
+ import org.junit.jupiter.api.Test;
+
+ import java.util.List;
+ import static org.assertj.core.api.Assertions.assertThat;
+
+ public class Baz {
+
+ @Test public void shouldDoStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void fullyQualified() {
+ // language=java
+ rewriteRun(java(
+ """
+ package de.foo.bar;
+
+ class Baz {
+
+ @org.testng.annotations.Test
+ public void shouldDoStuff() {
+ //
+ }
+ }
+ """,
+ """
+ package de.foo.bar;
+
+ class Baz {
+
+ @org.junit.jupiter.api.Test
+ public void shouldDoStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void fullyQualified_sameLineAsMethodDeclaration() {
+ // language=java
+ rewriteRun(java(
+ """
+ package de.foo.bar;
+
+ class Baz {
+
+ @org.testng.annotations.Test public void shouldDoStuff() {
+ //
+ }
+ }
+ """,
+ """
+ package de.foo.bar;
+
+ class Baz {
+
+ @org.junit.jupiter.api.Test public void shouldDoStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void mixedFullyQualifiedAndNot() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+ public class MyTest {
+ @org.testng.annotations.Test
+ public void feature1() {
+ }
+
+ @Test
+ public void feature2() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+ @org.junit.jupiter.api.Test
+ public void feature1() {
+ }
+
+ @Test
+ public void feature2() {
+ }
+ }
+ """
+ ));
+ }
+
+ @SuppressWarnings("JUnitMalformedDeclaration")
+ @Test void nestedClass() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ class Baz {
+
+ @Test public void shouldDoStuff() {
+ //
+ }
+
+ public class NestedGroupedTests {
+
+ @Test public void shouldDoStuff() {
+ //
+ }
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ class Baz {
+
+ @Test public void shouldDoStuff() {
+ //
+ }
+
+ public class NestedGroupedTests {
+
+ @Test public void shouldDoStuff() {
+ //
+ }
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void noChangeNecessary() {
+ // language=java
+ rewriteRun(java(
+ """
+ package de.foo.bar;
+
+ import org.junit.jupiter.api.Test;
+
+ class Baz {
+
+ @Test public void shouldDoStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void noChangeNecessary_fullyQualified() {
+ // language=java
+ rewriteRun(java(
+ """
+ package de.foo.bar;
+
+ class Baz {
+
+ @org.junit.jupiter.api.Test public void shouldDoStuff() {
+ //
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void noChangeNecessary_nestedClass() {
+ // language=java
+ rewriteRun(java(
+ """
+ package de.foo.bar;
+
+ import org.junit.jupiter.api.Nested;
+ import org.junit.jupiter.api.Test;
+
+ class Baz {
+
+ @Test public void shouldDoStuff() {
+ //
+ }
+
+ @Nested class NestedGroupedTests {
+
+ @Test public void shouldDoStuff() {
+ //
+ }
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @Nested class Attribute_description {
+
+ @Test void isMigratedToDisplayNameAnnotation_whenNotEmpty() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(description = "A test that tests something")
+ public void test() {
+ // some content
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.DisplayName;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ @DisplayName("A test that tests something")
+ public void test() {
+ // some content
+ }
+ }
+ """
+ ));
+ }
+
+ @SuppressWarnings("DefaultAnnotationParam")
+ @Test void isIgnored_whenEmpty() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(description = "")
+ public void test() {
+ // some content
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ // some content
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @Nested class Attribute_enabled {
+
+ @Test void isMigratedToDisabledAnnotation_whenFalse() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(enabled = false)
+ public void test() {
+ // some content
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Disabled;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ @Disabled
+ public void test() {
+ // some content
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @Nested class Attribute_expectedExceptions {
+
+ @Test void isMigratedToBodyWrappedInAssertThrows_forLiteralJavaExceptionThrown() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void test() {
+ throw new IllegalArgumentException("boom");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> {
+ throw new IllegalArgumentException("boom");
+ });
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void Attribute_expectedExceptionsMessageRegExp() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(expectedExceptions = IllegalArgumentException.class, expectedExceptionsMessageRegExp = "boom.*!")
+ public void test() {
+ throw new IllegalArgumentException("boom !");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ final Throwable thrown = Assertions.assertThrows(IllegalArgumentException.class, () -> {
+ throw new IllegalArgumentException("boom !");
+ });
+ Assertions.assertTrue(thrown.getMessage().matches("boom.*!"));
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isMigratedToBodyWrappedInAssertThrows_forLiteralCustomExceptionThrown() {
+ // language=java
+ rewriteRun(java(
+ """
+ package com.abc;
+ public class MyException extends Exception {
+ public MyException(String message) {
+ super(message);
+ }
+ }
+ """
+ ), java(
+ """
+ import com.abc.MyException;
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(expectedExceptions = MyException.class)
+ public void test() {
+ throw new MyException("my exception");
+ }
+ }
+ """,
+ """
+ import com.abc.MyException;
+ import org.junit.jupiter.api.Assertions;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ Assertions.assertThrows(MyException.class, () -> {
+ throw new MyException("my exception");
+ });
+ }
+ }
+ """
+ ));
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Test void isMigratedToBodyWrappedInAssertThrows_forSingleStatement() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(expectedExceptions = IndexOutOfBoundsException.class)
+ public void test() {
+ int arr = new int[]{}[0];
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ Assertions.assertThrows(IndexOutOfBoundsException.class, () -> {
+ int arr = new int[]{}[0];
+ });
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isMigratedToBodyWrappedInAssertThrows() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void test() {
+ String foo = "foo";
+ throw new IllegalArgumentException("boom");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> {
+ String foo = "foo";
+ throw new IllegalArgumentException("boom");
+ });
+ }
+ }
+ """
+ ));
+ }
+
+ @Nested class Array {
+
+ @Test void extractsFirstElementOfMultiple() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(expectedExceptions = { RuntimeException.class, IllegalAccessError.class, UnknownError.class } )
+ public void test() {
+ throw new RuntimeException("Whooopsie!");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ Assertions.assertThrows(RuntimeException.class, () -> {
+ throw new RuntimeException("Whooopsie!");
+ });
+ }
+ }
+ """
+ )
+ );
+ }
+ @SuppressWarnings("DefaultAnnotationParam")
+ @Test void doesNotAddAssert_ifEmpty() {
+ // language=java
+ rewriteRun(
+ java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(expectedExceptions = { } )
+ public void test() {
+ throw new RuntimeException("Not really caught nor tested");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ throw new RuntimeException("Not really caught nor tested");
+ }
+ }
+ """
+ )
+ );
+ }
+ }
+ }
+
+ @Nested class Attribute_groups {
+
+ @Test void isMigratedToSingleTagAnnotation_whenLiteralValue() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(groups = "Fast")
+ public void test() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Tag;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ @Tag("Fast")
+ public void test() {
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isMigratedToSingleTagAnnotation_whenSingleArrayValue() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(groups = { "Fast" })
+ public void test() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Tag;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ @Tag("Fast")
+ public void test() {
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isMigratedToMultipleTagAnnotations_forEveryArrayValue() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(groups = { "Fast", "Integration", "Regression-1312" })
+ public void test() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Tag;
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ @Tag("Fast")
+ @Tag("Integration")
+ @Tag("Regression-1312")
+ public void test() {
+ }
+ }
+ """
+ ));
+ }
+
+ @SuppressWarnings("DefaultAnnotationParam")
+ @Test void isIgnored_forEmptyArray() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(groups = { })
+ public void test() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ }
+ }
+ """
+ ));
+ }
+
+ @Test void isIgnored_forEmptyString() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(groups = "")
+ public void test() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+
+ public class MyTest {
+
+ @Test
+ public void test() {
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @Nested class Attribute_timeOut {
+
+ @Test void isMigratedToTimeoutAnnotation() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(timeOut = 500)
+ public void test() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+ import org.junit.jupiter.api.Timeout;
+
+ import java.util.concurrent.TimeUnit;
+
+ public class MyTest {
+
+ @Test
+ @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
+ public void test() {
+ }
+ }
+ """
+ ));
+ }
+
+ /**
+ * Unfortunately doesn't keep annotation on same line
+ * TODO investigate how this could be achieved
+ */
+ @Test void isMigratedToTimeoutAnnotation_butNotPreservingSameLine() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(timeOut = 500) public void test() {
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Test;
+ import org.junit.jupiter.api.Timeout;
+
+ import java.util.concurrent.TimeUnit;
+
+ public class MyTest {
+
+ @Test
+ @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
+ public void test() {
+ }
+ }
+ """
+ ));
+ }
+ }
+
+ @Nested class MultipleAttributes {
+
+ @Test void expectedExceptions_and_timeOut() {
+ // language=java
+ rewriteRun(java(
+ """
+ import org.testng.annotations.Test;
+
+ public class MyTest {
+
+ @Test(expectedExceptions = IllegalArgumentException.class, timeOut = 500)
+ public void test() {
+ throw new IllegalArgumentException("boom");
+ }
+ }
+ """,
+ """
+ import org.junit.jupiter.api.Assertions;
+ import org.junit.jupiter.api.Test;
+ import org.junit.jupiter.api.Timeout;
+
+ import java.util.concurrent.TimeUnit;
+
+ public class MyTest {
+
+ @Test
+ @Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
+ public void test() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> {
+ throw new IllegalArgumentException("boom");
+ });
+ }
+ }
+ """
+ ));
+ }
+ }
+}
diff --git a/src/test/java/org/philzen/oss/utils/CleanupTest.java b/src/test/java/org/philzen/oss/utils/CleanupTest.java
new file mode 100644
index 00000000..c3c559b8
--- /dev/null
+++ b/src/test/java/org/philzen/oss/utils/CleanupTest.java
@@ -0,0 +1,155 @@
+package org.philzen.oss.utils;
+
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.openrewrite.ExecutionContext;
+import org.openrewrite.internal.lang.NonNullApi;
+import org.openrewrite.java.AnnotationMatcher;
+import org.openrewrite.java.JavaIsoVisitor;
+import org.openrewrite.java.tree.J;
+import org.openrewrite.test.RecipeSpec;
+import org.openrewrite.test.RewriteTest;
+
+import static org.openrewrite.java.Assertions.java;
+
+@NonNullApi
+class CleanupTest implements RewriteTest {
+
+ @Override
+ public void defaults(RecipeSpec spec) {
+ spec.recipe(RewriteTest.toRecipe(() -> new JavaIsoVisitor<>() {
+
+ static final String TYPE_TO_REMOVE = "java.lang.Deprecated";
+ private static final AnnotationMatcher MATCHER = new AnnotationMatcher("@" + TYPE_TO_REMOVE);
+
+ @Override
+ public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
+ if (classDecl.getLeadingAnnotations().stream().noneMatch(MATCHER::matches)) {
+ return classDecl;
+ }
+
+ for (final J.Annotation annotation : classDecl.getLeadingAnnotations().stream().filter(MATCHER::matches).toList()) {
+ maybeRemoveImport(TYPE_TO_REMOVE);
+ classDecl = Cleanup.removeAnnotation(classDecl, annotation);
+ }
+
+ return classDecl;
+ }
+ }));
+ }
+
+ @Nested class removeAnnotation {
+
+ @Test void onClassWithModifier() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ @Deprecated
+ public class BazTest {
+
+ }
+ """,
+ """
+ public class BazTest {
+
+ }
+ """
+ )
+ );
+ }
+
+ @Test void onClassWithoutModifier() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ @Deprecated
+ class BazTest {
+
+ }
+ """,
+ """
+ class BazTest {
+
+ }
+ """
+ )
+ );
+ }
+
+ /**
+ * This test is a bit convoluted as it will fail with "Recipe was expected to make a change but made no changes"
+ * when not also including removal of an import (only happens on typed classes without modifiers)
+ */
+ @Test void onTypedClass() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import java.lang.Deprecated;
+
+ @Deprecated
+ class BazTest {
+
+ }
+ """,
+ """
+ class BazTest {
+
+ }
+ """
+ )
+ );
+ }
+
+ @Test void preservingExistingAnnotationsAfterIt() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import javax.annotation.concurrent.ThreadSafe;
+
+ @Deprecated
+ @ThreadSafe
+ public class BazTest {
+
+ }
+ """,
+ """
+ import javax.annotation.concurrent.ThreadSafe;
+
+ @ThreadSafe
+ public class BazTest {
+
+ }
+ """
+ )
+ );
+ }
+
+ @Test void preservingExistingAnnotationsBeforeIt() {
+ rewriteRun(
+ // language=java
+ java(
+ """
+ import javax.annotation.concurrent.ThreadSafe;
+
+ @ThreadSafe @Deprecated
+ public class BazTest {
+
+ }
+ """,
+ """
+ import javax.annotation.concurrent.ThreadSafe;
+
+ @ThreadSafe
+ public class BazTest {
+
+ }
+ """
+ )
+ );
+ }
+ }
+}