Add example for soft verification slow down test builders
Bug: 187496508
Change-Id: Id5f9d9f749a606e7c2e536ef9c2ada7d623416af
diff --git a/.gitignore b/.gitignore
index b929f34..b03bb7b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,8 @@
tests/2016-12-19/art.tar.gz
tests/2017-10-04/art
tests/2017-10-04/art.tar.gz
+third_party/api-outlining/simple-app-dump
+third_party/api-outlining/simple-app-dump.tar.gz
third_party/android_cts_baseline
third_party/android_cts_baseline.tar.gz
third_party/clank/clank_google3_prebuilt
diff --git a/build.gradle b/build.gradle
index 8e20e6d..3101886 100644
--- a/build.gradle
+++ b/build.gradle
@@ -315,6 +315,7 @@
"android_jar/lib-v29",
"android_jar/lib-v30",
"android_jar/lib-v31",
+ "api-outlining/simple-app-dump",
"core-lambda-stubs",
"dart-sdk",
"ddmlib",
diff --git a/src/test/examplesAndroidApi/softverificationerror/ApiCallerInlined.java b/src/test/examplesAndroidApi/softverificationerror/ApiCallerInlined.java
new file mode 100644
index 0000000..0ef2b5c
--- /dev/null
+++ b/src/test/examplesAndroidApi/softverificationerror/ApiCallerInlined.java
@@ -0,0 +1,41 @@
+// Copyright (c) 2021, 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.example.softverificationerror;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Build;
+
+public class ApiCallerInlined {
+
+ public static void callApi(android.content.Context context) {
+ // Create the NotificationChannel, but only on API 26+ because
+ // the NotificationChannel class is new and not in the support library
+ if (Build.VERSION.SDK_INT >= 26) {
+ constructUnknownObjectAndCallUnknownMethod(context);
+ }
+ }
+
+ public static void constructUnknownObject(android.content.Context context) {
+ NotificationChannel channel =
+ new NotificationChannel("CHANNEL_ID", "FOO", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription("This is a test channel");
+ }
+
+ public static void callUnknownMethod(android.content.Context context) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(null);
+ }
+
+ public static void constructUnknownObjectAndCallUnknownMethod(android.content.Context context) {
+ NotificationChannel channel =
+ new NotificationChannel("CHANNEL_ID", "FOO", NotificationManager.IMPORTANCE_DEFAULT);
+ channel.setDescription("This is a test channel");
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(channel);
+ }
+}
diff --git a/src/test/examplesAndroidApi/softverificationerror/ApiCallerOutlined.java b/src/test/examplesAndroidApi/softverificationerror/ApiCallerOutlined.java
new file mode 100644
index 0000000..47c3e61
--- /dev/null
+++ b/src/test/examplesAndroidApi/softverificationerror/ApiCallerOutlined.java
@@ -0,0 +1,16 @@
+// Copyright (c) 2021, 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.example.softverificationerror;
+
+import android.os.Build;
+
+public class ApiCallerOutlined {
+
+ public static void callApi(android.content.Context context) {
+ if (Build.VERSION.SDK_INT >= 26) {
+ ApiCallerInlined.constructUnknownObjectAndCallUnknownMethod(context);
+ }
+ }
+}
diff --git a/src/test/examplesAndroidApi/softverificationerror/Main.java b/src/test/examplesAndroidApi/softverificationerror/Main.java
new file mode 100644
index 0000000..f2a4376
--- /dev/null
+++ b/src/test/examplesAndroidApi/softverificationerror/Main.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2021, 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.example.softverificationerror;
+
+public class Main {
+
+ public static void test(android.content.Context context) {
+ ApiCallerInlined.callApi(context);
+ ApiCallerOutlined.callApi(context);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/SoftVerificationErrorJarGenerator.java b/src/test/java/com/android/tools/r8/SoftVerificationErrorJarGenerator.java
new file mode 100644
index 0000000..50f6e05
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/SoftVerificationErrorJarGenerator.java
@@ -0,0 +1,347 @@
+// Copyright (c) 2021, 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;
+
+import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.Pair;
+import com.android.tools.r8.utils.ZipUtils;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.util.function.Supplier;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class SoftVerificationErrorJarGenerator {
+
+ public enum ApiCallerName {
+ CONSTRUCT_UNKNOWN("constructUnknownObject"),
+ CALL_UNKNOWN("callUnknownMethod"),
+ CONSTRUCT_AND_CALL_UNKNOWN("constructUnknownObjectAndCallUnknownMethod");
+
+ private String apiCallerMethodName;
+
+ ApiCallerName(String apiCallerMethodName) {
+ this.apiCallerMethodName = apiCallerMethodName;
+ }
+
+ public String getApiCallerMethodName() {
+ return apiCallerMethodName;
+ }
+ }
+
+ public static String NEW_API_CLASS_NAME = "android/app/NotificationChannel";
+ public static String NEW_API_CLASS_METHOD_NAME = "setDescription";
+ public static String EXISTING_API_METHOD_NAME = "createNotificationChannel";
+
+ public static void createJar(
+ Path archive,
+ int numberOfClasses,
+ boolean isOutlined,
+ ApiCallerName callerName,
+ String newApiClassName,
+ String newApiClassMethodName,
+ String existingApiNewMethodName)
+ throws IOException {
+ OpenOption[] options =
+ new OpenOption[] {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING};
+ try (ZipOutputStream out =
+ new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(archive, options)))) {
+ IntBox intBox = new IntBox(0);
+ Pair<byte[], String> callerPair =
+ isOutlined
+ ? Dumps.dumpApiCallerInlined(
+ -1,
+ callerName.getApiCallerMethodName(),
+ newApiClassName,
+ newApiClassMethodName,
+ existingApiNewMethodName)
+ : null;
+ if (callerPair != null) {
+ // The outlined code will call the apiCallerName on the callerPair code.
+ ZipUtils.writeToZipStream(
+ out, callerPair.getSecond() + ".class", callerPair.getFirst(), ZipEntry.STORED);
+ }
+ byte[] mainBytes =
+ Dumps.dumpMain(
+ () -> {
+ if (intBox.get() > numberOfClasses) {
+ return null;
+ }
+ Pair<byte[], String> classData =
+ isOutlined
+ ? Dumps.dumpApiCallerOutlined(
+ intBox.getAndIncrement(),
+ callerPair.getSecond(),
+ callerName.getApiCallerMethodName())
+ : Dumps.dumpApiCallerInlined(
+ intBox.getAndIncrement(),
+ callerName.getApiCallerMethodName(),
+ newApiClassName,
+ newApiClassMethodName,
+ existingApiNewMethodName);
+ try {
+ ZipUtils.writeToZipStream(
+ out, classData.getSecond() + ".class", classData.getFirst(), ZipEntry.STORED);
+ } catch (IOException exception) {
+ throw new RuntimeException(exception);
+ }
+ return classData.getSecond();
+ });
+ ZipUtils.writeToZipStream(
+ out, "com/example/softverificationsample/TestRunner.class", mainBytes, ZipEntry.STORED);
+ }
+ }
+
+ public static class Dumps implements Opcodes {
+
+ public static byte[] dumpMain(Supplier<String> targetSupplier) {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(
+ V1_8,
+ ACC_PUBLIC | ACC_SUPER,
+ "com/example/softverificationsample/TestRunner",
+ null,
+ "java/lang/Object",
+ null);
+
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "run", "(Landroid/content/Context;)V", null, null);
+ methodVisitor.visitCode();
+ String target = targetSupplier.get();
+ while (target != null) {
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, target, "callApi", "(Landroid/content/Context;)V", false);
+ target = targetSupplier.get();
+ }
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ public static Pair<byte[], String> dumpApiCallerOutlined(
+ int index, String apiCallerName, String apiMethodCaller) {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ String binaryName =
+ "com/example/softverificationsample/ApiCallerOutlined" + (index > -1 ? index : "");
+
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, binaryName, null, "java/lang/Object", null);
+
+ classWriter.visitInnerClass(
+ "android/os/Build$VERSION", "android/os/Build", "VERSION", ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "callApi", "(Landroid/content/Context;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "android/os/Build$VERSION", "SDK_INT", "I");
+ methodVisitor.visitIntInsn(BIPUSH, 26);
+ Label label0 = new Label();
+ methodVisitor.visitJumpInsn(IF_ICMPLT, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, apiCallerName, apiMethodCaller, "(Landroid/content/Context;)V", false);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return Pair.create(classWriter.toByteArray(), binaryName);
+ }
+
+ public static Pair<byte[], String> dumpApiCallerInlined(
+ int index,
+ String apiMethodCaller,
+ String newApiClassName,
+ String newApiClassMethodName,
+ String existingApiNewMethodName) {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ String binaryName =
+ "com/example/softverificationsample/ApiCallerInlined" + (index > -1 ? index : "");
+
+ classWriter.visit(V1_8, ACC_PUBLIC | ACC_SUPER, binaryName, null, "java/lang/Object", null);
+
+ classWriter.visitInnerClass(
+ "android/os/Build$VERSION", "android/os/Build", "VERSION", ACC_PUBLIC | ACC_STATIC);
+
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "callApi", "(Landroid/content/Context;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "android/os/Build$VERSION", "SDK_INT", "I");
+ methodVisitor.visitIntInsn(BIPUSH, 26);
+ Label label0 = new Label();
+ methodVisitor.visitJumpInsn(IF_ICMPLT, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(
+ INVOKESTATIC, binaryName, apiMethodCaller, "(Landroid/content/Context;)V", false);
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "constructUnknownObject",
+ "(Landroid/content/Context;)V",
+ null,
+ null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(NEW, newApiClassName);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitLdcInsn("CHANNEL_ID");
+ methodVisitor.visitLdcInsn("FOO");
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "android/app/NotificationManager", "IMPORTANCE_DEFAULT", "I");
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ newApiClassName,
+ "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;I)V",
+ false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitLdcInsn("This is a test channel");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, newApiClassName, newApiClassMethodName, "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(5, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "callUnknownMethod",
+ "(Landroid/content/Context;)V",
+ null,
+ null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitLdcInsn(Type.getType("Landroid/app/NotificationManager;"));
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "android/content/Context",
+ "getSystemService",
+ "(Ljava/lang/Class;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitTypeInsn(CHECKCAST, "android/app/NotificationManager");
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitInsn(ACONST_NULL);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "android/app/NotificationManager",
+ existingApiNewMethodName,
+ "(L" + newApiClassName + ";)V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC,
+ "constructUnknownObjectAndCallUnknownMethod",
+ "(Landroid/content/Context;)V",
+ null,
+ null);
+ methodVisitor.visitCode();
+ methodVisitor.visitTypeInsn(NEW, newApiClassName);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitLdcInsn("CHANNEL_ID");
+ methodVisitor.visitLdcInsn("FOO");
+ methodVisitor.visitFieldInsn(
+ GETSTATIC, "android/app/NotificationManager", "IMPORTANCE_DEFAULT", "I");
+ methodVisitor.visitMethodInsn(
+ INVOKESPECIAL,
+ newApiClassName,
+ "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;I)V",
+ false);
+ methodVisitor.visitVarInsn(ASTORE, 1);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitLdcInsn("This is a test channel");
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, newApiClassName, newApiClassMethodName, "(Ljava/lang/String;)V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitLdcInsn(Type.getType("Landroid/app/NotificationManager;"));
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "android/content/Context",
+ "getSystemService",
+ "(Ljava/lang/Class;)Ljava/lang/Object;",
+ false);
+ methodVisitor.visitTypeInsn(CHECKCAST, "android/app/NotificationManager");
+ methodVisitor.visitVarInsn(ASTORE, 2);
+ methodVisitor.visitVarInsn(ALOAD, 2);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL,
+ "android/app/NotificationManager",
+ existingApiNewMethodName,
+ "(L" + newApiClassName + ";)V",
+ false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(5, 3);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return Pair.create(classWriter.toByteArray(), binaryName);
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/SoftVerificationErrorJarRunner.java b/src/test/java/com/android/tools/r8/SoftVerificationErrorJarRunner.java
new file mode 100644
index 0000000..80a2879
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/SoftVerificationErrorJarRunner.java
@@ -0,0 +1,118 @@
+// Copyright (c) 2021, 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;
+
+import static com.android.tools.r8.SoftVerificationErrorJarGenerator.EXISTING_API_METHOD_NAME;
+import static com.android.tools.r8.SoftVerificationErrorJarGenerator.NEW_API_CLASS_METHOD_NAME;
+import static com.android.tools.r8.SoftVerificationErrorJarGenerator.NEW_API_CLASS_NAME;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.SoftVerificationErrorJarGenerator.ApiCallerName;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.ApkUtils;
+import com.android.tools.r8.utils.BooleanBox;
+import com.android.tools.r8.utils.ZipUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class SoftVerificationErrorJarRunner extends TestBase {
+
+ public static Path DUMP_PATH =
+ Paths.get("third_party", "api-outlining", "simple-app-dump", "simple-app-dump.zip");
+ public static Path APK_PATH =
+ Paths.get("third_party", "api-outlining", "simple-app-dump", "app-release-unsigned.apk");
+
+ private final int numberOfClasses;
+ private final boolean isOutlined;
+
+ public SoftVerificationErrorJarRunner(int numberOfClasses, boolean isOutlined) {
+ this.numberOfClasses = numberOfClasses;
+ this.isOutlined = isOutlined;
+ }
+
+ public static void main(String[] args) throws Exception {
+ new SoftVerificationErrorJarRunner(1000, false).runTest();
+ }
+
+ public void runTest() throws Exception {
+
+ temp.create();
+
+ Path tempFolder = temp.newFolder().toPath();
+ Path outlineJar = tempFolder.resolve("outlined.jar");
+
+ SoftVerificationErrorJarGenerator.createJar(
+ outlineJar,
+ numberOfClasses,
+ isOutlined,
+ ApiCallerName.CONSTRUCT_AND_CALL_UNKNOWN,
+ NEW_API_CLASS_NAME,
+ NEW_API_CLASS_METHOD_NAME,
+ EXISTING_API_METHOD_NAME);
+
+ ZipUtils.unzip(DUMP_PATH.toString(), tempFolder.toFile());
+
+ File filteredProgramFolder = temp.newFolder();
+ BooleanBox seenTestRunner = new BooleanBox();
+ ZipUtils.unzip(
+ tempFolder.resolve("program.jar").toFile().toString(),
+ filteredProgramFolder,
+ zipEntry -> {
+ if (zipEntry.getName().equals("com/example/softverificationsample/TestRunner.class")) {
+ seenTestRunner.set();
+ return false;
+ }
+ return true;
+ });
+
+ assertTrue(seenTestRunner.get());
+
+ Path filteredProgramJar = tempFolder.resolve("filtered_program.jar");
+ ZipUtils.zip(filteredProgramJar, filteredProgramFolder.toPath());
+
+ // Build the app with R8.
+ Path output =
+ testForR8(Backend.DEX)
+ .addProgramFiles(outlineJar, filteredProgramJar)
+ .addClasspathFiles(tempFolder.resolve("classpath.jar"))
+ .addLibraryFiles(tempFolder.resolve("library.jar"))
+ // TODO(b/187496508): Modify keep rules to allow inlining and keep test code.
+ .addKeepRuleFiles(tempFolder.resolve("proguard.config"))
+ .addKeepRules("-keep class com.example.softverificationsample.* { *; }")
+ .setMinApi(AndroidApiLevel.M)
+ .allowUnusedProguardConfigurationRules()
+ .allowUnusedDontWarnPatterns()
+ .allowDiagnosticInfoMessages()
+ .compile()
+ .inspect(this::inspect)
+ .writeToZip();
+
+ Path finalApk = tempFolder.resolve("app-release-final.apk");
+ ProcessResult processResult = ApkUtils.apkMasseur(APK_PATH, output, finalApk);
+ // TODO(mkroghj): Figure out to have this command succeed when installing the apk
+ assertEquals(0, processResult.exitCode);
+ }
+
+ private void inspect(CodeInspector inspector) {
+ String name =
+ "com.example.softverificationsample."
+ + (isOutlined ? "ApiCallerOutlined" : "ApiCallerInlined")
+ + (numberOfClasses - 1);
+ ClassSubject clazz = inspector.clazz(name);
+ assertThat(clazz, isPresent());
+ if (isOutlined) {
+ ClassSubject apiCallerInlined =
+ inspector.clazz("com.example.softverificationsample.ApiCallerInlined");
+ assertThat(apiCallerInlined, isPresent());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/ApkUtils.java b/src/test/java/com/android/tools/r8/utils/ApkUtils.java
new file mode 100644
index 0000000..0fae220
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/utils/ApkUtils.java
@@ -0,0 +1,30 @@
+// Copyright (c) 2021, 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 com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class ApkUtils {
+
+ public static ProcessResult apkMasseur(Path apk, Path dexSources, Path out) throws IOException {
+ ImmutableList.Builder<String> command =
+ new ImmutableList.Builder<String>()
+ .add("tools/apk_masseur.py")
+ .add("--dex")
+ .add(dexSources.toString())
+ .add("--out")
+ .add(out.toString())
+ .add("--install")
+ .add(apk.toString());
+ ProcessBuilder builder = new ProcessBuilder(command.build());
+ builder.directory(Paths.get(ToolHelper.THIRD_PARTY_DIR).toAbsolutePath().getParent().toFile());
+ return ToolHelper.runProcess(builder);
+ }
+}
diff --git a/third_party/api-outlining/simple-app-dump.tar.gz.sha1 b/third_party/api-outlining/simple-app-dump.tar.gz.sha1
new file mode 100644
index 0000000..cf9cc96
--- /dev/null
+++ b/third_party/api-outlining/simple-app-dump.tar.gz.sha1
@@ -0,0 +1 @@
+9913f083a29519e3fdc626a769ad33c7ba2498c1
\ No newline at end of file