diff --git a/spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java b/spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java index cc468f2ef5d2..5d4b04ea75b0 100644 --- a/spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java +++ b/spring-core-test/src/main/java/org/springframework/core/test/tools/TestCompiler.java @@ -24,6 +24,7 @@ import java.util.Locale; import java.util.function.Consumer; import java.util.function.UnaryOperator; +import java.util.stream.Stream; import javax.annotation.processing.Processor; import javax.tools.Diagnostic; @@ -41,6 +42,7 @@ * * @author Phillip Webb * @author Scott Frederick + * @author Stephane Nicoll * @since 6.0 * @see #forSystem() */ @@ -59,10 +61,12 @@ public final class TestCompiler { private final List processors; + private final List compilerOptions; + private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler, SourceFiles sourceFiles, ResourceFiles resourceFiles, ClassFiles classFiles, - List processors) { + List processors, List compilerOptions) { this.classLoader = classLoader; this.compiler = compiler; @@ -70,6 +74,7 @@ private TestCompiler(@Nullable ClassLoader classLoader, JavaCompiler compiler, this.resourceFiles = resourceFiles; this.classFiles = classFiles; this.processors = processors; + this.compilerOptions = compilerOptions; } @@ -88,7 +93,7 @@ public static TestCompiler forSystem() { */ public static TestCompiler forCompiler(JavaCompiler javaCompiler) { return new TestCompiler(null, javaCompiler, SourceFiles.none(), - ResourceFiles.none(), ClassFiles.none(), Collections.emptyList()); + ResourceFiles.none(), ClassFiles.none(), Collections.emptyList(), Collections.emptyList()); } /** @@ -108,7 +113,7 @@ public TestCompiler with(UnaryOperator customizer) { public TestCompiler withSources(SourceFile... sourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles.and(sourceFiles), this.resourceFiles, - this.classFiles, this.processors); + this.classFiles, this.processors, this.compilerOptions); } /** @@ -119,7 +124,7 @@ public TestCompiler withSources(SourceFile... sourceFiles) { public TestCompiler withSources(Iterable sourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles.and(sourceFiles), this.resourceFiles, - this.classFiles, this.processors); + this.classFiles, this.processors, this.compilerOptions); } /** @@ -130,7 +135,7 @@ public TestCompiler withSources(Iterable sourceFiles) { public TestCompiler withSources(SourceFiles sourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles.and(sourceFiles), this.resourceFiles, - this.classFiles, this.processors); + this.classFiles, this.processors, this.compilerOptions); } /** @@ -140,7 +145,8 @@ public TestCompiler withSources(SourceFiles sourceFiles) { */ public TestCompiler withResources(ResourceFile... resourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles.and(resourceFiles), this.classFiles, this.processors); + this.resourceFiles.and(resourceFiles), this.classFiles, this.processors, + this.compilerOptions); } /** @@ -150,7 +156,8 @@ public TestCompiler withResources(ResourceFile... resourceFiles) { */ public TestCompiler withResources(Iterable resourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles.and(resourceFiles), this.classFiles, this.processors); + this.resourceFiles.and(resourceFiles), this.classFiles, this.processors, + this.compilerOptions); } /** @@ -160,7 +167,8 @@ public TestCompiler withResources(Iterable resourceFiles) { */ public TestCompiler withResources(ResourceFiles resourceFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles.and(resourceFiles), this.classFiles, this.processors); + this.resourceFiles.and(resourceFiles), this.classFiles, this.processors, + this.compilerOptions); } /** @@ -170,7 +178,8 @@ public TestCompiler withResources(ResourceFiles resourceFiles) { */ public TestCompiler withClasses(Iterable classFiles) { return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles, this.classFiles.and(classFiles), this.processors); + this.resourceFiles, this.classFiles.and(classFiles), this.processors, + this.compilerOptions); } /** @@ -182,7 +191,7 @@ public TestCompiler withProcessors(Processor... processors) { List mergedProcessors = new ArrayList<>(this.processors); mergedProcessors.addAll(Arrays.asList(processors)); return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles, this.classFiles, mergedProcessors); + this.resourceFiles, this.classFiles, mergedProcessors, this.compilerOptions); } /** @@ -194,7 +203,32 @@ public TestCompiler withProcessors(Iterable processors) { List mergedProcessors = new ArrayList<>(this.processors); processors.forEach(mergedProcessors::add); return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, - this.resourceFiles, this.classFiles, mergedProcessors); + this.resourceFiles, this.classFiles, mergedProcessors, this.compilerOptions); + } + + /** + * Create a new {@link TestCompiler} instance with the additional compiler options. + * @param options the additional compiler options + * @return a new {@code TestCompiler} instance + * @since 6.1 + */ + public TestCompiler withCompilerOptions(String... options) { + List mergedCompilerOptions = Stream.concat(this.compilerOptions.stream(), + Arrays.stream(options)).distinct().toList(); + return new TestCompiler(this.classLoader, this.compiler, this.sourceFiles, + this.resourceFiles, this.classFiles, this.processors, mergedCompilerOptions); + } + + /** + * Create a new {@link TestCompiler} instance that fails if any warning is + * encountered. This sets the {@code -Xlint:all} and {@code -Werror} compiler + * options. + * @return a new {@code TestCompiler} instance + * @since 6.1 + * @see #withCompilerOptions(String...) + */ + public TestCompiler failOnWarning() { + return withCompilerOptions("-Xlint:all", "-Werror"); } /** @@ -275,8 +309,8 @@ private DynamicClassLoader compile() { standardFileManager, classLoaderToUse, this.classFiles, this.resourceFiles); if (!this.sourceFiles.isEmpty()) { Errors errors = new Errors(); - CompilationTask task = this.compiler.getTask(null, fileManager, errors, null, - null, compilationUnits); + CompilationTask task = this.compiler.getTask(null, fileManager, errors, + this.compilerOptions, null, compilationUnits); if (!this.processors.isEmpty()) { task.setProcessors(this.processors); } diff --git a/spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java b/spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java index 63a3055c5ee5..c921a728553d 100644 --- a/spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java +++ b/spring-core-test/src/test/java/org/springframework/core/test/tools/TestCompilerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 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. @@ -43,6 +43,7 @@ * @author Phillip Webb * @author Andy Wilkinson * @author Scott Frederick + * @author Stephane Nicoll */ class TestCompilerTests { @@ -87,6 +88,20 @@ public String get() { } """; + private static final String HELLO_DEPRECATED = """ + package com.example; + + import java.util.function.Supplier; + + public class Hello implements Supplier { + + @Deprecated + public String get() { + return "Hello Deprecated"; + } + + } + """; @Test @SuppressWarnings("unchecked") @@ -119,6 +134,70 @@ void compileWhenSourceHasCompileErrors() { })); } + @Test + @SuppressWarnings("unchecked") + void compileWhenSourceUseDeprecateCodeAndNoOptionSet() { + SourceFile main = SourceFile.of(""" + package com.example; + + public class Main { + + public static void main(String[] args) { + new Hello().get(); + } + + } + """); + TestCompiler.forSystem().withSources( + SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> { + Supplier supplier = compiled.getInstance(Supplier.class, + "com.example.Hello"); + assertThat(supplier.get()).isEqualTo("Hello Deprecated"); + }); + } + + @Test + void compileWhenSourceUseDeprecateCodeAndFailOnWarningIsSet() { + SourceFile main = SourceFile.of(""" + package com.example; + + public class Main { + + public static void main(String[] args) { + new Hello().get(); + } + + } + """); + assertThatExceptionOfType(CompilationException.class).isThrownBy( + () -> TestCompiler.forSystem().failOnWarning().withSources( + SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> { + })).withMessageContaining("warnings found and -Werror specified"); + } + + @Test + @SuppressWarnings("unchecked") + void compileWhenSourceUseDeprecateCodeAndFailOnWarningWithSuppressWarnings() { + SourceFile main = SourceFile.of(""" + package com.example; + + public class Main { + + @SuppressWarnings("deprecation") + public static void main(String[] args) { + new Hello().get(); + } + + } + """); + TestCompiler.forSystem().failOnWarning().withSources( + SourceFile.of(HELLO_DEPRECATED), main).compile(compiled -> { + Supplier supplier = compiled.getInstance(Supplier.class, + "com.example.Hello"); + assertThat(supplier.get()).isEqualTo("Hello Deprecated"); + }); + } + @Test void withSourcesArrayAddsSource() { SourceFile sourceFile = SourceFile.of(HELLO_WORLD);