Add a sanity check test for processkeepruleslib.jar

Change-Id: Ibfd7e7072216c149b528015027ba5f91f0f99c9c
diff --git a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
index 6b5f2a1..a1844cc 100644
--- a/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
+++ b/d8_r8/commonBuildSrc/src/main/kotlin/TestConfigurationHelper.kt
@@ -113,9 +113,15 @@
       if (project.hasProperty("no_internal")) {
         test.exclude("com/android/tools/r8/internal/**")
       }
+
+      // Include sanity checks on lib jars when running --only_internal.
       if (project.hasProperty("only_internal")) {
         test.include("com/android/tools/r8/internal/**")
+        test.include("com/android/tools/r8/processkeeprules/sanitychecks/**")
+      } else {
+        test.exclude("com/android/tools/r8/processkeeprules/sanitychecks/**")
       }
+
       if (project.hasProperty("no_arttests")) {
         test.exclude("com/android/tools/r8/art/**")
       }
diff --git a/d8_r8/test/build.gradle.kts b/d8_r8/test/build.gradle.kts
index c82208b..0e7392d 100644
--- a/d8_r8/test/build.gradle.kts
+++ b/d8_r8/test/build.gradle.kts
@@ -27,6 +27,7 @@
 val assistantJarTask = projectTask("assistant", "jar")
 val mainDepsJarTask = projectTask("main", "depsJar")
 val swissArmyKnifeTask = projectTask("main", "swissArmyKnife")
+val processKeepRulesLibWithRelocatedDepsTask = projectTask("main", "processKeepRulesLibWithRelocatedDeps")
 val r8WithRelocatedDepsTask = projectTask("main", "r8WithRelocatedDeps")
 val mainSourcesTask = projectTask("main", "sourcesJar")
 val resourceShrinkerSourcesTask = projectTask("resourceshrinker", "sourcesJar")
@@ -500,6 +501,10 @@
       dependsOn(gradle.includedBuild("shared").task(":downloadDepsInternal"))
       dependsOn(gradle.includedBuild("shared").task(":downloadTestDepsInternal"))
     }
+    // Build processkeepruleslib.jar when running with --only_internal.
+    if (project.hasProperty("only_internal")) {
+      dependsOn(processKeepRulesLibWithRelocatedDepsTask)
+    }
     if (project.hasProperty("r8lib")) {
       dependsOn(testR8LibWithRelocatedDeps)
     } else if (project.hasProperty("r8lib_no_deps")) {
diff --git a/src/test/java/com/android/tools/r8/processkeeprules/sanitychecks/ProcessKeepRulesSanityCheckTest.java b/src/test/java/com/android/tools/r8/processkeeprules/sanitychecks/ProcessKeepRulesSanityCheckTest.java
new file mode 100644
index 0000000..91217ca
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/processkeeprules/sanitychecks/ProcessKeepRulesSanityCheckTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2025, 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.processkeeprules.sanitychecks;
+
+import static com.android.tools.r8.ToolHelper.PROCESS_KEEP_RULES_JAR;
+import static com.android.tools.r8.ToolHelper.PROCESS_KEEP_RULES_MAP;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.Version;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.FileUtils;
+import com.android.tools.r8.utils.StreamUtils;
+import com.android.tools.r8.utils.ZipUtils;
+import java.nio.file.Files;
+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;
+
+/**
+ * This test relies on build/libs/processkeepruleslib.jar being built. This test therefore only runs
+ * when test.py is run with the --only_internal flag, which adds a dependency on
+ * :main:processKeepRulesLibWithRelocatedDeps.
+ */
+@RunWith(Parameterized.class)
+public class ProcessKeepRulesSanityCheckTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void testProcessKeepRulesJarContent() throws Exception {
+    assertTrue(Files.exists(PROCESS_KEEP_RULES_JAR));
+    assertTrue(Files.exists(PROCESS_KEEP_RULES_MAP));
+    BooleanBox apiDatabaseSeen = new BooleanBox();
+    BooleanBox licenseSeen = new BooleanBox();
+    BooleanBox manifestSeen = new BooleanBox();
+    BooleanBox versionSeen = new BooleanBox();
+    ZipUtils.iter(
+        PROCESS_KEEP_RULES_JAR,
+        (entry, input) -> {
+          String name = entry.getName();
+          if (ZipUtils.isClassFile(name) || FileUtils.isKotlinBuiltinsFile(name)) {
+            assertThat(name, startsWith("com/android/tools/r8/"));
+            if (ZipUtils.isClassFile(name)) {
+              byte[] classFileBytes = StreamUtils.streamToByteArrayClose(input);
+              String sourceFile = extractSourceFile(classFileBytes);
+              assertNotNull(sourceFile);
+              if (Version.isMainVersion()) {
+                assertTrue(sourceFile, sourceFile.startsWith("R8_"));
+                assertEquals(108, sourceFile.length());
+              } else {
+                assertTrue(sourceFile, sourceFile.startsWith("R8_" + Version.LABEL));
+                assertEquals(68 + Version.LABEL.length(), sourceFile.length());
+              }
+              versionSeen.or(name.equals("com/android/tools/r8/Version.class"));
+            }
+          } else if (name.equals("META-INF/MANIFEST.MF")) {
+            manifestSeen.set();
+          } else if (name.equals("LICENSE")) {
+            licenseSeen.set();
+          } else if (name.equals("resources/new_api_database.ser")) {
+            apiDatabaseSeen.set();
+          } else if (name.endsWith("/")) {
+            fail("Unexpected directory entry '" + name + "'");
+          } else {
+            fail("Unexpected entry '" + name + "'");
+          }
+        });
+    assertTrue("Api database entry FOUND", apiDatabaseSeen.isFalse());
+    assertTrue("LICENSE entry NOT FOUND", licenseSeen.isTrue());
+    assertTrue("META-INF/MANIFEST.MF entry NOT FOUND", manifestSeen.isTrue());
+    assertTrue("com/android/tools/r8/Version.class entry NOT FOUND", versionSeen.isTrue());
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
index 59ca961..086e9b5 100644
--- a/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/testbase/java/com/android/tools/r8/ToolHelper.java
@@ -264,6 +264,9 @@
   public static final Path R8_WITH_RELOCATED_DEPS_17_JAR =
       Paths.get(LIBS_DIR, "r8_with_relocated_deps_17.jar");
   public static final Path ASSISTANT_JAR = Paths.get(LIBS_DIR, "assistant.jar");
+  public static final Path PROCESS_KEEP_RULES_JAR = Paths.get(LIBS_DIR, "processkeepruleslib.jar");
+  public static final Path PROCESS_KEEP_RULES_MAP =
+      Paths.get(LIBS_DIR, "processkeepruleslib.jar.map");
   public static final Path R8LIB_JAR = Paths.get(LIBS_DIR, "r8lib.jar");
   public static final Path R8LIB_MAP = Paths.get(LIBS_DIR, "r8lib.jar.map");
   public static final Path R8LIB_MAP_PARTITIONED = Paths.get(LIBS_DIR, "r8lib.jar_map.zip");