Add basic Dagger 2 tests
Change-Id: I8e779f512a5a8a662f921e6fa30d7fa345b212b6
diff --git a/src/test/examplesDagger/basic/I1.java b/src/test/examplesDagger/basic/I1.java
new file mode 100644
index 0000000..cd46afa
--- /dev/null
+++ b/src/test/examplesDagger/basic/I1.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+interface I1 {
+ String getName();
+}
diff --git a/src/test/examplesDagger/basic/I1Impl1.java b/src/test/examplesDagger/basic/I1Impl1.java
new file mode 100644
index 0000000..635a35f
--- /dev/null
+++ b/src/test/examplesDagger/basic/I1Impl1.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+import javax.inject.Inject;
+
+// @Singleton (added by transformer in some tests)
+public class I1Impl1 implements I1 {
+
+ @Inject
+ public I1Impl1() {}
+
+ public String getName() {
+ return "I1Impl1";
+ }
+}
diff --git a/src/test/examplesDagger/basic/I1Impl2.java b/src/test/examplesDagger/basic/I1Impl2.java
new file mode 100644
index 0000000..ed33281
--- /dev/null
+++ b/src/test/examplesDagger/basic/I1Impl2.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+public class I1Impl2 implements I1 {
+
+ public I1Impl2() {}
+
+ public String getName() {
+ return "I1Impl2";
+ }
+}
diff --git a/src/test/examplesDagger/basic/I2.java b/src/test/examplesDagger/basic/I2.java
new file mode 100644
index 0000000..51948ee
--- /dev/null
+++ b/src/test/examplesDagger/basic/I2.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+interface I2 {
+ String getName();
+}
diff --git a/src/test/examplesDagger/basic/I2Impl1.java b/src/test/examplesDagger/basic/I2Impl1.java
new file mode 100644
index 0000000..13f8b9a
--- /dev/null
+++ b/src/test/examplesDagger/basic/I2Impl1.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+import javax.inject.Inject;
+
+// @Singleton (added by transformer in some tests)
+public class I2Impl1 implements I2 {
+
+ @Inject
+ public I2Impl1() {}
+
+ public String getName() {
+ return "I2Impl1";
+ }
+}
diff --git a/src/test/examplesDagger/basic/I2Impl2.java b/src/test/examplesDagger/basic/I2Impl2.java
new file mode 100644
index 0000000..e1a5da7
--- /dev/null
+++ b/src/test/examplesDagger/basic/I2Impl2.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+public class I2Impl2 implements I2 {
+
+ public I2Impl2() {}
+
+ public String getName() {
+ return "I2Impl2";
+ }
+}
diff --git a/src/test/examplesDagger/basic/I3.java b/src/test/examplesDagger/basic/I3.java
new file mode 100644
index 0000000..154a47b
--- /dev/null
+++ b/src/test/examplesDagger/basic/I3.java
@@ -0,0 +1,8 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+interface I3 {
+ String getName();
+}
diff --git a/src/test/examplesDagger/basic/I3Impl1.java b/src/test/examplesDagger/basic/I3Impl1.java
new file mode 100644
index 0000000..560113b
--- /dev/null
+++ b/src/test/examplesDagger/basic/I3Impl1.java
@@ -0,0 +1,17 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+import javax.inject.Inject;
+
+// @Singleton (added by transformer in some tests)
+public class I3Impl1 implements I3 {
+
+ @Inject
+ public I3Impl1() {}
+
+ public String getName() {
+ return "I3Impl1";
+ }
+}
diff --git a/src/test/examplesDagger/basic/I3Impl2.java b/src/test/examplesDagger/basic/I3Impl2.java
new file mode 100644
index 0000000..99bdede
--- /dev/null
+++ b/src/test/examplesDagger/basic/I3Impl2.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+public class I3Impl2 implements I3 {
+
+ public I3Impl2() {}
+
+ public String getName() {
+ return "I3Impl2";
+ }
+}
diff --git a/src/test/examplesDagger/basic/MainBase.java b/src/test/examplesDagger/basic/MainBase.java
new file mode 100644
index 0000000..d1ad6f3
--- /dev/null
+++ b/src/test/examplesDagger/basic/MainBase.java
@@ -0,0 +1,18 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+public class MainBase {
+ public void test(MainComponent mainComponent) {
+ I1 i1 = mainComponent.i1();
+ I2 i2 = mainComponent.i2();
+ I3 i3 = mainComponent.i3();
+ System.out.println(i1 == mainComponent.i1());
+ System.out.println(i2 == mainComponent.i2());
+ System.out.println(i3 == mainComponent.i3());
+ System.out.println(i1.getName());
+ System.out.println(i2.getName());
+ System.out.println(i3.getName());
+ }
+}
diff --git a/src/test/examplesDagger/basic/MainComponent.java b/src/test/examplesDagger/basic/MainComponent.java
new file mode 100644
index 0000000..0a6e1f6
--- /dev/null
+++ b/src/test/examplesDagger/basic/MainComponent.java
@@ -0,0 +1,12 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+interface MainComponent {
+ I1 i1();
+
+ I2 i2();
+
+ I3 i3();
+}
diff --git a/src/test/examplesDagger/basic/MainComponentUsingBinds.java b/src/test/examplesDagger/basic/MainComponentUsingBinds.java
new file mode 100644
index 0000000..41896b0
--- /dev/null
+++ b/src/test/examplesDagger/basic/MainComponentUsingBinds.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+import dagger.Component;
+import javax.inject.Singleton;
+
+@Component(modules = ModuleUsingBinds.class)
+@Singleton
+interface MainComponentUsingBinds extends MainComponent {}
diff --git a/src/test/examplesDagger/basic/MainComponentUsingProvides.java b/src/test/examplesDagger/basic/MainComponentUsingProvides.java
new file mode 100644
index 0000000..c83fe30
--- /dev/null
+++ b/src/test/examplesDagger/basic/MainComponentUsingProvides.java
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+import dagger.Component;
+import javax.inject.Singleton;
+
+@Component(modules = ModuleUsingProvides.class)
+@Singleton
+interface MainComponentUsingProvides extends MainComponent {}
diff --git a/src/test/examplesDagger/basic/MainUsingBinds.java b/src/test/examplesDagger/basic/MainUsingBinds.java
new file mode 100644
index 0000000..4470917
--- /dev/null
+++ b/src/test/examplesDagger/basic/MainUsingBinds.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+public class MainUsingBinds extends MainBase {
+ public static void main(String[] args) {
+ new MainUsingBinds().test(DaggerMainComponentUsingBinds.create());
+ }
+}
diff --git a/src/test/examplesDagger/basic/MainUsingProvides.java b/src/test/examplesDagger/basic/MainUsingProvides.java
new file mode 100644
index 0000000..5ebe417
--- /dev/null
+++ b/src/test/examplesDagger/basic/MainUsingProvides.java
@@ -0,0 +1,10 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+public class MainUsingProvides extends MainBase {
+ public static void main(String[] args) {
+ new MainUsingProvides().test(DaggerMainComponentUsingProvides.create());
+ }
+}
diff --git a/src/test/examplesDagger/basic/ModuleUsingBinds.java b/src/test/examplesDagger/basic/ModuleUsingBinds.java
new file mode 100644
index 0000000..6b8df40
--- /dev/null
+++ b/src/test/examplesDagger/basic/ModuleUsingBinds.java
@@ -0,0 +1,19 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+import dagger.Binds;
+import dagger.Module;
+
+@Module
+interface ModuleUsingBinds {
+ @Binds
+ I1 i1(I1Impl1 i1);
+
+ @Binds
+ I2 i2(I2Impl1 i2);
+
+ @Binds
+ I3 i3(I3Impl1 i3);
+}
diff --git a/src/test/examplesDagger/basic/ModuleUsingProvides.java b/src/test/examplesDagger/basic/ModuleUsingProvides.java
new file mode 100644
index 0000000..4c3048b
--- /dev/null
+++ b/src/test/examplesDagger/basic/ModuleUsingProvides.java
@@ -0,0 +1,28 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package basic;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+class ModuleUsingProvides {
+ @Provides
+ // @Singleton (added by transformer in some tests)
+ public static I1 i1() {
+ return new I1Impl2();
+ }
+
+ @Provides
+ // @Singleton (added by transformer in some tests)
+ public static I2 i2() {
+ return new I2Impl2();
+ }
+
+ @Provides
+ // @Singleton (added by transformer in some tests)
+ public static I3 i3() {
+ return new I3Impl2();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/JavaCompilerTool.java b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
index c7f7652..1021d65 100644
--- a/src/test/java/com/android/tools/r8/JavaCompilerTool.java
+++ b/src/test/java/com/android/tools/r8/JavaCompilerTool.java
@@ -26,8 +26,11 @@
private final CfRuntime jdk;
private final TestState state;
private final List<Path> sources = new ArrayList<>();
+ private final List<String> classNames = new ArrayList<>();
private final List<Path> classpath = new ArrayList<>();
private final List<String> options = new ArrayList<>();
+ private final List<Path> annotationProcessorPath = new ArrayList<>();
+ private final List<String> annotationProcessors = new ArrayList<>();
private Path output = null;
private JavaCompilerTool(CfRuntime jdk, TestState state) {
@@ -63,6 +66,11 @@
return this;
}
+ public JavaCompilerTool addClassNames(Collection<String> classNames) {
+ this.classNames.addAll(classNames);
+ return this;
+ }
+
public JavaCompilerTool addClasspathFiles(Path... files) {
return addClasspathFiles(Arrays.asList(files));
}
@@ -72,6 +80,24 @@
return this;
}
+ public JavaCompilerTool addAnnotationProcessorPathFiles(Path... files) {
+ return addAnnotationProcessorPathFiles(Arrays.asList(files));
+ }
+
+ public JavaCompilerTool addAnnotationProcessorPathFiles(Collection<Path> files) {
+ annotationProcessorPath.addAll(files);
+ return this;
+ }
+
+ public JavaCompilerTool addAnnotationProcessors(String... processors) {
+ return addAnnotationProcessor(Arrays.asList(processors));
+ }
+
+ public JavaCompilerTool addAnnotationProcessor(Collection<String> processors) {
+ annotationProcessors.addAll(processors);
+ return this;
+ }
+
/** Set the output. Must be to an existing directory or to an non-existing jar file. */
public JavaCompilerTool setOutputPath(Path file) {
assert (Files.exists(file) && Files.isDirectory(file))
@@ -103,6 +129,8 @@
return output;
}
+ // For javac command line options see
+ // https://docs.oracle.com/en/java/javase/17/docs/specs/man/javac.html.
private ProcessResult compileInternal(Path output) throws IOException {
Path outdir = Files.isDirectory(output) ? output : state.getNewTempFolder();
List<String> cmdline = new ArrayList<>();
@@ -115,11 +143,23 @@
.map(Path::toString)
.collect(Collectors.joining(isWindows() ? ";" : ":")));
}
+ if (!annotationProcessorPath.isEmpty()) {
+ cmdline.add("-processorpath");
+ cmdline.add(
+ annotationProcessorPath.stream()
+ .map(Path::toString)
+ .collect(Collectors.joining(isWindows() ? ";" : ":")));
+ }
+ if (!annotationProcessors.isEmpty()) {
+ cmdline.add("-processor");
+ cmdline.add(String.join(",", annotationProcessors));
+ }
cmdline.add("-d");
cmdline.add(outdir.toString());
for (Path source : sources) {
cmdline.add(source.toString());
}
+ cmdline.addAll(classNames);
ProcessBuilder builder = new ProcessBuilder(cmdline);
ProcessResult javacResult = ToolHelper.runProcess(builder);
if (FileUtils.isJarFile(output)) {
diff --git a/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java
new file mode 100644
index 0000000..c6ca2b1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingBindsTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.dagger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.DaggerUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DaggerBasicNotSingletonUsingBindsTest extends DaggerBasicTestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimes().withAllApiLevels().build());
+ }
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ compileWithoutSingleton();
+ }
+
+ public static final String MAIN_CLASS = "basic.MainUsingBinds";
+ public static final List<String> EXPECTED_OUTPUT =
+ ImmutableList.of("false", "false", "false", "I1Impl1", "I2Impl1", "I3Impl1");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertEquals(parameters.isCfRuntime() ? 1 : 2, inspector.allClasses().size());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .inspect(this::inspect)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingProvidesTest.java b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingProvidesTest.java
new file mode 100644
index 0000000..149b665
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dagger/DaggerBasicNotSingletonUsingProvidesTest.java
@@ -0,0 +1,80 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.dagger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.DaggerUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DaggerBasicNotSingletonUsingProvidesTest extends DaggerBasicTestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimes().withAllApiLevels().build());
+ }
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ compileWithoutSingleton();
+ }
+
+ public static final String MAIN_CLASS = "basic.MainUsingProvides";
+ public static final List<String> EXPECTED_OUTPUT =
+ ImmutableList.of("false", "false", "false", "I1Impl2", "I2Impl2", "I3Impl2");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertEquals(1, inspector.allClasses().size());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .inspect(this::inspect)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dagger/DaggerBasicSingletonUsingBindsTest.java b/src/test/java/com/android/tools/r8/dagger/DaggerBasicSingletonUsingBindsTest.java
new file mode 100644
index 0000000..50e0493
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dagger/DaggerBasicSingletonUsingBindsTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.dagger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.DaggerUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DaggerBasicSingletonUsingBindsTest extends DaggerBasicTestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimes().withAllApiLevels().build());
+ }
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ DaggerBasicTestBase.compileWithSingleton();
+ }
+
+ public static final String MAIN_CLASS = "basic.MainUsingBinds";
+ public static final List<String> EXPECTED_OUTPUT =
+ ImmutableList.of("true", "true", "true", "I1Impl1", "I2Impl1", "I3Impl1");
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ assertEquals(
+ ImmutableSet.of(
+ "basic.I1Impl1",
+ "basic.I2Impl1",
+ "basic.I3Impl1",
+ "basic.MainUsingBinds",
+ "basic.DaggerMainComponentUsingBinds",
+ "dagger.internal.DoubleCheck",
+ "javax.inject.Provider"),
+ inspector.allClasses().stream()
+ .map(FoundClassSubject::getOriginalName)
+ .filter(name -> !name.contains("_Factory"))
+ .collect(Collectors.toSet()));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> {
+ inspector
+ .assertIsCompleteMergeGroup(
+ "basic.I1Impl1_Factory", "basic.I2Impl1_Factory", "basic.I3Impl1_Factory")
+ .assertIsCompleteMergeGroup(
+ "basic.I1Impl1_Factory$InstanceHolder",
+ "basic.I2Impl1_Factory$InstanceHolder",
+ "basic.I3Impl1_Factory$InstanceHolder")
+ .assertNoOtherClassesMerged();
+ })
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype("basic.I1", "basic.I2", "basic.I3"))
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .inspect(this::inspect)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dagger/DaggerBasicSingletonUsingProvidesTest.java b/src/test/java/com/android/tools/r8/dagger/DaggerBasicSingletonUsingProvidesTest.java
new file mode 100644
index 0000000..dfe6a1a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dagger/DaggerBasicSingletonUsingProvidesTest.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.dagger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.DaggerUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DaggerBasicSingletonUsingProvidesTest extends DaggerBasicTestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(getTestParameters().withAllRuntimes().withAllApiLevels().build());
+ }
+
+ @BeforeClass
+ public static void setUp() throws Exception {
+ DaggerBasicTestBase.compileWithSingleton();
+ }
+
+ public static final String MAIN_CLASS = "basic.MainUsingProvides";
+ public static final List<String> EXPECTED_OUTPUT =
+ ImmutableList.of("true", "true", "true", "I1Impl2", "I2Impl2", "I3Impl2");
+
+ private void inspect(CodeInspector inspector) {
+ assertEquals(
+ ImmutableSet.of(
+ "basic.I1Impl2",
+ "basic.I2Impl2",
+ "basic.I3Impl2",
+ "basic.MainUsingProvides",
+ "basic.DaggerMainComponentUsingProvides",
+ "dagger.internal.DoubleCheck",
+ "javax.inject.Provider"),
+ inspector.allClasses().stream()
+ .map(FoundClassSubject::getOriginalName)
+ .filter(name -> !name.contains("Factory"))
+ .collect(Collectors.toSet()));
+ }
+
+ @Test
+ public void testJvm() throws Exception {
+ assumeTrue(parameters.isCfRuntime());
+ testForJvm()
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramNotDependingOnDagger)
+ .addProgramFiles(DaggerBasicTestBase.compiledProgramDependingOnDagger)
+ .addProgramFiles(DaggerUtils.getDaggerRuntime())
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(MAIN_CLASS)
+ .addHorizontallyMergedClassesInspector(
+ inspector -> {
+ inspector
+ .assertIsCompleteMergeGroup(
+ "basic.ModuleUsingProvides_I1Factory",
+ "basic.ModuleUsingProvides_I2Factory",
+ "basic.ModuleUsingProvides_I3Factory")
+ .assertIsCompleteMergeGroup(
+ "basic.ModuleUsingProvides_I1Factory$InstanceHolder",
+ "basic.ModuleUsingProvides_I2Factory$InstanceHolder",
+ "basic.ModuleUsingProvides_I3Factory$InstanceHolder")
+ .assertIsCompleteMergeGroup(
+ "basic.ModuleUsingProvides", "basic.DaggerMainComponentUsingProvides$1")
+ .assertNoOtherClassesMerged();
+ })
+ .addVerticallyMergedClassesInspector(
+ inspector -> inspector.assertMergedIntoSubtype("basic.I1", "basic.I2", "basic.I3"))
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .inspect(this::inspect)
+ .assertSuccessWithOutputLines(EXPECTED_OUTPUT);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dagger/DaggerBasicTestBase.java b/src/test/java/com/android/tools/r8/dagger/DaggerBasicTestBase.java
new file mode 100644
index 0000000..3f56ab1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/dagger/DaggerBasicTestBase.java
@@ -0,0 +1,141 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.dagger;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.transformers.ClassTransformer;
+import com.android.tools.r8.transformers.MethodTransformer;
+import com.android.tools.r8.utils.DaggerUtils;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class DaggerBasicTestBase extends TestBase {
+
+ public static Path compiledProgramNotDependingOnDagger;
+ public static Path compiledProgramDependingOnDagger;
+ public static Path daggerGeneratedSourceFiles;
+
+ private static void compile(
+ List<Path> sourceNotDependingOnDagger,
+ List<Path> sourceDependingOnDagger,
+ BiFunction<String, byte[], byte[]> transformer)
+ throws Exception {
+ compiledProgramNotDependingOnDagger =
+ DaggerUtils.compileWithoutAnnotationProcessing(sourceNotDependingOnDagger);
+ // Use transformer on code not depending on Dagger.
+ if (transformer != null) {
+ compiledProgramNotDependingOnDagger =
+ ZipUtils.map(
+ compiledProgramNotDependingOnDagger,
+ getStaticTemp().newFolder().toPath().resolve("transformed.jar"),
+ (entry, bytes) ->
+ FileUtils.isClassFile(entry.getName())
+ ? transformer.apply(
+ entry
+ .getName()
+ .substring(
+ 0, entry.getName().length() - FileUtils.CLASS_EXTENSION.length()),
+ bytes)
+ : bytes);
+ }
+ // Compile with Dagger annotation processor. The part of the program not relying on dagger
+ // generated types are passed as class files (potentially after transformation) and on command
+ // line for being included in annotation processing.
+ compiledProgramDependingOnDagger =
+ DaggerUtils.compileWithAnnotationProcessing(
+ sourceDependingOnDagger, ImmutableList.of(compiledProgramNotDependingOnDagger));
+ daggerGeneratedSourceFiles =
+ ZipUtils.filter(
+ compiledProgramDependingOnDagger,
+ getStaticTemp().newFolder().toPath().resolve("source.jar"),
+ entry -> entry.getName().endsWith(".java"));
+ // Check the generated Dagger source files.
+ Set<String> generatedFiles = new HashSet<>();
+ ZipUtils.iter(
+ DaggerBasicTestBase.daggerGeneratedSourceFiles,
+ (entry, unused) -> generatedFiles.add(entry.getName()));
+ assertEquals(
+ ImmutableSet.of(
+ "basic/I1Impl1_Factory.java",
+ "basic/I2Impl1_Factory.java",
+ "basic/I3Impl1_Factory.java",
+ "basic/ModuleUsingProvides_I1Factory.java",
+ "basic/ModuleUsingProvides_I2Factory.java",
+ "basic/ModuleUsingProvides_I3Factory.java",
+ "basic/ModuleUsingProvides_Proxy.java",
+ "basic/DaggerMainComponentUsingBinds.java",
+ "basic/DaggerMainComponentUsingProvides.java"),
+ generatedFiles);
+ }
+
+ private static boolean sourceFileReferingDaggerGeneratedClasses(Path file) {
+ return file.getFileName().toString().startsWith("Main");
+ }
+
+ static void compileWithSingleton() throws Exception {
+ compile(
+ javaSourceFiles("basic", path -> !sourceFileReferingDaggerGeneratedClasses(path)),
+ javaSourceFiles("basic", DaggerBasicTestBase::sourceFileReferingDaggerGeneratedClasses),
+ DaggerBasicTestBase::transformAddSingleton);
+ }
+
+ static void compileWithoutSingleton() throws Exception {
+ compile(
+ javaSourceFiles("basic", path -> !sourceFileReferingDaggerGeneratedClasses(path)),
+ javaSourceFiles("basic", DaggerBasicTestBase::sourceFileReferingDaggerGeneratedClasses),
+ null);
+ }
+
+ private static byte[] transformAddSingleton(String binaryName, byte[] bytes) {
+ // Add @Singleton to the constructors used with @Bind.
+ if (binaryName.endsWith("Impl1")) {
+ return transformer(bytes, Reference.classFromBinaryName(binaryName))
+ .addClassTransformer(
+ new ClassTransformer() {
+ @Override
+ public void visitEnd() {
+ super.visitAnnotation("Ljavax/inject/Singleton;", true);
+ }
+ })
+ .transform();
+ }
+ // Add @Singleton to the methods annotated with @Provides.
+ if (binaryName.endsWith("ModuleUsingProvides")) {
+ return transformer(bytes, Reference.classFromBinaryName(binaryName))
+ .addMethodTransformer(
+ new MethodTransformer() {
+ @Override
+ public void visitEnd() {
+ super.visitAnnotation("Ljavax/inject/Singleton;", true);
+ }
+ })
+ .transform();
+ }
+ return bytes;
+ }
+
+ public static List<Path> javaSourceFiles(String testDir, Predicate<Path> filter)
+ throws Exception {
+ try (Stream<Path> walk =
+ Files.walk(Paths.get(ToolHelper.TESTS_DIR, "examplesDagger", testDir))) {
+ return walk.filter(filter).filter(FileUtils::isJavaFile).collect(Collectors.toList());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/DaggerUtils.java b/src/test/java/com/android/tools/r8/utils/DaggerUtils.java
new file mode 100644
index 0000000..24eabb5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/DaggerUtils.java
@@ -0,0 +1,111 @@
+// Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.utils;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRuntime;
+import com.google.common.collect.ImmutableList;
+import com.google.common.io.ByteStreams;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class DaggerUtils {
+
+ private static final Path DAGGER_ROOT = Paths.get("third_party", "dagger", "2.41");
+ private static final List<Path> DAGGER_COMPILER =
+ ImmutableList.of(
+ "annotations-13.0.jar",
+ "checker-compat-qual-2.5.5.jar",
+ "checker-qual-3.12.0.jar",
+ "dagger-2.41.jar",
+ "dagger-compiler-2.41.jar",
+ "dagger-producers-2.41.jar",
+ "dagger-spi-2.41.jar",
+ "error_prone_annotations-2.7.1.jar",
+ "failureaccess-1.0.1.jar",
+ "google-java-format-1.5.jar",
+ "guava-31.0.1-jre.jar",
+ "incap-0.2.jar",
+ "j2objc-annotations-1.3.jar",
+ "javac-shaded-9-dev-r4023-3.jar",
+ "javapoet-1.13.0.jar",
+ "javax.inject-1.jar",
+ "jsr305-3.0.2.jar",
+ "kotlin-stdlib-1.5.32.jar",
+ "kotlin-stdlib-common-1.5.32.jar",
+ "kotlin-stdlib-jdk7-1.5.32.jar",
+ "kotlin-stdlib-jdk8-1.5.32.jar",
+ "kotlinx-metadata-jvm-0.3.0.jar",
+ "symbol-processing-api-1.5.30-1.0.0.jar")
+ .stream()
+ .map(DAGGER_ROOT::resolve)
+ .collect(ImmutableList.toImmutableList());
+ private static final List<Path> DAGGER_RUNTIME =
+ ImmutableList.of("dagger-2.41.jar", "javax.inject-1.jar").stream()
+ .map(DAGGER_ROOT::resolve)
+ .collect(ImmutableList.toImmutableList());
+
+ public static List<Path> getDaggerRuntime() {
+ return DAGGER_RUNTIME;
+ }
+
+ public static Path compileWithAnnotationProcessing(
+ Collection<Path> sourceFiles, Collection<Path> classFiles) throws Exception {
+ // Class files are provided in JAR files. Extract all the class names to pass to javac for
+ // annotation processing.
+ List<String> classNames = new ArrayList<>();
+ for (Path path : classFiles) {
+ ZipUtils.iter(
+ path,
+ (entry, inputStream) -> {
+ String entryString = entry.toString();
+ if (FileUtils.isClassFile(entryString)) {
+ byte[] bytes = ByteStreams.toByteArray(inputStream);
+ classNames.add(TestBase.extractClassName(bytes));
+ }
+ });
+ }
+ return TestBase.javac(TestRuntime.getCheckedInJdk8(), TestBase.getStaticTemp())
+ .addSourceFiles(sourceFiles)
+ .addClassNames(classNames)
+ .addClasspathFiles(classFiles)
+ .addClasspathFiles(getDaggerRuntime())
+ .addAnnotationProcessorPathFiles(DAGGER_COMPILER)
+ .addAnnotationProcessors("dagger.internal.codegen.ComponentProcessor")
+ .compile();
+ }
+
+ public static Path compileWithAnnotationProcessing(Collection<Path> files) throws Exception {
+ // Split the files passed into source files and class files. Class files are expected to be in
+ // JARs.
+ List<Path> sourceFiles =
+ files.stream().filter(FileUtils::isJavaFile).collect(Collectors.toList());
+ List<Path> classFiles =
+ files.stream().filter(FileUtils::isJarFile).collect(Collectors.toList());
+ assertEquals(files.size(), sourceFiles.size() + classFiles.size());
+ return compileWithAnnotationProcessing(sourceFiles, classFiles);
+ }
+
+ public static Path compileWithoutAnnotationProcessing(Collection<Path> files) throws Exception {
+ return TestBase.javac(TestRuntime.getCheckedInJdk8(), TestBase.getStaticTemp())
+ .addSourceFiles(files)
+ .addClasspathFiles(getDaggerRuntime())
+ .compile();
+ }
+
+ public static Path compileWithoutAnnotationProcessing(Path... files) throws Exception {
+ return compileWithoutAnnotationProcessing(Arrays.asList(files));
+ }
+
+ public static Path compileWithAnnotationProcessing(Path... files) throws Exception {
+ return compileWithAnnotationProcessing(Arrays.asList(files));
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
index 05031f2..d1f04cb 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/VerticallyMergedClassesInspector.java
@@ -9,6 +9,8 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
public class VerticallyMergedClassesInspector {
@@ -33,6 +35,20 @@
return this;
}
+ public VerticallyMergedClassesInspector assertMergedIntoSubtype(ClassReference classReference) {
+ assertTrue(
+ verticallyMergedClasses.hasBeenMergedIntoSubtype(
+ toDexType(classReference, dexItemFactory)));
+ return this;
+ }
+
+ public VerticallyMergedClassesInspector assertMergedIntoSubtype(String... typeNames) {
+ for (String typeName : typeNames) {
+ assertMergedIntoSubtype(Reference.classFromTypeName(typeName));
+ }
+ return this;
+ }
+
public VerticallyMergedClassesInspector assertNoClassesMerged() {
assertTrue(verticallyMergedClasses.isEmpty());
return this;