1 /* 2 * Copyright (C) 2022 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 buildtests; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; 21 22 import java.io.File; 23 import java.io.IOException; 24 import java.util.Arrays; 25 import java.util.Collection; 26 import org.gradle.testkit.runner.BuildResult; 27 import org.gradle.testkit.runner.GradleRunner; 28 import org.junit.Rule; 29 import org.junit.Test; 30 import org.junit.rules.TemporaryFolder; 31 import org.junit.runner.RunWith; 32 import org.junit.runners.Parameterized; 33 import org.junit.runners.Parameterized.Parameters; 34 35 // This is a regression test for https://github.com/google/dagger/issues/3136 36 @RunWith(Parameterized.class) 37 public class TransitiveSubcomponentModulesTest { 38 @Parameters(name = "{0}") parameters()39 public static Collection<Object[]> parameters() { 40 return Arrays.asList(new Object[][] {{"implementation"}, {"api"}}); 41 } 42 43 @Rule public TemporaryFolder folder = new TemporaryFolder(); 44 45 private final String transitiveDependencyType; 46 TransitiveSubcomponentModulesTest(String transitiveDependencyType)47 public TransitiveSubcomponentModulesTest(String transitiveDependencyType) { 48 this.transitiveDependencyType = transitiveDependencyType; 49 } 50 51 @Test testSubcomponentAnnotationWithTransitiveModule()52 public void testSubcomponentAnnotationWithTransitiveModule() throws IOException { 53 GradleRunner runner = 54 setupRunner( 55 GradleFile.create( 56 "MySubcomponent.java", 57 "package library1;", 58 "", 59 "import dagger.Subcomponent;", 60 "import library2.TransitiveModule;", 61 "", 62 "@Subcomponent(modules = TransitiveModule.class)", 63 "public abstract class MySubcomponent {", 64 " public abstract int getInt();", 65 "}")); 66 BuildResult result; 67 switch (transitiveDependencyType) { 68 case "implementation": 69 result = runner.buildAndFail(); 70 assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); 71 String expectedErrorMsg = 72 "error: ComponentProcessingStep was unable to process 'app.MyComponent' because" 73 + " 'library2.TransitiveModule' could not be resolved." 74 + "\n " 75 + "\n Dependency trace:" 76 + "\n => element (CLASS): library1.MySubcomponent" 77 + "\n => annotation type: dagger.Subcomponent" 78 + "\n => annotation: @dagger.Subcomponent(modules={library2.TransitiveModule})" 79 + "\n => annotation value (TYPE_ARRAY): modules={library2.TransitiveModule}" 80 + "\n => annotation value (TYPE): modules=library2.TransitiveModule"; 81 assertThat(result.getOutput()).contains(expectedErrorMsg); 82 break; 83 case "api": 84 result = runner.build(); 85 assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); 86 break; 87 } 88 } 89 90 @Test testSubcomponentAnnotationWithModuleIncludesTransitiveModuleDependencies()91 public void testSubcomponentAnnotationWithModuleIncludesTransitiveModuleDependencies() 92 throws IOException { 93 GradleRunner runner = 94 setupRunner( 95 GradleFile.create( 96 "MySubcomponent.java", 97 "package library1;", 98 "", 99 "import dagger.Subcomponent;", 100 "", 101 "@Subcomponent(modules = IncludesTransitiveModule.class)", 102 "public abstract class MySubcomponent {", 103 " public abstract int getInt();", 104 "}")); 105 BuildResult result; 106 switch (transitiveDependencyType) { 107 case "implementation": 108 result = runner.buildAndFail(); 109 assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); 110 String expectedErrorMsg = 111 "error: ComponentProcessingStep was unable to process 'app.MyComponent' because" 112 + " 'library2.TransitiveModule' could not be resolved." 113 + "\n " 114 + "\n Dependency trace:" 115 + "\n => element (INTERFACE): library1.IncludesTransitiveModule" 116 + "\n => annotation type: dagger.Module" 117 + "\n => annotation: " 118 + "@dagger.Module(includes={library2.TransitiveModule}, subcomponents={})" 119 + "\n => annotation value (TYPE_ARRAY): includes={library2.TransitiveModule}" 120 + "\n => annotation value (TYPE): includes=library2.TransitiveModule"; 121 assertThat(result.getOutput()).contains(expectedErrorMsg); 122 break; 123 case "api": 124 result = runner.build(); 125 assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); 126 break; 127 } 128 } 129 setupRunner(GradleFile subcomponent)130 private GradleRunner setupRunner(GradleFile subcomponent) throws IOException { 131 File projectDir = folder.getRoot(); 132 GradleModule.create(projectDir) 133 .addSettingsFile("include 'app'", "include 'library1'", "include 'library2'") 134 .addBuildFile( 135 "buildscript {", 136 " ext {", 137 String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), 138 " }", 139 "}", 140 "", 141 "allprojects {", 142 " repositories {", 143 " mavenCentral()", 144 " mavenLocal()", 145 " }", 146 "}"); 147 148 GradleModule.create(projectDir, "app") 149 .addBuildFile( 150 "plugins {", 151 " id 'java'", 152 " id 'application'", 153 "}", 154 "tasks.withType(JavaCompile) {", 155 " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", 156 "}", 157 "dependencies {", 158 " implementation project(':library1')", 159 " implementation \"com.google.dagger:dagger:$dagger_version\"", 160 " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", 161 "}") 162 .addSrcFile( 163 "MyComponent.java", 164 "package app;", 165 "", 166 "import dagger.Component;", 167 "import library1.MySubcomponent;", 168 "", 169 "@Component", 170 "public interface MyComponent {", 171 " MySubcomponent mySubcomponent();", 172 "}"); 173 174 GradleModule.create(projectDir, "library1") 175 .addBuildFile( 176 "plugins {", 177 " id 'java'", 178 " id 'java-library'", 179 "}", 180 "dependencies {", 181 transitiveDependencyType + " project(':library2')", 182 " implementation \"com.google.dagger:dagger:$dagger_version\"", 183 " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", 184 "}") 185 .addSrcFile( 186 "IncludesTransitiveModule.java", 187 "package library1;", 188 "", 189 "import dagger.Module;", 190 "import dagger.Provides;", 191 "import library2.TransitiveModule;", 192 "", 193 "@Module(includes = TransitiveModule.class)", 194 "public interface IncludesTransitiveModule {}") 195 .addSrcFile(subcomponent); 196 197 GradleModule.create(projectDir, "library2") 198 .addBuildFile( 199 "plugins {", 200 " id 'java'", 201 " id 'java-library'", 202 "}", 203 "dependencies {", 204 " implementation \"com.google.dagger:dagger:$dagger_version\"", 205 " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", 206 "}") 207 .addSrcFile( 208 "TransitiveModule.java", 209 "package library2;", 210 "", 211 "import dagger.Module;", 212 "import dagger.Provides;", 213 "", 214 "@Module", 215 "public interface TransitiveModule {", 216 " @Provides", 217 " static int provideInt() {", 218 " return 0;", 219 " }", 220 "}"); 221 222 return GradleRunner.create().withArguments("--stacktrace", "build").withProjectDir(projectDir); 223 } 224 } 225