1 /* 2 * Copyright (C) 2016 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.internal.codegen; 18 19 import static com.google.common.truth.Truth.assertAbout; 20 21 import androidx.room.compiler.processing.util.Source; 22 import com.google.common.collect.ImmutableList; 23 import com.google.common.truth.FailureMetadata; 24 import com.google.common.truth.Subject; 25 import com.google.common.truth.Truth; 26 import dagger.Module; 27 import dagger.producers.ProducerModule; 28 import dagger.testing.compile.CompilerTests; 29 import java.io.PrintWriter; 30 import java.io.StringWriter; 31 import java.util.Arrays; 32 import java.util.List; 33 34 /** A {@link Truth} subject for testing Dagger module methods. */ 35 final class DaggerModuleMethodSubject extends Subject { 36 37 /** A {@link Truth} subject factory for testing Dagger module methods. */ 38 static final class Factory implements Subject.Factory<DaggerModuleMethodSubject, String> { 39 40 /** Starts a clause testing a Dagger {@link Module @Module} method. */ assertThatModuleMethod(String method)41 static DaggerModuleMethodSubject assertThatModuleMethod(String method) { 42 return assertAbout(new Factory()) 43 .that(method) 44 .withDeclaration("@Module abstract class %s { %s }"); 45 } 46 47 /** Starts a clause testing a Dagger {@link ProducerModule @ProducerModule} method. */ assertThatProductionModuleMethod(String method)48 static DaggerModuleMethodSubject assertThatProductionModuleMethod(String method) { 49 return assertAbout(new Factory()) 50 .that(method) 51 .withDeclaration("@ProducerModule abstract class %s { %s }"); 52 } 53 54 /** Starts a clause testing a method in an unannotated class. */ assertThatMethodInUnannotatedClass(String method)55 static DaggerModuleMethodSubject assertThatMethodInUnannotatedClass(String method) { 56 return assertAbout(new Factory()) 57 .that(method) 58 .withDeclaration("abstract class %s { %s }"); 59 } 60 Factory()61 private Factory() {} 62 63 @Override createSubject(FailureMetadata failureMetadata, String that)64 public DaggerModuleMethodSubject createSubject(FailureMetadata failureMetadata, String that) { 65 return new DaggerModuleMethodSubject(failureMetadata, that); 66 } 67 } 68 69 private final String actual; 70 private final ImmutableList.Builder<String> imports = 71 new ImmutableList.Builder<String>() 72 .add( 73 // explicitly import Module so it's not ambiguous with java.lang.Module 74 "import dagger.Module;", 75 "import dagger.*;", 76 "import dagger.multibindings.*;", 77 "import dagger.producers.*;", 78 "import java.util.*;", 79 "import javax.inject.*;"); 80 private String declaration; 81 private ImmutableList<Source> additionalSources = ImmutableList.of(); 82 DaggerModuleMethodSubject(FailureMetadata failureMetadata, String subject)83 private DaggerModuleMethodSubject(FailureMetadata failureMetadata, String subject) { 84 super(failureMetadata, subject); 85 this.actual = subject; 86 } 87 88 /** 89 * Imports classes and interfaces. Note that all types in the following packages are already 90 * imported:<ul> 91 * <li>{@code dagger.*} 92 * <li>{@code dagger.multibindings.*} 93 * <li>(@code dagger.producers.*} 94 * <li>{@code java.util.*} 95 * <li>{@code javax.inject.*} 96 * </ul> 97 */ importing(Class<?>.... imports)98 DaggerModuleMethodSubject importing(Class<?>... imports) { 99 return importing(Arrays.asList(imports)); 100 } 101 102 /** 103 * Imports classes and interfaces. Note that all types in the following packages are already 104 * imported:<ul> 105 * <li>{@code dagger.*} 106 * <li>{@code dagger.multibindings.*} 107 * <li>(@code dagger.producers.*} 108 * <li>{@code java.util.*} 109 * <li>{@code javax.inject.*} 110 * </ul> 111 */ importing(List<? extends Class<?>> imports)112 DaggerModuleMethodSubject importing(List<? extends Class<?>> imports) { 113 imports.stream() 114 .map(clazz -> String.format("import %s;", clazz.getCanonicalName())) 115 .forEachOrdered(this.imports::add); 116 return this; 117 } 118 119 /** 120 * Sets the declaration of the module. Must be a string with two {@code %s} parameters. The first 121 * will be replaced with the name of the type, and the second with the method declaration, which 122 * must be within paired braces. 123 */ withDeclaration(String declaration)124 DaggerModuleMethodSubject withDeclaration(String declaration) { 125 this.declaration = declaration; 126 return this; 127 } 128 129 /** Additional source files that must be compiled with the module. */ withAdditionalSources(Source... sources)130 DaggerModuleMethodSubject withAdditionalSources(Source... sources) { 131 this.additionalSources = ImmutableList.copyOf(sources); 132 return this; 133 } 134 135 /** 136 * Fails if compiling the module with the method doesn't report an error at the method 137 * declaration whose message contains {@code errorSubstring}. 138 */ hasError(String errorSubstring)139 void hasError(String errorSubstring) { 140 String source = moduleSource(); 141 Source module = CompilerTests.javaSource("test.TestModule", source); 142 CompilerTests.daggerCompiler( 143 ImmutableList.<Source>builder().add(module).addAll(additionalSources).build()) 144 .compile( 145 subject -> 146 subject 147 .hasErrorContaining(errorSubstring) 148 .onSource(module) 149 .onLine(methodLine(source))); 150 } 151 methodLine(String source)152 private int methodLine(String source) { 153 String beforeMethod = source.substring(0, source.indexOf(actual)); 154 int methodLine = 1; 155 for (int nextNewlineIndex = beforeMethod.indexOf('\n'); 156 nextNewlineIndex >= 0; 157 nextNewlineIndex = beforeMethod.indexOf('\n', nextNewlineIndex + 1)) { 158 methodLine++; 159 } 160 return methodLine; 161 } 162 moduleSource()163 private String moduleSource() { 164 StringWriter stringWriter = new StringWriter(); 165 PrintWriter writer = new PrintWriter(stringWriter); 166 writer.println("package test;"); 167 writer.println(); 168 for (String importLine : imports.build()) { 169 writer.println(importLine); 170 } 171 writer.println(); 172 writer.printf(declaration, "TestModule", "\n" + actual + "\n"); 173 writer.println(); 174 return stringWriter.toString(); 175 } 176 } 177