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