/* * Copyright (C) 2016 The Dagger Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dagger.internal.codegen; import static com.google.common.truth.Truth.assertAbout; import androidx.room.compiler.processing.util.Source; import com.google.common.collect.ImmutableList; import com.google.common.truth.FailureMetadata; import com.google.common.truth.Subject; import com.google.common.truth.Truth; import dagger.Module; import dagger.producers.ProducerModule; import dagger.testing.compile.CompilerTests; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; import java.util.List; /** A {@link Truth} subject for testing Dagger module methods. */ final class DaggerModuleMethodSubject extends Subject { /** A {@link Truth} subject factory for testing Dagger module methods. */ static final class Factory implements Subject.Factory { /** Starts a clause testing a Dagger {@link Module @Module} method. */ static DaggerModuleMethodSubject assertThatModuleMethod(String method) { return assertAbout(new Factory()) .that(method) .withDeclaration("@Module abstract class %s { %s }"); } /** Starts a clause testing a Dagger {@link ProducerModule @ProducerModule} method. */ static DaggerModuleMethodSubject assertThatProductionModuleMethod(String method) { return assertAbout(new Factory()) .that(method) .withDeclaration("@ProducerModule abstract class %s { %s }"); } /** Starts a clause testing a method in an unannotated class. */ static DaggerModuleMethodSubject assertThatMethodInUnannotatedClass(String method) { return assertAbout(new Factory()) .that(method) .withDeclaration("abstract class %s { %s }"); } private Factory() {} @Override public DaggerModuleMethodSubject createSubject(FailureMetadata failureMetadata, String that) { return new DaggerModuleMethodSubject(failureMetadata, that); } } private final String actual; private final ImmutableList.Builder imports = new ImmutableList.Builder() .add( // explicitly import Module so it's not ambiguous with java.lang.Module "import dagger.Module;", "import dagger.*;", "import dagger.multibindings.*;", "import dagger.producers.*;", "import java.util.*;", "import javax.inject.*;"); private String declaration; private ImmutableList additionalSources = ImmutableList.of(); private DaggerModuleMethodSubject(FailureMetadata failureMetadata, String subject) { super(failureMetadata, subject); this.actual = subject; } /** * Imports classes and interfaces. Note that all types in the following packages are already * imported:
    *
  • {@code dagger.*} *
  • {@code dagger.multibindings.*} *
  • (@code dagger.producers.*} *
  • {@code java.util.*} *
  • {@code javax.inject.*} *
*/ DaggerModuleMethodSubject importing(Class... imports) { return importing(Arrays.asList(imports)); } /** * Imports classes and interfaces. Note that all types in the following packages are already * imported:
    *
  • {@code dagger.*} *
  • {@code dagger.multibindings.*} *
  • (@code dagger.producers.*} *
  • {@code java.util.*} *
  • {@code javax.inject.*} *
*/ DaggerModuleMethodSubject importing(List> imports) { imports.stream() .map(clazz -> String.format("import %s;", clazz.getCanonicalName())) .forEachOrdered(this.imports::add); return this; } /** * Sets the declaration of the module. Must be a string with two {@code %s} parameters. The first * will be replaced with the name of the type, and the second with the method declaration, which * must be within paired braces. */ DaggerModuleMethodSubject withDeclaration(String declaration) { this.declaration = declaration; return this; } /** Additional source files that must be compiled with the module. */ DaggerModuleMethodSubject withAdditionalSources(Source... sources) { this.additionalSources = ImmutableList.copyOf(sources); return this; } /** * Fails if compiling the module with the method doesn't report an error at the method * declaration whose message contains {@code errorSubstring}. */ void hasError(String errorSubstring) { String source = moduleSource(); Source module = CompilerTests.javaSource("test.TestModule", source); CompilerTests.daggerCompiler( ImmutableList.builder().add(module).addAll(additionalSources).build()) .compile( subject -> subject .hasErrorContaining(errorSubstring) .onSource(module) .onLine(methodLine(source))); } private int methodLine(String source) { String beforeMethod = source.substring(0, source.indexOf(actual)); int methodLine = 1; for (int nextNewlineIndex = beforeMethod.indexOf('\n'); nextNewlineIndex >= 0; nextNewlineIndex = beforeMethod.indexOf('\n', nextNewlineIndex + 1)) { methodLine++; } return methodLine; } private String moduleSource() { StringWriter stringWriter = new StringWriter(); PrintWriter writer = new PrintWriter(stringWriter); writer.println("package test;"); writer.println(); for (String importLine : imports.build()) { writer.println(importLine); } writer.println(); writer.printf(declaration, "TestModule", "\n" + actual + "\n"); writer.println(); return stringWriter.toString(); } }