xref: /aosp_15_r20/external/dagger2/java/dagger/testing/compile/CompilerTests.java (revision f585d8a307d0621d6060bd7e80091fdcbf94fe27)
1 /*
2  * Copyright (C) 2020 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.testing.compile;
18 
19 import static com.google.common.base.Strings.isNullOrEmpty;
20 import static com.google.common.collect.MoreCollectors.onlyElement;
21 import static com.google.common.collect.Streams.stream;
22 import static com.google.testing.compile.Compiler.javac;
23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
24 import static java.util.stream.Collectors.toMap;
25 
26 import androidx.room.compiler.processing.XProcessingEnv;
27 import androidx.room.compiler.processing.XProcessingEnvConfig;
28 import androidx.room.compiler.processing.XProcessingStep;
29 import androidx.room.compiler.processing.util.CompilationResultSubject;
30 import androidx.room.compiler.processing.util.ProcessorTestExtKt;
31 import androidx.room.compiler.processing.util.Source;
32 import androidx.room.compiler.processing.util.XTestInvocation;
33 import androidx.room.compiler.processing.util.compiler.TestCompilationArguments;
34 import androidx.room.compiler.processing.util.compiler.TestCompilationResult;
35 import androidx.room.compiler.processing.util.compiler.TestKotlinCompilerKt;
36 import com.google.auto.value.AutoValue;
37 import com.google.common.base.Supplier;
38 import com.google.common.collect.ImmutableCollection;
39 import com.google.common.collect.ImmutableList;
40 import com.google.common.collect.ImmutableMap;
41 import com.google.common.collect.ImmutableSet;
42 import com.google.common.io.Files;
43 import com.google.devtools.ksp.processing.SymbolProcessorProvider;
44 import com.google.testing.compile.Compiler;
45 import dagger.internal.codegen.ComponentProcessor;
46 import dagger.internal.codegen.KspComponentProcessor;
47 import dagger.spi.model.BindingGraphPlugin;
48 import java.io.File;
49 import java.nio.file.Path;
50 import java.nio.file.Paths;
51 import java.util.Collection;
52 import java.util.HashMap;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.NoSuchElementException;
56 import java.util.function.Consumer;
57 import javax.annotation.processing.Processor;
58 import org.junit.rules.TemporaryFolder;
59 
60 /** A helper class for working with java compiler tests. */
61 public final class CompilerTests {
62   // TODO(bcorso): Share this with java/dagger/internal/codegen/DelegateComponentProcessor.java
63   static final XProcessingEnvConfig PROCESSING_ENV_CONFIG =
64       new XProcessingEnvConfig.Builder().disableAnnotatedElementValidation(true).build();
65 
66   // TODO(bcorso): Share this with javatests/dagger/internal/codegen/Compilers.java
67   private static final ImmutableMap<String, String> DEFAULT_PROCESSOR_OPTIONS =
68       ImmutableMap.of(
69           "dagger.experimentalDaggerErrorMessages", "enabled");
70 
71   /** Returns the {@link XProcessingEnv.Backend} for the given {@link CompilationResultSubject}. */
backend(CompilationResultSubject subject)72   public static XProcessingEnv.Backend backend(CompilationResultSubject subject) {
73     // TODO(bcorso): Create a more official API for this in XProcessing testing.
74     String output = subject.getCompilationResult().toString();
75     if (output.startsWith("CompilationResult (with ksp)")) {
76       return XProcessingEnv.Backend.KSP;
77     } else if (output.startsWith("CompilationResult (with javac)")
78                    || output.startsWith("CompilationResult (with kapt)")) {
79       return XProcessingEnv.Backend.JAVAC;
80     }
81     throw new AssertionError("Unexpected backend for subject.");
82   }
83 
84   /** Returns a {@link Source.KotlinSource} with the given file name and content. */
kotlinSource( String fileName, ImmutableCollection<String> srcLines)85   public static Source.KotlinSource kotlinSource(
86       String fileName, ImmutableCollection<String> srcLines) {
87     return (Source.KotlinSource) Source.Companion.kotlin(fileName, String.join("\n", srcLines));
88   }
89 
90   /** Returns a {@link Source.KotlinSource} with the given file name and content. */
kotlinSource(String fileName, String... srcLines)91   public static Source.KotlinSource kotlinSource(String fileName, String... srcLines) {
92     return (Source.KotlinSource) Source.Companion.kotlin(fileName, String.join("\n", srcLines));
93   }
94 
95   /** Returns a {@link Source.JavaSource} with the given file name and content. */
javaSource( String fileName, ImmutableCollection<String> srcLines)96   public static Source.JavaSource javaSource(
97       String fileName, ImmutableCollection<String> srcLines) {
98     return (Source.JavaSource) Source.Companion.java(fileName, String.join("\n", srcLines));
99   }
100 
101   /** Returns a {@link Source.JavaSource} with the given file name and content. */
javaSource(String fileName, String... srcLines)102   public static Source.JavaSource javaSource(String fileName, String... srcLines) {
103     return (Source.JavaSource) Source.Companion.java(fileName, String.join("\n", srcLines));
104   }
105 
106   /** Returns a {@link Compiler} instance with the given sources. */
daggerCompiler(Source... sources)107   public static DaggerCompiler daggerCompiler(Source... sources) {
108     return daggerCompiler(ImmutableList.copyOf(sources));
109   }
110 
111   /** Returns a {@link Compiler} instance with the given sources. */
daggerCompiler(ImmutableCollection<Source> sources)112   public static DaggerCompiler daggerCompiler(ImmutableCollection<Source> sources) {
113     return DaggerCompiler.builder().sources(sources).build();
114   }
115 
116   /**
117    * Used to compile regular java or kotlin sources and inspect the elements processed in the test
118    * processing environment.
119    */
invocationCompiler(Source... sources)120   public static InvocationCompiler invocationCompiler(Source... sources) {
121     return new AutoValue_CompilerTests_InvocationCompiler(
122         ImmutableList.copyOf(sources), DEFAULT_PROCESSOR_OPTIONS);
123   }
124 
125   /** */
126   @AutoValue
127   public abstract static class InvocationCompiler {
128     /** Returns the sources being compiled */
sources()129     abstract ImmutableList<Source> sources();
130 
131     /** Returns the annotation processor options */
processorOptions()132     abstract ImmutableMap<String, String> processorOptions();
133 
compile(Consumer<XTestInvocation> onInvocation)134     public void compile(Consumer<XTestInvocation> onInvocation) {
135       ProcessorTestExtKt.runProcessorTest(
136           sources(),
137           /* classpath= */ ImmutableList.of(),
138           processorOptions(),
139           /* javacArguments= */ ImmutableList.of(),
140           /* kotlincArguments= */ ImmutableList.of(),
141           /* config= */ PROCESSING_ENV_CONFIG,
142           invocation -> {
143             onInvocation.accept(invocation);
144             return null;
145           });
146     }
147   }
148 
149   /** Used to compile Dagger sources and inspect the compiled results. */
150   @AutoValue
151   public abstract static class DaggerCompiler {
builder()152     static Builder builder() {
153       Builder builder = new AutoValue_CompilerTests_DaggerCompiler.Builder();
154       // Set default values
155       return builder
156           .processorOptions(DEFAULT_PROCESSOR_OPTIONS)
157           .additionalJavacProcessors(ImmutableList.of())
158           .additionalKspProcessors(ImmutableList.of())
159           .processingStepSuppliers(ImmutableSet.of())
160           .bindingGraphPluginSuppliers(ImmutableSet.of());
161     }
162 
163     /** Returns the sources being compiled */
sources()164     abstract ImmutableCollection<Source> sources();
165 
166     /** Returns the annotation processor options */
processorOptions()167     abstract ImmutableMap<String, String> processorOptions();
168 
169     /** Returns the extra Javac processors. */
additionalJavacProcessors()170     abstract ImmutableCollection<Processor> additionalJavacProcessors();
171 
172     /** Returns the extra KSP processors. */
additionalKspProcessors()173     abstract ImmutableCollection<SymbolProcessorProvider> additionalKspProcessors();
174 
175     /** Returns the processing steps suppliers. */
processingStepSuppliers()176     abstract ImmutableCollection<Supplier<XProcessingStep>> processingStepSuppliers();
177 
178     /** Returns the processing steps. */
processingSteps()179     private ImmutableList<XProcessingStep> processingSteps() {
180       return processingStepSuppliers().stream().map(Supplier::get).collect(toImmutableList());
181     }
182 
183     /** Returns the {@link BindingGraphPlugin} suppliers. */
bindingGraphPluginSuppliers()184     abstract ImmutableCollection<Supplier<BindingGraphPlugin>> bindingGraphPluginSuppliers();
185 
186     /** Returns the {@link BindingGraphPlugin}s. */
bindingGraphPlugins()187     private ImmutableList<BindingGraphPlugin> bindingGraphPlugins() {
188       return bindingGraphPluginSuppliers().stream().map(Supplier::get).collect(toImmutableList());
189     }
190 
191     /** Returns a builder with the current values of this {@link Compiler} as default. */
toBuilder()192     abstract Builder toBuilder();
193 
194     /**
195      * Returns a new {@link Compiler} instance with the given processor options.
196      *
197      * <p>Note that the default processor options are still applied unless they are explicitly
198      * overridden by the given processing options.
199      */
withProcessingOptions(Map<String, String> processorOptions)200     public DaggerCompiler withProcessingOptions(Map<String, String> processorOptions) {
201       // Add default processor options first to allow overridding with new key-value pairs.
202       Map<String, String> newProcessorOptions = new HashMap<>(DEFAULT_PROCESSOR_OPTIONS);
203       newProcessorOptions.putAll(processorOptions);
204       return toBuilder().processorOptions(newProcessorOptions).build();
205     }
206 
207     /** Returns a new {@link HiltCompiler} instance with the additional Javac processors. */
withAdditionalJavacProcessors(Processor... processors)208     public DaggerCompiler withAdditionalJavacProcessors(Processor... processors) {
209       return toBuilder().additionalJavacProcessors(ImmutableList.copyOf(processors)).build();
210     }
211 
212     /** Returns a new {@link HiltCompiler} instance with the additional KSP processors. */
withAdditionalKspProcessors(SymbolProcessorProvider... processors)213     public DaggerCompiler withAdditionalKspProcessors(SymbolProcessorProvider... processors) {
214       return toBuilder().additionalKspProcessors(ImmutableList.copyOf(processors)).build();
215     }
216 
217     /** Returns a new {@link Compiler} instance with the given processing steps. */
withProcessingSteps(Supplier<XProcessingStep>.... suppliers)218     public DaggerCompiler withProcessingSteps(Supplier<XProcessingStep>... suppliers) {
219       return toBuilder().processingStepSuppliers(ImmutableList.copyOf(suppliers)).build();
220     }
221 
withBindingGraphPlugins(Supplier<BindingGraphPlugin>.... suppliers)222     public DaggerCompiler withBindingGraphPlugins(Supplier<BindingGraphPlugin>... suppliers) {
223       return toBuilder().bindingGraphPluginSuppliers(ImmutableList.copyOf(suppliers)).build();
224     }
225 
compile(Consumer<CompilationResultSubject> onCompilationResult)226     public void compile(Consumer<CompilationResultSubject> onCompilationResult) {
227       ProcessorTestExtKt.runProcessorTest(
228           sources().asList(),
229           /* classpath= */ ImmutableList.of(),
230           processorOptions(),
231           /* javacArguments= */ ImmutableList.of(),
232           /* kotlincArguments= */ ImmutableList.of(
233               "-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true"),
234           /* config= */ PROCESSING_ENV_CONFIG,
235           /* javacProcessors= */ mergeProcessors(
236               ImmutableList.of(
237                   ComponentProcessor.withTestPlugins(bindingGraphPlugins()),
238                   new CompilerProcessors.JavacProcessor(processingSteps())),
239               additionalJavacProcessors()),
240           /* symbolProcessorProviders= */ mergeProcessors(
241               ImmutableList.of(
242                   KspComponentProcessor.Provider.withTestPlugins(bindingGraphPlugins()),
243                   new CompilerProcessors.KspProcessor.Provider(processingSteps())),
244               additionalKspProcessors()),
245           result -> {
246             onCompilationResult.accept(result);
247             return null;
248           });
249     }
250 
mergeProcessors( Collection<T> defaultProcessors, Collection<T> extraProcessors)251     private static <T> ImmutableList<T> mergeProcessors(
252         Collection<T> defaultProcessors, Collection<T> extraProcessors) {
253       Map<Class<?>, T> processors =
254           defaultProcessors.stream()
255               .collect(toMap(Object::getClass, (T e) -> e, (p1, p2) -> p2, HashMap::new));
256       // Adds extra processors, and allows overriding any processors of the same class.
257       extraProcessors.forEach(processor -> processors.put(processor.getClass(), processor));
258       return ImmutableList.copyOf(processors.values());
259     }
260 
261     /** Used to build a {@link DaggerCompiler}. */
262     @AutoValue.Builder
263     public abstract static class Builder {
sources(ImmutableCollection<Source> sources)264       abstract Builder sources(ImmutableCollection<Source> sources);
processorOptions(Map<String, String> processorOptions)265       abstract Builder processorOptions(Map<String, String> processorOptions);
266 
additionalJavacProcessors(ImmutableCollection<Processor> processors)267       abstract Builder additionalJavacProcessors(ImmutableCollection<Processor> processors);
268 
additionalKspProcessors( ImmutableCollection<SymbolProcessorProvider> processors)269       abstract Builder additionalKspProcessors(
270           ImmutableCollection<SymbolProcessorProvider> processors);
271 
processingStepSuppliers( ImmutableCollection<Supplier<XProcessingStep>> processingStepSuppliers)272       abstract Builder processingStepSuppliers(
273           ImmutableCollection<Supplier<XProcessingStep>> processingStepSuppliers);
bindingGraphPluginSuppliers( ImmutableCollection<Supplier<BindingGraphPlugin>> bindingGraphPluginSuppliers)274       abstract Builder bindingGraphPluginSuppliers(
275           ImmutableCollection<Supplier<BindingGraphPlugin>> bindingGraphPluginSuppliers);
build()276       abstract DaggerCompiler build();
277     }
278   }
279 
280   /** Returns the {@plainlink File jar file} containing the compiler deps. */
compilerDepsJar()281   public static File compilerDepsJar() {
282     try {
283       return stream(Files.fileTraverser().breadthFirst(getRunfilesDir()))
284           .filter(file -> file.getName().endsWith("_compiler_deps_deploy.jar"))
285           .collect(onlyElement());
286     } catch (NoSuchElementException e) {
287       throw new IllegalStateException(
288           "No compiler deps jar found. Are you using the Dagger compiler_test macro?", e);
289     }
290   }
291 
292   /** Returns a {@link Compiler} with the compiler deps jar added to the class path. */
compiler()293   public static Compiler compiler() {
294     return javac().withClasspath(ImmutableList.of(compilerDepsJar()));
295   }
296 
compileWithKapt( List<Source> sources, TemporaryFolder tempFolder, Consumer<TestCompilationResult> onCompilationResult)297   public static void compileWithKapt(
298       List<Source> sources,
299       TemporaryFolder tempFolder,
300       Consumer<TestCompilationResult> onCompilationResult) {
301     compileWithKapt(sources, ImmutableMap.of(), tempFolder, onCompilationResult);
302   }
303 
compileWithKapt( List<Source> sources, Map<String, String> processorOptions, TemporaryFolder tempFolder, Consumer<TestCompilationResult> onCompilationResult)304   public static void compileWithKapt(
305       List<Source> sources,
306       Map<String, String> processorOptions,
307       TemporaryFolder tempFolder,
308       Consumer<TestCompilationResult> onCompilationResult) {
309     TestCompilationResult result = TestKotlinCompilerKt.compile(
310         tempFolder.getRoot(),
311         new TestCompilationArguments(
312             sources,
313             /*classpath=*/ ImmutableList.of(compilerDepsJar()),
314             /*inheritClasspath=*/ false,
315             /*javacArguments=*/ ImmutableList.of(),
316             /*kotlincArguments=*/ ImmutableList.of(),
317             /*kaptProcessors=*/ ImmutableList.of(new ComponentProcessor()),
318             /*symbolProcessorProviders=*/ ImmutableList.of(),
319             /*processorOptions=*/ processorOptions));
320     onCompilationResult.accept(result);
321   }
322 
getRunfilesDir()323   private static File getRunfilesDir() {
324     return getRunfilesPath().toFile();
325   }
326 
getRunfilesPath()327   private static Path getRunfilesPath() {
328     Path propPath = getRunfilesPath(System.getProperties());
329     if (propPath != null) {
330       return propPath;
331     }
332 
333     Path envPath = getRunfilesPath(System.getenv());
334     if (envPath != null) {
335       return envPath;
336     }
337 
338     Path cwd = Paths.get("").toAbsolutePath();
339     return cwd.getParent();
340   }
341 
getRunfilesPath(Map<?, ?> map)342   private static Path getRunfilesPath(Map<?, ?> map) {
343     String runfilesPath = (String) map.get("TEST_SRCDIR");
344     return isNullOrEmpty(runfilesPath) ? null : Paths.get(runfilesPath);
345   }
346 
CompilerTests()347   private CompilerTests() {}
348 }
349