• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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   private static final ImmutableList<String> DEFAULT_JAVAC_OPTIONS = ImmutableList.of();
72 
73   private static final ImmutableList<String> DEFAULT_KOTLINC_OPTIONS =
74       ImmutableList.of(
75           "-api-version=1.9",
76           "-language-version=1.9",
77           "-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true");
78 
79   /** Returns the {@link XProcessingEnv.Backend} for the given {@link CompilationResultSubject}. */
backend(CompilationResultSubject subject)80   public static XProcessingEnv.Backend backend(CompilationResultSubject subject) {
81     // TODO(bcorso): Create a more official API for this in XProcessing testing.
82     String output = subject.getCompilationResult().toString();
83     if (output.startsWith("CompilationResult (with ksp)")) {
84       return XProcessingEnv.Backend.KSP;
85     } else if (output.startsWith("CompilationResult (with javac)")
86                    || output.startsWith("CompilationResult (with kapt)")) {
87       return XProcessingEnv.Backend.JAVAC;
88     }
89     throw new AssertionError("Unexpected backend for subject.");
90   }
91 
92   /** Returns a {@link Source.KotlinSource} with the given file name and content. */
kotlinSource( String fileName, ImmutableCollection<String> srcLines)93   public static Source.KotlinSource kotlinSource(
94       String fileName, ImmutableCollection<String> srcLines) {
95     return (Source.KotlinSource) Source.Companion.kotlin(fileName, String.join("\n", srcLines));
96   }
97 
98   /** Returns a {@link Source.KotlinSource} with the given file name and content. */
kotlinSource(String fileName, String... srcLines)99   public static Source.KotlinSource kotlinSource(String fileName, String... srcLines) {
100     return (Source.KotlinSource) Source.Companion.kotlin(fileName, String.join("\n", srcLines));
101   }
102 
103   /** Returns a {@link Source.JavaSource} with the given file name and content. */
javaSource( String fileName, ImmutableCollection<String> srcLines)104   public static Source.JavaSource javaSource(
105       String fileName, ImmutableCollection<String> srcLines) {
106     return (Source.JavaSource) Source.Companion.java(fileName, String.join("\n", srcLines));
107   }
108 
109   /** Returns a {@link Source.JavaSource} with the given file name and content. */
javaSource(String fileName, String... srcLines)110   public static Source.JavaSource javaSource(String fileName, String... srcLines) {
111     return (Source.JavaSource) Source.Companion.java(fileName, String.join("\n", srcLines));
112   }
113 
114   /** Returns a {@link Compiler} instance with the given sources. */
daggerCompiler(Source... sources)115   public static DaggerCompiler daggerCompiler(Source... sources) {
116     return daggerCompiler(ImmutableList.copyOf(sources));
117   }
118 
119   /** Returns a {@link Compiler} instance with the given sources. */
daggerCompiler(ImmutableCollection<Source> sources)120   public static DaggerCompiler daggerCompiler(ImmutableCollection<Source> sources) {
121     return DaggerCompiler.builder().sources(sources).build();
122   }
123 
124   /**
125    * Used to compile regular java or kotlin sources and inspect the elements processed in the test
126    * processing environment.
127    */
invocationCompiler(Source... sources)128   public static InvocationCompiler invocationCompiler(Source... sources) {
129     return new AutoValue_CompilerTests_InvocationCompiler(
130         ImmutableList.copyOf(sources), DEFAULT_PROCESSOR_OPTIONS);
131   }
132 
133   /** */
134   @AutoValue
135   public abstract static class InvocationCompiler {
136     /** Returns the sources being compiled */
sources()137     abstract ImmutableList<Source> sources();
138 
139     /** Returns the annotation processor options */
processorOptions()140     abstract ImmutableMap<String, String> processorOptions();
141 
compile(Consumer<XTestInvocation> onInvocation)142     public void compile(Consumer<XTestInvocation> onInvocation) {
143       ProcessorTestExtKt.runProcessorTest(
144           sources(),
145           /* classpath= */ ImmutableList.of(),
146           processorOptions(),
147           /* javacArguments= */ DEFAULT_JAVAC_OPTIONS,
148           /* kotlincArguments= */ DEFAULT_KOTLINC_OPTIONS,
149           /* config= */ PROCESSING_ENV_CONFIG,
150           invocation -> {
151             onInvocation.accept(invocation);
152             return null;
153           });
154     }
155   }
156 
157   /** Used to compile Dagger sources and inspect the compiled results. */
158   @AutoValue
159   public abstract static class DaggerCompiler {
builder()160     static Builder builder() {
161       Builder builder = new AutoValue_CompilerTests_DaggerCompiler.Builder();
162       // Set default values
163       return builder
164           .processorOptions(DEFAULT_PROCESSOR_OPTIONS)
165           .additionalJavacProcessors(ImmutableList.of())
166           .additionalKspProcessors(ImmutableList.of())
167           .processingStepSuppliers(ImmutableSet.of())
168           .bindingGraphPluginSuppliers(ImmutableSet.of());
169     }
170 
171     /** Returns the sources being compiled */
sources()172     abstract ImmutableCollection<Source> sources();
173 
174     /** Returns the annotation processor options */
processorOptions()175     abstract ImmutableMap<String, String> processorOptions();
176 
177     /** Returns the extra Javac processors. */
additionalJavacProcessors()178     abstract ImmutableCollection<Processor> additionalJavacProcessors();
179 
180     /** Returns the extra KSP processors. */
additionalKspProcessors()181     abstract ImmutableCollection<SymbolProcessorProvider> additionalKspProcessors();
182 
183     /** Returns the processing steps suppliers. */
processingStepSuppliers()184     abstract ImmutableCollection<Supplier<XProcessingStep>> processingStepSuppliers();
185 
186     /** Returns the processing steps. */
processingSteps()187     private ImmutableList<XProcessingStep> processingSteps() {
188       return processingStepSuppliers().stream().map(Supplier::get).collect(toImmutableList());
189     }
190 
191     /** Returns the {@link BindingGraphPlugin} suppliers. */
bindingGraphPluginSuppliers()192     abstract ImmutableCollection<Supplier<BindingGraphPlugin>> bindingGraphPluginSuppliers();
193 
194     /** Returns the {@link BindingGraphPlugin}s. */
bindingGraphPlugins()195     private ImmutableList<BindingGraphPlugin> bindingGraphPlugins() {
196       return bindingGraphPluginSuppliers().stream().map(Supplier::get).collect(toImmutableList());
197     }
198 
199     /** Returns a builder with the current values of this {@link Compiler} as default. */
toBuilder()200     abstract Builder toBuilder();
201 
202     /**
203      * Returns a new {@link Compiler} instance with the given processor options.
204      *
205      * <p>Note that the default processor options are still applied unless they are explicitly
206      * overridden by the given processing options.
207      */
withProcessingOptions(Map<String, String> processorOptions)208     public DaggerCompiler withProcessingOptions(Map<String, String> processorOptions) {
209       // Add default processor options first to allow overridding with new key-value pairs.
210       Map<String, String> newProcessorOptions = new HashMap<>(DEFAULT_PROCESSOR_OPTIONS);
211       newProcessorOptions.putAll(processorOptions);
212       return toBuilder().processorOptions(newProcessorOptions).build();
213     }
214 
215     /** Returns a new {@link HiltCompiler} instance with the additional Javac processors. */
withAdditionalJavacProcessors(Processor... processors)216     public DaggerCompiler withAdditionalJavacProcessors(Processor... processors) {
217       return toBuilder().additionalJavacProcessors(ImmutableList.copyOf(processors)).build();
218     }
219 
220     /** Returns a new {@link HiltCompiler} instance with the additional KSP processors. */
withAdditionalKspProcessors(SymbolProcessorProvider... processors)221     public DaggerCompiler withAdditionalKspProcessors(SymbolProcessorProvider... processors) {
222       return toBuilder().additionalKspProcessors(ImmutableList.copyOf(processors)).build();
223     }
224 
225     /** Returns a new {@link Compiler} instance with the given processing steps. */
withProcessingSteps(Supplier<XProcessingStep>.... suppliers)226     public DaggerCompiler withProcessingSteps(Supplier<XProcessingStep>... suppliers) {
227       return toBuilder().processingStepSuppliers(ImmutableList.copyOf(suppliers)).build();
228     }
229 
withBindingGraphPlugins(Supplier<BindingGraphPlugin>.... suppliers)230     public DaggerCompiler withBindingGraphPlugins(Supplier<BindingGraphPlugin>... suppliers) {
231       return toBuilder().bindingGraphPluginSuppliers(ImmutableList.copyOf(suppliers)).build();
232     }
233 
compile(Consumer<CompilationResultSubject> onCompilationResult)234     public void compile(Consumer<CompilationResultSubject> onCompilationResult) {
235       ProcessorTestExtKt.runProcessorTest(
236           sources().asList(),
237           /* classpath= */ ImmutableList.of(),
238           processorOptions(),
239           /* javacArguments= */ DEFAULT_JAVAC_OPTIONS,
240           /* kotlincArguments= */ DEFAULT_KOTLINC_OPTIONS,
241           /* config= */ PROCESSING_ENV_CONFIG,
242           /* javacProcessors= */ mergeProcessors(
243               ImmutableList.of(
244                   ComponentProcessor.withTestPlugins(bindingGraphPlugins()),
245                   new CompilerProcessors.JavacProcessor(processingSteps())),
246               additionalJavacProcessors()),
247           /* symbolProcessorProviders= */ mergeProcessors(
248               ImmutableList.of(
249                   KspComponentProcessor.Provider.withTestPlugins(bindingGraphPlugins()),
250                   new CompilerProcessors.KspProcessor.Provider(processingSteps())),
251               additionalKspProcessors()),
252           result -> {
253             onCompilationResult.accept(result);
254             return null;
255           });
256     }
257 
mergeProcessors( Collection<T> defaultProcessors, Collection<T> extraProcessors)258     private static <T> ImmutableList<T> mergeProcessors(
259         Collection<T> defaultProcessors, Collection<T> extraProcessors) {
260       Map<Class<?>, T> processors =
261           defaultProcessors.stream()
262               .collect(toMap(Object::getClass, (T e) -> e, (p1, p2) -> p2, HashMap::new));
263       // Adds extra processors, and allows overriding any processors of the same class.
264       extraProcessors.forEach(processor -> processors.put(processor.getClass(), processor));
265       return ImmutableList.copyOf(processors.values());
266     }
267 
268     /** Used to build a {@link DaggerCompiler}. */
269     @AutoValue.Builder
270     public abstract static class Builder {
sources(ImmutableCollection<Source> sources)271       abstract Builder sources(ImmutableCollection<Source> sources);
processorOptions(Map<String, String> processorOptions)272       abstract Builder processorOptions(Map<String, String> processorOptions);
273 
additionalJavacProcessors(ImmutableCollection<Processor> processors)274       abstract Builder additionalJavacProcessors(ImmutableCollection<Processor> processors);
275 
additionalKspProcessors( ImmutableCollection<SymbolProcessorProvider> processors)276       abstract Builder additionalKspProcessors(
277           ImmutableCollection<SymbolProcessorProvider> processors);
278 
processingStepSuppliers( ImmutableCollection<Supplier<XProcessingStep>> processingStepSuppliers)279       abstract Builder processingStepSuppliers(
280           ImmutableCollection<Supplier<XProcessingStep>> processingStepSuppliers);
bindingGraphPluginSuppliers( ImmutableCollection<Supplier<BindingGraphPlugin>> bindingGraphPluginSuppliers)281       abstract Builder bindingGraphPluginSuppliers(
282           ImmutableCollection<Supplier<BindingGraphPlugin>> bindingGraphPluginSuppliers);
build()283       abstract DaggerCompiler build();
284     }
285   }
286 
287   /** Returns the {@plainlink File jar file} containing the compiler deps. */
compilerDepsJar()288   public static File compilerDepsJar() {
289     try {
290       return stream(Files.fileTraverser().breadthFirst(getRunfilesDir()))
291           .filter(file -> file.getName().endsWith("_compiler_deps_deploy.jar"))
292           .collect(onlyElement());
293     } catch (NoSuchElementException e) {
294       throw new IllegalStateException(
295           "No compiler deps jar found. Are you using the Dagger compiler_test macro?", e);
296     }
297   }
298 
299   /** Returns a {@link Compiler} with the compiler deps jar added to the class path. */
compiler()300   public static Compiler compiler() {
301     return javac().withClasspath(ImmutableList.of(compilerDepsJar()));
302   }
303 
compileWithKapt( List<Source> sources, TemporaryFolder tempFolder, Consumer<TestCompilationResult> onCompilationResult)304   public static void compileWithKapt(
305       List<Source> sources,
306       TemporaryFolder tempFolder,
307       Consumer<TestCompilationResult> onCompilationResult) {
308     compileWithKapt(sources, ImmutableMap.of(), tempFolder, onCompilationResult);
309   }
310 
compileWithKapt( List<Source> sources, Map<String, String> processorOptions, TemporaryFolder tempFolder, Consumer<TestCompilationResult> onCompilationResult)311   public static void compileWithKapt(
312       List<Source> sources,
313       Map<String, String> processorOptions,
314       TemporaryFolder tempFolder,
315       Consumer<TestCompilationResult> onCompilationResult) {
316     TestCompilationResult result =
317         TestKotlinCompilerKt.compile(
318             tempFolder.getRoot(),
319             new TestCompilationArguments(
320                 sources,
321                 /* classpath= */ ImmutableList.of(compilerDepsJar()),
322                 /* inheritClasspath= */ false,
323                 /* javacArguments= */ DEFAULT_JAVAC_OPTIONS,
324                 /* kotlincArguments= */ DEFAULT_KOTLINC_OPTIONS,
325                 /* kaptProcessors= */ ImmutableList.of(new ComponentProcessor()),
326                 /* symbolProcessorProviders= */ ImmutableList.of(),
327                 /* processorOptions= */ processorOptions));
328     onCompilationResult.accept(result);
329   }
330 
getRunfilesDir()331   private static File getRunfilesDir() {
332     return getRunfilesPath().toFile();
333   }
334 
getRunfilesPath()335   private static Path getRunfilesPath() {
336     Path propPath = getRunfilesPath(System.getProperties());
337     if (propPath != null) {
338       return propPath;
339     }
340 
341     Path envPath = getRunfilesPath(System.getenv());
342     if (envPath != null) {
343       return envPath;
344     }
345 
346     Path cwd = Paths.get("").toAbsolutePath();
347     return cwd.getParent();
348   }
349 
getRunfilesPath(Map<?, ?> map)350   private static Path getRunfilesPath(Map<?, ?> map) {
351     String runfilesPath = (String) map.get("TEST_SRCDIR");
352     return isNullOrEmpty(runfilesPath) ? null : Paths.get(runfilesPath);
353   }
354 
CompilerTests()355   private CompilerTests() {}
356 }
357