From c4ab9a5c5cd0c0e8a992a02bf9a5177d68638a1d Mon Sep 17 00:00:00 2001 From: Jente Sondervorst Date: Sun, 3 Nov 2024 13:40:39 +0100 Subject: [PATCH] Initial support for DbRider migration from junit4 to junit5 (#627) * Initial draft of the ExecutionListenerToDbRiderAnnotation ScanningRecipe fixes #624 * Added missing import * Ran best practices * Finishing up on the declarative recipe * Finishing up on the declarative recipe * Minimize tests * Apply formatter * Review comment refactoring * Polish to static constructor & reduce visibility * Final polish: fix header year & only use ridr-junit5 in src/main --------- Co-authored-by: Jente Sondervorst Co-authored-by: Tim te Beek --- build.gradle.kts | 4 +- .../ExecutionListenerToDbRiderAnnotation.java | 265 ++++++++++++++++++ .../java/testing/dbrider/package-info.java | 21 ++ .../rewrite/classpath/rider-junit5-1.44.0.jar | Bin 0 -> 25791 bytes .../resources/META-INF/rewrite/dbrider.yml | 32 +++ .../resources/META-INF/rewrite/junit5.yml | 1 + ...onListenerToDbRiderAnnotationUnitTest.java | 233 +++++++++++++++ .../rewrite/classpath/rider-spring-1.18.0.jar | Bin 0 -> 7276 bytes 8 files changed, 553 insertions(+), 3 deletions(-) create mode 100644 src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java create mode 100644 src/main/java/org/openrewrite/java/testing/dbrider/package-info.java create mode 100644 src/main/resources/META-INF/rewrite/classpath/rider-junit5-1.44.0.jar create mode 100644 src/main/resources/META-INF/rewrite/dbrider.yml create mode 100644 src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java create mode 100644 src/test/resources/META-INF/rewrite/classpath/rider-spring-1.18.0.jar diff --git a/build.gradle.kts b/build.gradle.kts index 71a58d2a0..d29963509 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -26,6 +26,7 @@ recipeDependencies { parserClasspath("org.powermock:powermock-core:1.7.+") parserClasspath("com.squareup.okhttp3:mockwebserver:4.10.0") parserClasspath("org.springframework:spring-test:6.1.12") + parserClasspath("com.github.database-rider:rider-junit5:1.44.0") } val rewriteVersion = rewriteRecipe.rewriteVersion.get() @@ -69,7 +70,4 @@ dependencies { testRuntimeOnly("org.mockito.kotlin:mockito-kotlin:latest.release") testRuntimeOnly("org.testcontainers:testcontainers:latest.release") testRuntimeOnly("org.testcontainers:nginx:latest.release") - -// testImplementation("org.hamcrest:hamcrest:latest.release") -// testImplementation("org.assertj:assertj-core:latest.release") } diff --git a/src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java b/src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java new file mode 100644 index 000000000..93c1ac817 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotation.java @@ -0,0 +1,265 @@ +/* + * 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.openrewrite.java.testing.dbrider; + +import org.jspecify.annotations.Nullable; +import org.openrewrite.ExecutionContext; +import org.openrewrite.Preconditions; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.AnnotationMatcher; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.JavaParser; +import org.openrewrite.java.JavaTemplate; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.Expression; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.Space; + +import java.util.Comparator; +import java.util.List; + +public class ExecutionListenerToDbRiderAnnotation extends Recipe { + + private static final AnnotationMatcher EXECUTION_LISTENER_ANNOTATION_MATCHER = new AnnotationMatcher("@org.springframework.test.context.TestExecutionListeners"); + private static final AnnotationMatcher DBRIDER_ANNOTATION_MATCHER = new AnnotationMatcher("@com.github.database.rider.junit5.api.DBRider"); + private static final String DBRIDER_TEST_EXECUTION_LISTENER = "com.github.database.rider.spring.DBRiderTestExecutionListener"; + + @Override + public String getDisplayName() { + return "Migrate the `DBRiderTestExecutionListener` to the `@DBRider` annotation"; + } + + @Override + public String getDescription() { + return "Migrate the `DBRiderTestExecutionListener` to the `@DBRider` annotation. " + + "This recipe is useful when migrating from JUnit 4 `dbrider-spring` to JUnit 5 `dbrider-junit5`."; + } + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(DBRIDER_TEST_EXECUTION_LISTENER, true), new JavaIsoVisitor() { + + @Override + public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDeclaration, ExecutionContext ctx) { + J.ClassDeclaration cd = super.visitClassDeclaration(classDeclaration, ctx); + DbRiderExecutionListenerContext context = DbRiderExecutionListenerContext.ofClass(cd); + if (!context.shouldMigrate()) { + return cd; + } + if (context.shouldAddDbRiderAnnotation()) { + cd = JavaTemplate.builder("@DBRider") + .imports("com.github.database.rider.junit5.api.DBRider") + .javaParser(JavaParser.fromJavaVersion().classpathFromResources(ctx, "rider-junit5-1.44")) + .build() + .apply(getCursor(), cd.getCoordinates().addAnnotation(Comparator.comparing(J.Annotation::getSimpleName))); + maybeAddImport("com.github.database.rider.junit5.api.DBRider"); + } + Space prefix = cd.getLeadingAnnotations().get(cd.getLeadingAnnotations().size() - 1).getPrefix(); + return cd.withLeadingAnnotations(ListUtils.map(cd.getLeadingAnnotations(), annotation -> { + if (annotation != null && EXECUTION_LISTENER_ANNOTATION_MATCHER.matches(annotation)) { + J.Annotation executionListenerAnnotation = context.getExecutionListenerAnnotation(); + maybeRemoveImport(DBRIDER_TEST_EXECUTION_LISTENER); + maybeRemoveImport("org.springframework.test.context.TestExecutionListeners.MergeMode"); + maybeRemoveImport("org.springframework.test.context.TestExecutionListeners"); + if (executionListenerAnnotation != null) { + return executionListenerAnnotation + .withArguments(firstItemPrefixWorkaround(executionListenerAnnotation.getArguments())) + .withPrefix(prefix); + } + return null; + } + return annotation; + })); + } + }); + } + + private static class DbRiderExecutionListenerContext { + private J.@Nullable Annotation testExecutionListenerAnnotation; + private boolean dbriderFound = false; + private J.@Nullable NewArray listeners; + private J.@Nullable FieldAccess listener; + private @Nullable Expression inheritListeners; + private @Nullable Expression mergeMode; + + static DbRiderExecutionListenerContext ofClass(J.ClassDeclaration clazz) { + DbRiderExecutionListenerContext context = new DbRiderExecutionListenerContext(); + clazz.getLeadingAnnotations().forEach(annotation -> { + if (EXECUTION_LISTENER_ANNOTATION_MATCHER.matches(annotation)) { + context.testExecutionListenersFound(annotation); + } else if (DBRIDER_ANNOTATION_MATCHER.matches(annotation)) { + context.dbriderFound = true; + } + }); + return context; + } + + private void testExecutionListenersFound(final J.Annotation annotation) { + testExecutionListenerAnnotation = annotation; + if (annotation.getArguments() != null) { + annotation.getArguments().forEach(arg -> { + if (arg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) arg; + switch (((J.Identifier) assignment.getVariable()).getSimpleName()) { + case "value": + case "listeners": + if (assignment.getAssignment() instanceof J.NewArray) { + listeners = (J.NewArray) assignment.getAssignment(); + } + break; + case "inheritListeners": + inheritListeners = assignment.getAssignment(); + break; + case "mergeMode": + mergeMode = assignment.getAssignment(); + break; + } + } else if (arg instanceof J.NewArray) { + listeners = (J.NewArray) arg; + } else if (arg instanceof J.FieldAccess) { + listener = (J.FieldAccess) arg; + } + }); + } + } + + public boolean shouldMigrate() { + return isTestExecutionListenerForDbRider() && !dbriderFound; + } + + public boolean shouldAddDbRiderAnnotation() { + if (dbriderFound) { + return false; + } + + return isTestExecutionListenerForDbRider(); + } + + public J.@Nullable Annotation getExecutionListenerAnnotation() { + if (isTestExecutionListenerForDbRider()) { + if (canTestExecutionListenerBeRemoved()) { + return null; + } + if (testExecutionListenerAnnotation != null && testExecutionListenerAnnotation.getArguments() != null) { + return testExecutionListenerAnnotation.withArguments(ListUtils.map(testExecutionListenerAnnotation.getArguments(), arg -> { + if (arg instanceof J.Assignment) { + J.Assignment assignment = (J.Assignment) arg; + Expression newValue = assignment.getAssignment(); + switch (((J.Identifier) assignment.getVariable()).getSimpleName()) { + case "value": + case "listeners": + if (assignment.getAssignment() instanceof J.NewArray) { + newValue = getMigratedListeners(); + } + break; + case "inheritListeners": + newValue = getMigratedInheritListeners(); + break; + case "mergeMode": + newValue = getMigratedMergeMode(); + break; + } + if (newValue == null) { + return null; + } + return assignment.withAssignment(newValue); + } else if (arg instanceof J.NewArray) { + return getMigratedListeners(); + } + if (arg instanceof J.FieldAccess && isTypeReference(arg, DBRIDER_TEST_EXECUTION_LISTENER)) { + return null; + } + return arg; + })); + } + } + + return testExecutionListenerAnnotation; + } + + // We can only remove an execution listener annotation if: + // - InheritListeners was null or true + // - MergeMode was TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS + // By default, the TestExecutionListeners.MergeMode is REPLACE_DEFAULTS so if we remove the annotation, other defaults would kick in. + private boolean canTestExecutionListenerBeRemoved() { + if (listener == null && listeners != null && listeners.getInitializer() != null && + listeners.getInitializer().stream().allMatch(listener -> isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER))) { + return (getMigratedInheritListeners() == null && getMigratedMergeMode() != null); + } + return false; + } + + private @Nullable Expression getMigratedMergeMode() { + if (mergeMode != null && mergeMode instanceof J.FieldAccess && "REPLACE_DEFAULTS".equals(((J.FieldAccess) mergeMode).getName().getSimpleName())) { + return null; + } + return mergeMode; + } + + private @Nullable Expression getMigratedInheritListeners() { + if (inheritListeners != null && (inheritListeners instanceof J.Literal && Boolean.TRUE.equals(((J.Literal) inheritListeners).getValue()))) { + return null; + } + return inheritListeners; + } + + // Remove the DBRiderTestExecutionListener from the listeners array + // If the listeners array is empty after removing the DBRiderTestExecutionListener, return null so that the array itself can be removed + private J.@Nullable NewArray getMigratedListeners() { + if (listeners != null && listeners.getInitializer() != null) { + List newListeners = ListUtils.map(listeners.getInitializer(), listener -> { + if (listener instanceof J.FieldAccess && isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER)) { + return null; + } + return listener; + }); + if (newListeners.isEmpty()) { + return null; + } + return listeners.withInitializer(firstItemPrefixWorkaround(newListeners)); + } + return listeners; + } + + private boolean isTestExecutionListenerForDbRider() { + if (listener != null) { + return isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER); + } + if (listeners != null && listeners.getInitializer() != null) { + return listeners.getInitializer().stream().anyMatch(listener -> isTypeReference(listener, DBRIDER_TEST_EXECUTION_LISTENER)); + } + return false; + } + + private static boolean isTypeReference(Expression expression, String type) { + return expression.getType() instanceof JavaType.Parameterized && + ((JavaType.Parameterized) expression.getType()).getFullyQualifiedName().equals("java.lang.Class") && + ((JavaType.Parameterized) expression.getType()).getTypeParameters().size() == 1 && + ((JavaType.Parameterized) expression.getType()).getTypeParameters().get(0) instanceof JavaType.Class && + ((JavaType.Class) ((JavaType.Parameterized) expression.getType()).getTypeParameters().get(0)).getFullyQualifiedName().equals(type); + } + } + + private static @Nullable List firstItemPrefixWorkaround(@Nullable List list) { + if (list == null || list.isEmpty()) { + return list; + } + return ListUtils.mapFirst(list, t -> t.withPrefix(t.getPrefix().withWhitespace(t.getPrefix().getLastWhitespace().replaceAll(" $", "")))); + } +} diff --git a/src/main/java/org/openrewrite/java/testing/dbrider/package-info.java b/src/main/java/org/openrewrite/java/testing/dbrider/package-info.java new file mode 100644 index 000000000..8a8f30054 --- /dev/null +++ b/src/main/java/org/openrewrite/java/testing/dbrider/package-info.java @@ -0,0 +1,21 @@ +/* + * 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. + */ +@NullMarked +@NonNullFields +package org.openrewrite.java.testing.dbrider; + +import org.jspecify.annotations.NullMarked; +import org.openrewrite.internal.lang.NonNullFields; diff --git a/src/main/resources/META-INF/rewrite/classpath/rider-junit5-1.44.0.jar b/src/main/resources/META-INF/rewrite/classpath/rider-junit5-1.44.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c4bbfc62831e7c154d4f42fc7884d29f12c852d GIT binary patch literal 25791 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 F{SWgfZLt6V literal 0 HcmV?d00001 diff --git a/src/main/resources/META-INF/rewrite/dbrider.yml b/src/main/resources/META-INF/rewrite/dbrider.yml new file mode 100644 index 000000000..a6ecc2980 --- /dev/null +++ b/src/main/resources/META-INF/rewrite/dbrider.yml @@ -0,0 +1,32 @@ +# +# 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.openrewrite.java.testing.dbrider.MigrateDbRiderSpringToDbRiderJUnit5 +displayName: Migrate rider-spring (JUnit4) to rider-junit5 (JUnit5) +description: This recipe will migrate the necessary dependencies and annotations from DbRider with JUnit4 to JUnit5 in a Spring application. +tags: + - testing + - dbrider + - spring +recipeList: + - org.openrewrite.java.testing.dbrider.ExecutionListenerToDbRiderAnnotation + - org.openrewrite.java.dependencies.ChangeDependency: + oldGroupId: com.github.database-rider + oldArtifactId: rider-spring + newArtifactId: rider-junit5 + newVersion: 1.x +--- \ No newline at end of file diff --git a/src/main/resources/META-INF/rewrite/junit5.yml b/src/main/resources/META-INF/rewrite/junit5.yml index a8a38c9cf..4cbe3fe26 100755 --- a/src/main/resources/META-INF/rewrite/junit5.yml +++ b/src/main/resources/META-INF/rewrite/junit5.yml @@ -142,6 +142,7 @@ recipeList: - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: org.jbehave.core.junit.JUnitStories newFullyQualifiedTypeName: org.jbehave.core.junit.JupiterStories + - org.openrewrite.java.testing.dbrider.MigrateDbRiderSpringToDbRiderJUnit5 --- type: specs.openrewrite.org/v1beta/recipe diff --git a/src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java b/src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java new file mode 100644 index 000000000..b1820b6fc --- /dev/null +++ b/src/test/java/org/openrewrite/java/testing/dbrider/ExecutionListenerToDbRiderAnnotationUnitTest.java @@ -0,0 +1,233 @@ +/* + * 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.openrewrite.java.testing.dbrider; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +class ExecutionListenerToDbRiderAnnotationUnitTest implements RewriteTest { + @Override + public void defaults(RecipeSpec spec) { + spec.parser(JavaParser.fromJavaVersion().classpathFromResources(new InMemoryExecutionContext(), "spring-test-6.1", "rider-spring-1.18", "rider-junit5-1.44")) + .recipe(new ExecutionListenerToDbRiderAnnotation()); + } + + @Test + @DocumentExample + void replaceAnnotationIfOnlyDbRiderListenerMergedWithDefaults() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS, listeners = {DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerReplacedDefaults() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.REPLACE_DEFAULTS, listeners = {DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerAndNoMergeModeSpecified() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(listeners = {DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerThroughValue() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(DBRiderTestExecutionListener.class) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void addAnnotationIfOnlyDbRiderListenerThroughValueArray() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners({DBRiderTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners + public class Sample {} + """ + ) + ); + } + + @Test + void keepAnnotationIfOnlyDbRiderListenerSetAndNonDefaultSetting() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(value = {DBRiderTestExecutionListener.class}, inheritListeners = false) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners(inheritListeners = false) + public class Sample {} + """ + ) + ); + } + + @Test + void removeListenerFromOtherListeners() { + rewriteRun( + //language=java + java( + """ + import org.springframework.test.context.TestExecutionListeners; + import org.springframework.test.context.support.DirtiesContextTestExecutionListener; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @TestExecutionListeners(mergeMode = TestExecutionListeners.MergeMode.REPLACE_DEFAULTS, listeners = {DBRiderTestExecutionListener.class, DirtiesContextTestExecutionListener.class}) + public class Sample {} + """, + """ + import org.springframework.test.context.TestExecutionListeners; + import org.springframework.test.context.support.DirtiesContextTestExecutionListener; + import com.github.database.rider.junit5.api.DBRider; + + @DBRider + @TestExecutionListeners(listeners = {DirtiesContextTestExecutionListener.class}) + public class Sample {} + """ + ) + ); + } + + @Test + void doNotTouchIfNoListenerPresent() { + rewriteRun( + //language=java + java( + """ + @Deprecated + public class Sample {} + """ + ) + ); + } + + @Test + void doNotTouchIfDbRiderAlreadyPresent() { + rewriteRun( + //language=java + java( + """ + import com.github.database.rider.junit5.api.DBRider; + import org.springframework.test.context.TestExecutionListeners; + import com.github.database.rider.spring.DBRiderTestExecutionListener; + + @DBRider + @TestExecutionListeners(listeners = {DBRiderTestExecutionListener.class}, inheritListeners = false) + public class Sample {} + """ + ) + ); + } +} diff --git a/src/test/resources/META-INF/rewrite/classpath/rider-spring-1.18.0.jar b/src/test/resources/META-INF/rewrite/classpath/rider-spring-1.18.0.jar new file mode 100644 index 0000000000000000000000000000000000000000..6809b154b4374fff36fdb86b4bcc027caf6d82f5 GIT binary patch literal 7276 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 Bm0