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