diff --git a/javatests/artifacts/dagger/build-tests/build.gradle b/javatests/artifacts/dagger/build-tests/build.gradle new file mode 100644 index 00000000000..407c7ef0f78 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/build.gradle @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2021 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. + */ + +plugins { + id 'java' + id 'application' +} + +// Set the versions in a system property so that tests can access it. +test { + systemProperty 'dagger_version', "$dagger_version" +} + +dependencies { + testImplementation "com.google.truth:truth:$truth_version" + testImplementation "junit:junit:$junit_version" + testImplementation gradleTestKit() +} \ No newline at end of file diff --git a/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleModule.java b/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleModule.java new file mode 100644 index 00000000000..6b7a6204ae2 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleModule.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2021 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 buildtests; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; + +/** Used to create files for a Gradle module in a particular directory. */ +public final class GradleModule { + public static GradleModule create(File moduleDir) { + return new GradleModule(moduleDir); + } + + public static GradleModule create(File projectDir, String moduleName) { + return new GradleModule(new File(projectDir, moduleName)); + } + + private final File moduleDir; + private final File moduleSrcDir; + + private GradleModule(File moduleDir) { + this.moduleDir = moduleDir; + this.moduleSrcDir = new File(moduleDir, "src/main/java/"); + } + + public GradleModule addBuildFile(String... content) throws IOException { + writeFile(createFile(moduleDir, "build.gradle"), content); + return this; + } + + public GradleModule addSettingsFile(String... content) throws IOException { + writeFile(createFile(moduleDir, "settings.gradle"), content); + return this; + } + + public GradleModule addFile(String fileName, String... content) throws IOException { + writeFile(createFile(moduleDir, fileName), content); + return this; + } + + public GradleModule addSrcFile(String fileName, String... content) throws IOException { + writeFile(createFile(moduleSrcDir, fileName), content); + return this; + } + + private static File createFile(File dir, String fileName) { + File file = new File(dir, fileName); + file.getParentFile().mkdirs(); + return file; + } + + private static void writeFile(File destination, String... content) throws IOException { + BufferedWriter output = null; + try { + output = new BufferedWriter(new FileWriter(destination)); + for (String line : content) { + output.write(line + "\n"); + } + } finally { + if (output != null) { + output.close(); + } + } + } +} diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveScopeTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveScopeTest.java new file mode 100644 index 00000000000..cb694f85a3a --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveScopeTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2021 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 buildtests; + +import static com.google.common.truth.Truth.assertThat; +import static org.gradle.testkit.runner.TaskOutcome.SUCCESS; + +import java.io.File; +import java.io.IOException; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(JUnit4.class) +public class TransitiveScopeTest { + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void testTransitiveScope_WithImplementation() throws IOException { + BuildResult result = setupBuildWith("implementation"); + + // TODO(bcorso): This is a repro of https://github.com/google/dagger/issues/3136. + // Once the issue is fixed, this test case should fail to build. + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("@Inject library1.Foo(): UNSCOPED"); + } + + @Test + public void testTransitiveScope_WithApi() throws IOException { + BuildResult result = setupBuildWith("api"); + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("@Inject library1.Foo(): SCOPED"); + } + + private BuildResult setupBuildWith(String dependencyType) throws IOException { + File projectDir = folder.getRoot(); + GradleModule.create(projectDir) + .addSettingsFile( + "include 'app'", + "include 'library1'", + "include 'library2'", + "include 'spi-plugin'") + .addBuildFile( + "buildscript {", + " ext {", + String.format("dagger_version = \"%s\"", System.getProperty("dagger_version")), + " }", + "}", + "", + "allprojects {", + " repositories {", + " jcenter()", + " mavenCentral()", + " mavenLocal()", + " }", + "}"); + + GradleModule.create(projectDir, "app") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'application'", + "}", + "dependencies {", + " implementation project(':library1')", + " annotationProcessor project(':spi-plugin')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyComponent.java", + "package app;", + "", + "import dagger.Component;", + "import library1.MySubcomponent;", + "", + "@Component", + "public interface MyComponent {", + " MySubcomponent subcomponent();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + dependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "Foo.java", + "package library1;", + "", + "import javax.inject.Inject;", + "import library2.MyScope;", + "", + "@MyScope", + "public class Foo {", + " @Inject Foo() {}", + "}") + // Note: In order to repro the issue we place MyScope on a subcomponent so that it can be a + // transitive dependency of the component. If MyScope was placed on directly on the + // component, it would need to be a direct dependency of the component. + .addSrcFile( + "MySubcomponent.java", + "package library1;", + "", + "import dagger.Subcomponent;", + "import library2.MyScope;", + "", + "@MyScope", + "@Subcomponent", + "public interface MySubcomponent {", + " Foo foo();", + "}"); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MyScope.java", + "package library2;", + "", + "import javax.inject.Scope;", + "", + "@Scope", + "public @interface MyScope {}"); + + // This plugin is used to print output about bindings that we can assert on in tests. + GradleModule.create(projectDir, "spi-plugin") + .addBuildFile( + "plugins {", + " id 'java'", + "}", + "dependencies {", + " implementation \"com.google.dagger:dagger-spi:$dagger_version\"", + " implementation 'com.google.auto.service:auto-service-annotations:1.0.1'", + " annotationProcessor 'com.google.auto.service:auto-service:1.0.1'", + "}") + .addSrcFile( + "TestBindingGraphPlugin.java", + "package spiplugin;", + "", + "import com.google.auto.service.AutoService;", + "import dagger.model.BindingGraph;", + "import dagger.spi.BindingGraphPlugin;", + "import dagger.spi.DiagnosticReporter;", + "", + "@AutoService(BindingGraphPlugin.class)", + "public class TestBindingGraphPlugin implements BindingGraphPlugin {", + " @Override", + " public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter" + + " diagnosticReporter) {", + " bindingGraph.bindings().stream()", + " .filter(binding -> binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": SCOPED\"));", + " bindingGraph.bindings().stream()", + " .filter(binding -> !binding.scope().isPresent())", + " .forEach(binding -> System.out.println(binding + \": UNSCOPED\"));", + " }", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir) + .build(); + } +} diff --git a/javatests/artifacts/dagger/settings.gradle b/javatests/artifacts/dagger/settings.gradle index 6f7bf3ac048..57a7cec6727 100644 --- a/javatests/artifacts/dagger/settings.gradle +++ b/javatests/artifacts/dagger/settings.gradle @@ -1,4 +1,5 @@ rootProject.name = 'Dagger Apps' +include ':build-tests' include ':java-app' include ':kotlin-app' include ':kotlin-app:kotlin-library'