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 TransitiveBindsScopeTest { 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 TransitiveBindsScopeTest(String transitiveDependencyType)47 public TransitiveBindsScopeTest(String transitiveDependencyType) { 48 this.transitiveDependencyType = transitiveDependencyType; 49 } 50 51 @Test testScopeOnBindsMethod()52 public void testScopeOnBindsMethod() throws IOException { 53 BuildResult result; 54 switch (transitiveDependencyType) { 55 case "implementation": 56 result = setupRunner().buildAndFail(); 57 assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); 58 assertThat(result.getOutput()) 59 .contains( 60 "ComponentProcessingStep was unable to process 'app.MyComponent' because " 61 + "'library2.MyScope' could not be resolved." 62 + "\n " 63 + "\n Dependency trace:" 64 + "\n => element (INTERFACE): library1.MyModule" 65 + "\n => element (METHOD): bindObject(java.lang.String)" 66 + "\n => annotation: @MyScope" 67 + "\n => type (ERROR annotation type): library2.MyScope"); 68 break; 69 case "api": 70 result = setupRunner().build(); 71 assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); 72 assertThat(result.getOutput()) 73 .contains( 74 "@Binds @library2.MyScope Object library1.MyModule.bindObject(String): SCOPED"); 75 break; 76 } 77 } 78 setupRunner()79 private GradleRunner setupRunner() throws IOException { 80 File projectDir = folder.getRoot(); 81 GradleModule.create(projectDir) 82 .addSettingsFile( 83 "include 'app'", 84 "include 'library1'", 85 "include 'library2'", 86 "include 'spi-plugin'") 87 .addBuildFile( 88 "buildscript {", 89 " ext {", 90 String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), 91 " }", 92 "}", 93 "", 94 "allprojects {", 95 " repositories {", 96 " mavenCentral()", 97 " mavenLocal()", 98 " }", 99 "}"); 100 101 GradleModule.create(projectDir, "app") 102 .addBuildFile( 103 "plugins {", 104 " id 'java'", 105 " id 'application'", 106 "}", 107 "tasks.withType(JavaCompile) {", 108 " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", 109 "}", 110 "dependencies {", 111 " implementation project(':library1')", 112 " annotationProcessor project(':spi-plugin')", 113 " implementation \"com.google.dagger:dagger:$dagger_version\"", 114 " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", 115 "}") 116 .addSrcFile( 117 "MyComponent.java", 118 "package app;", 119 "", 120 "import dagger.Component;", 121 "import library1.MySubcomponent;", 122 "", 123 "@Component", 124 "public interface MyComponent {", 125 " MySubcomponent subcomponent();", 126 "}"); 127 128 GradleModule.create(projectDir, "library1") 129 .addBuildFile( 130 "plugins {", 131 " id 'java'", 132 " id 'java-library'", 133 "}", 134 "dependencies {", 135 transitiveDependencyType + " project(':library2')", 136 " implementation \"com.google.dagger:dagger:$dagger_version\"", 137 " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", 138 "}") 139 // Note: In order to repro the issue we place MyScope on a subcomponent so that it can be a 140 // transitive dependency of the component. If MyScope was placed on directly on the 141 // component, it would need to be a direct dependency of the component. 142 .addSrcFile( 143 "MySubcomponent.java", 144 "package library1;", 145 "", 146 "import dagger.Subcomponent;", 147 "import library2.MyScope;", 148 "", 149 "@MyScope", 150 "@Subcomponent(modules = MyModule.class)", 151 "public interface MySubcomponent {", 152 " Object object();", 153 "}") 154 .addSrcFile( 155 "MyModule.java", 156 "package library1;", 157 "", 158 "import dagger.Binds;", 159 "import dagger.Module;", 160 "import dagger.Provides;", 161 "import library2.MyScope;", 162 "", 163 "@Module", 164 "public interface MyModule {", 165 " @MyScope", 166 " @Binds", 167 " Object bindObject(String string);", 168 "", 169 " @Provides", 170 " static String provideString() {", 171 " return \"\";", 172 " }", 173 "}"); 174 175 GradleModule.create(projectDir, "library2") 176 .addBuildFile( 177 "plugins {", 178 " id 'java'", 179 " id 'java-library'", 180 "}", 181 "dependencies {", 182 " implementation 'javax.inject:javax.inject:1'", 183 "}") 184 .addSrcFile( 185 "MyScope.java", 186 "package library2;", 187 "", 188 "import javax.inject.Scope;", 189 "", 190 "@Scope", 191 "public @interface MyScope {}"); 192 193 // This plugin is used to print output about bindings that we can assert on in tests. 194 GradleModule.create(projectDir, "spi-plugin") 195 .addBuildFile( 196 "plugins {", 197 " id 'java'", 198 "}", 199 "dependencies {", 200 " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", 201 " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", 202 " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", 203 "}") 204 .addSrcFile( 205 "TestBindingGraphPlugin.java", 206 "package spiplugin;", 207 "", 208 "import com.google.auto.service.AutoService;", 209 "import dagger.model.BindingGraph;", 210 "import dagger.model.BindingGraph.DependencyEdge;", 211 "import dagger.model.DependencyRequest;", 212 "import dagger.spi.BindingGraphPlugin;", 213 "import dagger.spi.DiagnosticReporter;", 214 "", 215 "@AutoService(BindingGraphPlugin.class)", 216 "public class TestBindingGraphPlugin implements BindingGraphPlugin {", 217 " @Override", 218 " public void visitGraph(", 219 " BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {", 220 " bindingGraph.bindings().stream()", 221 " .filter(binding -> binding.scope().isPresent())", 222 " .forEach(binding -> System.out.println(binding + \": SCOPED\"));", 223 " bindingGraph.bindings().stream()", 224 " .filter(binding -> !binding.scope().isPresent())", 225 " .forEach(binding -> System.out.println(binding + \": UNSCOPED\"));", 226 " }", 227 "}"); 228 229 return GradleRunner.create() 230 .withArguments("--stacktrace", "build") 231 .withProjectDir(projectDir); 232 } 233 } 234