1 /* 2 * Copyright (C) 2021 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 org.gradle.testkit.runner.BuildResult; 25 import org.gradle.testkit.runner.GradleRunner; 26 import org.junit.Rule; 27 import org.junit.Test; 28 import org.junit.rules.TemporaryFolder; 29 import org.junit.runner.RunWith; 30 import org.junit.runners.JUnit4; 31 32 // This is a regression test for https://github.com/google/dagger/issues/3136 33 @RunWith(JUnit4.class) 34 public class TransitiveScopeTest { 35 @Rule public TemporaryFolder folder = new TemporaryFolder(); 36 37 @Test testTransitiveScope_WithImplementation()38 public void testTransitiveScope_WithImplementation() throws IOException { 39 BuildResult result = setupRunnerWith("implementation").buildAndFail(); 40 assertThat(result.getOutput()).contains("Task :app:compileJava FAILED"); 41 assertThat(result.getOutput()) 42 .contains( 43 "ComponentProcessingStep was unable to process 'app.MyComponent' because " 44 + "'library2.MyScope' could not be resolved." 45 + "\n " 46 + "\n Dependency trace:" 47 // Note: this fails on the subcomponent rather than Foo because the subcomponent is 48 // validated before any of its dependencies. 49 + "\n => element (INTERFACE): library1.MySubcomponent" 50 + "\n => annotation: @MyScope" 51 + "\n => type (ERROR annotation type): library2.MyScope"); 52 } 53 54 @Test testTransitiveScope_WithApi()55 public void testTransitiveScope_WithApi() throws IOException { 56 BuildResult result = setupRunnerWith("api").build(); 57 assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); 58 assertThat(result.getOutput()).contains("@Inject library1.Foo(): SCOPED"); 59 } 60 setupRunnerWith(String dependencyType)61 private GradleRunner setupRunnerWith(String dependencyType) throws IOException { 62 File projectDir = folder.getRoot(); 63 GradleModule.create(projectDir) 64 .addSettingsFile( 65 "include 'app'", 66 "include 'library1'", 67 "include 'library2'", 68 "include 'spi-plugin'") 69 .addBuildFile( 70 "buildscript {", 71 " ext {", 72 String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), 73 " }", 74 "}", 75 "", 76 "allprojects {", 77 " repositories {", 78 " mavenCentral()", 79 " mavenLocal()", 80 " }", 81 "}"); 82 83 GradleModule.create(projectDir, "app") 84 .addBuildFile( 85 "plugins {", 86 " id 'java'", 87 " id 'application'", 88 "}", 89 "dependencies {", 90 " implementation project(':library1')", 91 " annotationProcessor project(':spi-plugin')", 92 " implementation \"com.google.dagger:dagger:$dagger_version\"", 93 " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", 94 "}") 95 .addSrcFile( 96 "MyComponent.java", 97 "package app;", 98 "", 99 "import dagger.Component;", 100 "import library1.MySubcomponent;", 101 "", 102 "@Component", 103 "public interface MyComponent {", 104 " MySubcomponent subcomponent();", 105 "}"); 106 107 GradleModule.create(projectDir, "library1") 108 .addBuildFile( 109 "plugins {", 110 " id 'java'", 111 " id 'java-library'", 112 "}", 113 "dependencies {", 114 dependencyType + " project(':library2')", 115 " implementation \"com.google.dagger:dagger:$dagger_version\"", 116 " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", 117 "}") 118 .addSrcFile( 119 "Foo.java", 120 "package library1;", 121 "", 122 "import javax.inject.Inject;", 123 "import library2.MyScope;", 124 "", 125 "@MyScope", 126 "public class Foo {", 127 " @Inject Foo() {}", 128 "}") 129 // Note: In order to repro the issue we place MyScope on a subcomponent so that it can be a 130 // transitive dependency of the component. If MyScope was placed on directly on the 131 // component, it would need to be a direct dependency of the component. 132 .addSrcFile( 133 "MySubcomponent.java", 134 "package library1;", 135 "", 136 "import dagger.Subcomponent;", 137 "import library2.MyScope;", 138 "", 139 "@MyScope", 140 "@Subcomponent", 141 "public interface MySubcomponent {", 142 " Foo foo();", 143 "}"); 144 145 GradleModule.create(projectDir, "library2") 146 .addBuildFile( 147 "plugins {", 148 " id 'java'", 149 " id 'java-library'", 150 "}", 151 "dependencies {", 152 " implementation 'javax.inject:javax.inject:1'", 153 "}") 154 .addSrcFile( 155 "MyScope.java", 156 "package library2;", 157 "", 158 "import javax.inject.Scope;", 159 "", 160 "@Scope", 161 "public @interface MyScope {}"); 162 163 // This plugin is used to print output about bindings that we can assert on in tests. 164 GradleModule.create(projectDir, "spi-plugin") 165 .addBuildFile( 166 "plugins {", 167 " id 'java'", 168 "}", 169 "dependencies {", 170 " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", 171 " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", 172 " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", 173 "}") 174 .addSrcFile( 175 "TestBindingGraphPlugin.java", 176 "package spiplugin;", 177 "", 178 "import com.google.auto.service.AutoService;", 179 "import dagger.model.BindingGraph;", 180 "import dagger.spi.BindingGraphPlugin;", 181 "import dagger.spi.DiagnosticReporter;", 182 "", 183 "@AutoService(BindingGraphPlugin.class)", 184 "public class TestBindingGraphPlugin implements BindingGraphPlugin {", 185 " @Override", 186 " public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter" 187 + " diagnosticReporter) {", 188 " bindingGraph.bindings().stream()", 189 " .filter(binding -> binding.scope().isPresent())", 190 " .forEach(binding -> System.out.println(binding + \": SCOPED\"));", 191 " bindingGraph.bindings().stream()", 192 " .filter(binding -> !binding.scope().isPresent())", 193 " .forEach(binding -> System.out.println(binding + \": UNSCOPED\"));", 194 " }", 195 "}"); 196 197 return GradleRunner.create() 198 .withArguments("--stacktrace", "build") 199 .withProjectDir(projectDir); 200 } 201 } 202