Add tests for reading archives with both class files and DEX files
Bug: b/307998468
Change-Id: Idf2dd88fdaba57e39f3d92be193e8c06718a89f7
diff --git a/src/test/java/com/android/tools/r8/files/ArchiveWithDexTest.java b/src/test/java/com/android/tools/r8/files/ArchiveWithDexTest.java
new file mode 100644
index 0000000..0c3bd18
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/files/ArchiveWithDexTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2023, 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.files;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.ArchiveProgramResourceProvider;
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ArchiveResourceProvider;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+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 ArchiveWithDexTest extends TestBase {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withNoneRuntime().build();
+ }
+
+ private static Path zipWithDexAndClass;
+
+ @BeforeClass
+ public static void createZipWitDexAndClass() throws IOException {
+ Path zipContent = getStaticTemp().newFolder().toPath();
+ Files.copy(
+ ToolHelper.getClassFileForTestClass(TestClass.class),
+ zipContent.resolve("TestClass.class"));
+ Files.createFile(zipContent.resolve("other.dex"));
+ zipWithDexAndClass = getStaticTemp().newFolder().toPath().resolve("input.zip");
+ ZipUtils.zip(zipWithDexAndClass, zipContent);
+ }
+
+ private void checkOneDexFiles(Path archive) throws Exception {
+ BooleanBox seenClassesDex = new BooleanBox();
+ ZipUtils.iter(
+ archive,
+ (entry, stream) -> {
+ if (entry.getName().equals("classes.dex")) {
+ seenClassesDex.set();
+ } else {
+ fail();
+ }
+ });
+ assertTrue(seenClassesDex.get());
+ }
+
+ private void checkTwoDexFiles(Path archive) throws Exception {
+ BooleanBox seenClassesDex = new BooleanBox();
+ BooleanBox seenOtherDex = new BooleanBox();
+ ZipUtils.iter(
+ archive,
+ (entry, stream) -> {
+ if (entry.getName().equals("classes.dex")) {
+ seenClassesDex.set();
+ } else if (entry.getName().equals("other.dex")) {
+ seenOtherDex.set();
+ } else {
+ fail();
+ }
+ });
+ assertTrue(seenClassesDex.get() && seenOtherDex.get());
+ }
+
+ @Test
+ public void testR8() {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(Backend.DEX)
+ .addProgramResourceProviders(
+ ArchiveProgramResourceProvider.fromArchive(zipWithDexAndClass))
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString(
+ "Cannot create android app from an archive containing both DEX"
+ + " and Java-bytecode content.")))));
+ }
+
+ @Test
+ public void testR8WithFilter() throws Exception {
+ checkOneDexFiles(
+ testForR8(Backend.DEX)
+ .addProgramResourceProviders(
+ ArchiveProgramResourceProvider.fromArchive(
+ zipWithDexAndClass, FileUtils::isClassFile))
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .writeToZip());
+ }
+
+ @Test
+ public void testR8LegacyProgramResourceProviderIgnoreDex() throws Exception {
+ // The other.dex is seen as a resource and passed through.
+ checkTwoDexFiles(
+ testForR8(Backend.DEX)
+ .addProgramResourceProviders(
+ ArchiveResourceProvider.fromArchive(zipWithDexAndClass, true))
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compile()
+ .writeToZip());
+ }
+
+ @Test
+ public void testR8LegacyProgramResourceProviderDontIgnoreDex() {
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(Backend.DEX)
+ .addProgramResourceProviders(
+ ArchiveResourceProvider.fromArchive(zipWithDexAndClass, false))
+ .addKeepMainRule(TestClass.class)
+ .setMinApi(AndroidApiLevel.B)
+ .compileWithExpectedDiagnostics(
+ diagnostics ->
+ diagnostics.assertErrorsMatch(
+ diagnosticMessage(
+ containsString("containing both DEX and Java-bytecode content")))));
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ System.out.println("Hello, world!");
+ }
+ }
+}