diff --git a/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleFile.java b/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleFile.java new file mode 100644 index 00000000000..6cd279c91d8 --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleFile.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2022 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; + +/** Stores the name and content of a file. */ +public final class GradleFile { + /** Creates a {@link GradleFile} with the given name and content */ + public static GradleFile create(String fileName, String... fileContent) { + return new GradleFile(fileName, fileContent); + } + + private final String fileName; + private final String[] fileContent; + + GradleFile(String fileName, String... fileContent) { + this.fileName = fileName; + this.fileContent = fileContent; + } + + String fileName() { + return fileName; + } + + String[] fileContent() { + return fileContent; + } +} 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 index 6b7a6204ae2..8405d4b9012 100644 --- a/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleModule.java +++ b/javatests/artifacts/dagger/build-tests/src/main/java/buildtests/GradleModule.java @@ -49,11 +49,19 @@ public GradleModule addSettingsFile(String... content) throws IOException { return this; } + public GradleModule addFile(GradleFile gradleFile) throws IOException { + return addFile(gradleFile.fileName(), gradleFile.fileContent()); + } + public GradleModule addFile(String fileName, String... content) throws IOException { writeFile(createFile(moduleDir, fileName), content); return this; } + public GradleModule addSrcFile(GradleFile gradleFile) throws IOException { + return addSrcFile(gradleFile.fileName(), gradleFile.fileContent()); + } + public GradleModule addSrcFile(String fileName, String... content) throws IOException { writeFile(createFile(moduleSrcDir, fileName), content); return this; diff --git a/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveQualifierTest.java b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveQualifierTest.java new file mode 100644 index 00000000000..de0d7a6704c --- /dev/null +++ b/javatests/artifacts/dagger/build-tests/src/test/java/buildtests/TransitiveQualifierTest.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2022 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 java.util.Arrays; +import java.util.Collection; +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.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +// This is a regression test for https://github.com/google/dagger/issues/3136 +@RunWith(Parameterized.class) +public class TransitiveQualifierTest { + @Parameters(name = "{0}") + public static Collection parameters() { + return Arrays.asList(new Object[][] {{ "implementation" }, { "api" }}); + } + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private final String transitiveDependencyType; + + public TransitiveQualifierTest(String transitiveDependencyType) { + this.transitiveDependencyType = transitiveDependencyType; + } + + @Test + public void testQualifierOnInjectConstructorParameter() throws IOException { + BuildResult result = + setupBuildWith( + GradleFile.create( + "QualifierUsage.java", + "package library1;", + "", + "import javax.inject.Inject;", + "import library2.MyQualifier;", + "", + "public class QualifierUsage {", + " @Inject QualifierUsage(@MyQualifier int i) {}", + "}")); + switch (transitiveDependencyType) { + case "implementation": + // TODO(bcorso): This is a repro of https://github.com/google/dagger/issues/3136, where + // a qualifier will be missing if the it is not in the classpath during compilation. + // Once the issue is fixed, this test case should fail to build. + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: java.lang.Integer"); + break; + case "api": + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + @Test + public void testQualifierOnInjectField() throws IOException { + BuildResult result = + setupBuildWith( + GradleFile.create( + "QualifierUsage.java", + "package library1;", + "", + "import javax.inject.Inject;", + "import library2.MyQualifier;", + "", + "public class QualifierUsage {", + " @Inject @MyQualifier int i;", + "", + " @Inject QualifierUsage() {}", + "}")); + switch (transitiveDependencyType) { + case "implementation": + // TODO(bcorso): This is a repro of https://github.com/google/dagger/issues/3136, where + // a qualifier will be missing if the it is not in the classpath during compilation. + // Once the issue is fixed, this test case should fail to build. + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: java.lang.Integer"); + break; + case "api": + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + @Test + public void testQualifierOnInjectMethodParameter() throws IOException { + BuildResult result = + setupBuildWith( + GradleFile.create( + "QualifierUsage.java", + "package library1;", + "", + "import javax.inject.Inject;", + "import library2.MyQualifier;", + "", + "public class QualifierUsage {", + " @Inject QualifierUsage() {}", + "", + " @Inject void injectMethod(@MyQualifier int i) {}", + "}")); + switch (transitiveDependencyType) { + case "implementation": + // TODO(bcorso): This is a repro of https://github.com/google/dagger/issues/3136, where + // a qualifier will be missing if the it is not in the classpath during compilation. + // Once the issue is fixed, this test case should fail to build. + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: java.lang.Integer"); + break; + case "api": + assertThat(result.task(":app:assemble").getOutcome()).isEqualTo(SUCCESS); + assertThat(result.getOutput()).contains("REQUEST: @library2.MyQualifier java.lang.Integer"); + break; + } + } + + private BuildResult setupBuildWith(GradleFile qualifierUsage) 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'", + "}", + "tasks.withType(JavaCompile) {", + " options.compilerArgs += '-Adagger.experimentalDaggerErrorMessages=ENABLED'", + "}", + "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.MyModule;", + "import library1.QualifierUsage;", + "", + "@Component(modules = MyModule.class)", + "public interface MyComponent {", + " QualifierUsage qualifierUsage();", + "}"); + + GradleModule.create(projectDir, "library1") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + transitiveDependencyType + " project(':library2')", + " implementation \"com.google.dagger:dagger:$dagger_version\"", + " annotationProcessor \"com.google.dagger:dagger-compiler:$dagger_version\"", + "}") + .addSrcFile( + "MyModule.java", + "package library1;", + "", + "import dagger.Module;", + "import dagger.Provides;", + "import library2.MyQualifier;", + "", + "@Module", + "public interface MyModule {", + " @Provides", + " @MyQualifier", + " static int provideInt() {", + " return 0;", + " }", + "}") + .addSrcFile(qualifierUsage); + + GradleModule.create(projectDir, "library2") + .addBuildFile( + "plugins {", + " id 'java'", + " id 'java-library'", + "}", + "dependencies {", + " implementation 'javax.inject:javax.inject:1'", + "}") + .addSrcFile( + "MyQualifier.java", + "package library2;", + "", + "import javax.inject.Qualifier;", + "", + "@Qualifier", + "public @interface MyQualifier {}"); + + // 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.model.BindingGraph.DependencyEdge;", + "import dagger.model.DependencyRequest;", + "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.dependencyEdges().stream()", + " .map(DependencyEdge::dependencyRequest)", + " .map(DependencyRequest::key)", + " .forEach(key -> System.out.println(\"REQUEST: \" + key));", + " }", + "}"); + + return GradleRunner.create() + .withArguments("--stacktrace", "build") + .withProjectDir(projectDir) + .build(); + } +}