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