diff --git a/extensions/build.gradle b/extensions/build.gradle index 438c5ec..505cf2b 100644 --- a/extensions/build.gradle +++ b/extensions/build.gradle @@ -9,6 +9,7 @@ dependencies { testImplementation libs.assertj testImplementation libs.bundles.junit.jupiter testImplementation libs.commons.io + testImplementation libs.compile.testing testImplementation libs.dummymaker testImplementation libs.guava testImplementation libs.mapstruct.core diff --git a/extensions/src/test/java/org/mapstruct/extensions/spring/converter/AbstractProcessorTest.java b/extensions/src/test/java/org/mapstruct/extensions/spring/converter/AbstractProcessorTest.java index 621bf03..bce6625 100644 --- a/extensions/src/test/java/org/mapstruct/extensions/spring/converter/AbstractProcessorTest.java +++ b/extensions/src/test/java/org/mapstruct/extensions/spring/converter/AbstractProcessorTest.java @@ -1,14 +1,12 @@ package org.mapstruct.extensions.spring.converter; import static com.google.common.collect.Iterables.concat; -import static java.nio.file.Files.createTempDirectory; import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; import static javax.lang.model.element.Modifier.*; -import static javax.tools.StandardLocation.CLASS_OUTPUT; +import com.google.testing.compile.Compilation; +import com.google.testing.compile.Compiler; import com.squareup.javapoet.*; -import java.io.IOException; import java.util.Set; import javax.annotation.processing.Processor; import javax.tools.*; @@ -49,27 +47,14 @@ protected static JavaFileObject toJavaFileObject(JavaFile.Builder fileBuilder) { COMPONENT_JAVA_FILE_OBJECT, LAZY_JAVA_FILE_OBJECT); - protected static boolean compile( - final Processor processor, final JavaFileObject... additionalCompilationUnits) - throws IOException { + protected static Compilation compile( + final Processor processor, final JavaFileObject... additionalCompilationUnits) { return compile(processor, concat(COMMON_COMPILATION_UNITS, asList(additionalCompilationUnits))); } - protected static boolean compile( - final Processor processor, final Iterable compilationUnits) - throws IOException { - final var compiler = ToolProvider.getSystemJavaCompiler(); - - final var diagnostics = new DiagnosticCollector(); - final var fileManager = compiler.getStandardFileManager(diagnostics, null, null); - fileManager.setLocation(CLASS_OUTPUT, singletonList(createTempDirectory("classes").toFile())); - - final var task = compiler.getTask(null, fileManager, diagnostics, null, null, compilationUnits); - task.setProcessors(singletonList(processor)); - - final var success = task.call(); - diagnostics.getDiagnostics().forEach(System.err::println); - return success; + protected static Compilation compile( + final Processor processor, final Iterable compilationUnits) { + return Compiler.javac().withProcessors(processor).compile(compilationUnits); } private static TypeSpec buildSimpleModelClassTypeSpec(final String className) { diff --git a/extensions/src/test/java/org/mapstruct/extensions/spring/converter/ConverterMapperProcessorTest.java b/extensions/src/test/java/org/mapstruct/extensions/spring/converter/ConverterMapperProcessorTest.java index 676af56..cff7b1f 100644 --- a/extensions/src/test/java/org/mapstruct/extensions/spring/converter/ConverterMapperProcessorTest.java +++ b/extensions/src/test/java/org/mapstruct/extensions/spring/converter/ConverterMapperProcessorTest.java @@ -1,18 +1,23 @@ package org.mapstruct.extensions.spring.converter; +import static com.google.common.collect.Iterables.concat; +import static com.google.testing.compile.CompilationSubject.assertThat; +import static com.google.testing.compile.Compiler.javac; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.SOURCE; import static javax.lang.model.element.Modifier.*; import static org.assertj.core.api.BDDAssertions.then; +import static org.assertj.core.api.InstanceOfAssertFactories.list; import static org.mockito.ArgumentMatchers.any; +import com.google.testing.compile.Compilation; import com.squareup.javapoet.*; -import java.io.IOException; import java.io.Writer; import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.lang.ref.WeakReference; import java.time.Clock; +import java.util.List; import java.util.Locale; import javax.tools.*; import org.junit.jupiter.api.Test; @@ -51,7 +56,7 @@ class ConverterMapperProcessorTest extends AbstractProcessorTest { @Captor private ArgumentCaptor descriptorArgumentCaptor; - private boolean compile(final JavaFileObject... additionalCompilationUnits) throws IOException { + private Compilation compile(final JavaFileObject... additionalCompilationUnits) { return compile(processor, additionalCompilationUnits); } @@ -72,46 +77,75 @@ private static TypeSpec buildConfigClassWithExternalConversion(final String clas } @Test - void shouldCompileSimpleConverterMapper() throws IOException { + void shouldCompileSimpleConverterMapper() { // Given final JavaFile mapperFile = JavaFile.builder( PACKAGE_NAME, converterMapperWithoutGenericSourceOrTargetTypeBuilder().build()) .build(); - // When - final boolean compileResult = compile(mapperFile.toJavaFileObject()); - - // Then - then(compileResult).isTrue(); + // When - Then + assertThat( + javac() + .withProcessors(processor) + .compile(concat(List.of(mapperFile.toJavaFileObject()), COMMON_COMPILATION_UNITS))) + .succeeded(); + BDDMockito.then(adapterGenerator) + .should() + .writeGeneratedCodeToOutput(descriptorArgumentCaptor.capture(), any(Writer.class)); + then(descriptorArgumentCaptor.getValue()) + .isNotNull() + .extracting(ConversionServiceAdapterDescriptor::getFromToMappings) + .asInstanceOf(list(FromToMapping.class)) + .containsExactly(new FromToMapping().source(CAR_CLASS_NAME).target(CAR_DTO_CLASS_NAME)); } @Test - void shouldCompileConverterMapperWithGenericSourceType() throws IOException { + void shouldCompileConverterMapperWithGenericSourceType() { // Given final JavaFile mapperFile = JavaFile.builder(PACKAGE_NAME, converterMapperWithGenericSourceTypeBuilder().build()) .build(); - // When - final boolean compileResult = compile(mapperFile.toJavaFileObject()); - - // Then - then(compileResult).isTrue(); + // When - Then + assertThat( + javac() + .withProcessors(processor) + .compile(concat(List.of(mapperFile.toJavaFileObject()), COMMON_COMPILATION_UNITS))) + .succeeded(); + BDDMockito.then(adapterGenerator) + .should() + .writeGeneratedCodeToOutput(descriptorArgumentCaptor.capture(), any(Writer.class)); + then(descriptorArgumentCaptor.getValue()) + .isNotNull() + .extracting(ConversionServiceAdapterDescriptor::getFromToMappings) + .asInstanceOf(list(FromToMapping.class)) + .containsExactly( + new FromToMapping().source(genericSourceTypeName()).target(CAR_DTO_CLASS_NAME)); } @Test - void shouldCompileConverterMapperWithGenericTargetType() throws IOException { + void shouldCompileConverterMapperWithGenericTargetType() { // Given final JavaFile mapperFile = JavaFile.builder(PACKAGE_NAME, converterMapperWithGenericTargetTypeBuilder().build()) .build(); - // When - final boolean compileResult = compile(mapperFile.toJavaFileObject()); - - // Then - then(compileResult).isTrue(); + // When - Then + assertThat( + javac() + .withProcessors(processor) + .compile(concat(List.of(mapperFile.toJavaFileObject()), COMMON_COMPILATION_UNITS))) + .succeeded(); + BDDMockito.then(adapterGenerator) + .should() + .writeGeneratedCodeToOutput(descriptorArgumentCaptor.capture(), any(Writer.class)); + then(descriptorArgumentCaptor.getValue()) + .isNotNull() + .extracting(ConversionServiceAdapterDescriptor::getFromToMappings) + .asInstanceOf(list(FromToMapping.class)) + .containsExactly( + new FromToMapping().source(CAR_CLASS_NAME).target(genericTargetTypeName())); } private static TypeSpec.Builder converterMapperWithoutGenericSourceOrTargetTypeBuilder() { @@ -127,15 +161,23 @@ private static TypeSpec.Builder converterMapperBuilder( } private static TypeSpec.Builder converterMapperWithGenericSourceTypeBuilder() { - return converterMapperBuilder( - ParameterizedTypeName.get(ClassName.get(WeakReference.class), CAR_CLASS_NAME), - CAR_DTO_CLASS_NAME); + return converterMapperBuilder(genericSourceTypeName(), CAR_DTO_CLASS_NAME); + } + + private static ParameterizedTypeName genericSourceTypeName() { + return genericTypeName(CAR_CLASS_NAME); + } + + private static ParameterizedTypeName genericTypeName(final ClassName wrappedClassName) { + return ParameterizedTypeName.get(ClassName.get(WeakReference.class), wrappedClassName); } private static TypeSpec.Builder converterMapperWithGenericTargetTypeBuilder() { - return converterMapperBuilder( - CAR_CLASS_NAME, - ParameterizedTypeName.get(ClassName.get(WeakReference.class), CAR_DTO_CLASS_NAME)); + return converterMapperBuilder(CAR_CLASS_NAME, genericTargetTypeName()); + } + + private static ParameterizedTypeName genericTargetTypeName() { + return genericTypeName(CAR_DTO_CLASS_NAME); } private static TypeSpec.Builder plainCarMapperBuilder( @@ -155,18 +197,20 @@ private static MethodSpec convertMethod( } @Test - void shouldIgnoreNonConverterMappers() throws IOException { + void shouldIgnoreNonConverterMappers() { // Given final JavaFile plainMapperFile = JavaFile.builder( PACKAGE_NAME, plainCarMapperBuilder(CAR_CLASS_NAME, CAR_DTO_CLASS_NAME).build()) .build(); - // When - final boolean compileResult = compile(plainMapperFile.toJavaFileObject()); - - // Then - then(compileResult).isTrue(); + // When - Then + assertThat( + javac() + .withProcessors(processor) + .compile( + concat(List.of(plainMapperFile.toJavaFileObject()), COMMON_COMPILATION_UNITS))) + .succeeded(); BDDMockito.then(adapterGenerator) .should() .writeGeneratedCodeToOutput(descriptorArgumentCaptor.capture(), any(Writer.class)); @@ -176,7 +220,7 @@ PACKAGE_NAME, plainCarMapperBuilder(CAR_CLASS_NAME, CAR_DTO_CLASS_NAME).build()) } @Test - void shouldProcessOnlyConvertMethodForMapperWithMultipleMethods() throws IOException { + void shouldProcessOnlyConvertMethodForMapperWithMultipleMethods() { // Given final JavaFile mapperFile = JavaFile.builder( @@ -186,11 +230,12 @@ void shouldProcessOnlyConvertMethodForMapperWithMultipleMethods() throws IOExcep .build()) .build(); - // When - final boolean compileResult = compile(mapperFile.toJavaFileObject()); - - // Then - then(compileResult).isTrue(); + // When - Then + assertThat( + javac() + .withProcessors(processor) + .compile(concat(List.of(mapperFile.toJavaFileObject()), COMMON_COMPILATION_UNITS))) + .succeeded(); BDDMockito.then(adapterGenerator) .should() .writeGeneratedCodeToOutput(descriptorArgumentCaptor.capture(), any(Writer.class)); @@ -202,7 +247,7 @@ void shouldProcessOnlyConvertMethodForMapperWithMultipleMethods() throws IOExcep } @Test - void shouldAddConversionServiceCallsForExternalConversions() throws IOException { + void shouldAddConversionServiceCallsForExternalConversions() { // Given final JavaFile mappingConfigFile = JavaFile.builder( @@ -213,12 +258,17 @@ PACKAGE_NAME, buildConfigClassWithExternalConversion("StringToLocaleConfig")) PACKAGE_NAME, converterMapperWithoutGenericSourceOrTargetTypeBuilder().build()) .build(); - // When - final boolean compileResult = - compile(mappingConfigFile.toJavaFileObject(), mapperFile.toJavaFileObject()); + // When - Then + assertThat( + javac() + .withProcessors(processor) + .compile( + concat( + List.of( + mapperFile.toJavaFileObject(), mappingConfigFile.toJavaFileObject()), + COMMON_COMPILATION_UNITS))) + .succeeded(); - // Then - then(compileResult).isTrue(); BDDMockito.then(adapterGenerator) .should() .writeGeneratedCodeToOutput(descriptorArgumentCaptor.capture(), any(Writer.class)); @@ -234,7 +284,7 @@ PACKAGE_NAME, converterMapperWithoutGenericSourceOrTargetTypeBuilder().build()) } @Test - void shouldCompileMapperWithDelegatingConverterAnnotation() throws IOException { + void shouldCompileMapperWithDelegatingConverterAnnotation() { final var delegatingConverterAnnotationTypeSpec = TypeSpec.annotationBuilder(ClassName.get(DelegatingConverter.class)) .addAnnotation( @@ -279,12 +329,11 @@ void shouldCompileMapperWithDelegatingConverterAnnotation() throws IOException { final var autowiredFile = JavaFile.builder("org.springframework.beans.factory.annotation", autowiredTypeSpec).build(); - final var compileResult = - compile( - delegatingConverterFile.toJavaFileObject(), - mapperFile.toJavaFileObject(), - autowiredFile.toJavaFileObject()); - - then(compileResult).isTrue(); + assertThat( + compile( + delegatingConverterFile.toJavaFileObject(), + mapperFile.toJavaFileObject(), + autowiredFile.toJavaFileObject())) + .succeeded(); } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8a41d87..b2d2f57 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,7 @@ asciidoctorPlugin = "3.3.2" assertj = "3.24.2" commons-io = "2.13.0" commons-lang3 = "3.13.0" +compile-testing = "0.21.0" dependencyCheck = "8.4.0" dummymaker = "4.0.0" guava = "32.1.2-jre" @@ -21,6 +22,7 @@ versionsPlugin = "0.48.0" assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj"} commons-io = { group = "commons-io", name = "commons-io", version.ref = "commons-io" } commons-lang3 = { group = "org.apache.commons", name = "commons-lang3", version.ref = "commons-lang3" } +compile-testing = { group = "com.google.testing.compile", name = "compile-testing", version.ref = "compile-testing" } dummymaker = { group = "com.github.goodforgod", name = "dummymaker", version.ref = "dummymaker"} guava = { group = "com.google.guava", name = "guava", version.ref = "guava" } jakarta-annotation = { group = "jakarta.annotation", name = "jakarta.annotation-api", version.ref = "jakarta-annotation-api" }