• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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