diff options
9 files changed, 280 insertions, 35 deletions
diff --git a/artd/artd.cc b/artd/artd.cc index 73208a67d1..2e55537dae 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -588,6 +588,9 @@ ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profil for (const std::string& dex_file : in_dexFiles) { OR_RETURN_FATAL(ValidateDexPath(dex_file)); } + if (in_options.forceMerge + in_options.dumpOnly + in_options.dumpClassesAndMethods > 1) { + return Fatal("Only one of 'forceMerge', 'dumpOnly', and 'dumpClassesAndMethods' can be set"); + } CmdlineBuilder args; FdLogger fd_logger; @@ -622,6 +625,11 @@ ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profil OR_RETURN_NON_FATAL(NewFile::Create(output_profile_path, in_outputProfile->fsPermission)); if (in_referenceProfile.has_value()) { + if (in_options.forceMerge || in_options.dumpOnly || in_options.dumpClassesAndMethods) { + return Fatal( + "Reference profile must not be set when 'forceMerge', 'dumpOnly', or " + "'dumpClassesAndMethods' is set"); + } std::string reference_profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(*in_referenceProfile)); if (in_referenceProfile->getTag() == ProfilePath::dexMetadataPath) { @@ -630,8 +638,12 @@ ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profil OR_RETURN_NON_FATAL(CopyFile(reference_profile_path, *output_profile_file)); } - // profman is ok with this being an empty file when in_referenceProfile isn't set. - args.Add("--reference-profile-file-fd=%d", output_profile_file->Fd()); + if (in_options.dumpOnly || in_options.dumpClassesAndMethods) { + args.Add("--dump-output-to-fd=%d", output_profile_file->Fd()); + } else { + // profman is ok with this being an empty file when in_referenceProfile isn't set. + args.Add("--reference-profile-file-fd=%d", output_profile_file->Fd()); + } fd_logger.Add(*output_profile_file); std::vector<std::unique_ptr<File>> dex_files; @@ -642,12 +654,16 @@ ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profil dex_files.push_back(std::move(dex_file)); } - args.AddIfNonEmpty("--min-new-classes-percent-change=%s", - props_->GetOrEmpty("dalvik.vm.bgdexopt.new-classes-percent")) - .AddIfNonEmpty("--min-new-methods-percent-change=%s", - props_->GetOrEmpty("dalvik.vm.bgdexopt.new-methods-percent")) - .AddIf(in_options.forceMerge, "--force-merge") - .AddIf(in_options.forBootImage, "--boot-image-merge"); + if (in_options.dumpOnly || in_options.dumpClassesAndMethods) { + args.Add(in_options.dumpOnly ? "--dump-only" : "--dump-classes-and-methods"); + } else { + args.AddIfNonEmpty("--min-new-classes-percent-change=%s", + props_->GetOrEmpty("dalvik.vm.bgdexopt.new-classes-percent")) + .AddIfNonEmpty("--min-new-methods-percent-change=%s", + props_->GetOrEmpty("dalvik.vm.bgdexopt.new-methods-percent")) + .AddIf(in_options.forceMerge, "--force-merge") + .AddIf(in_options.forBootImage, "--boot-image-merge"); + } LOG(INFO) << "Running profman: " << Join(args.Get(), /*separator=*/" ") << "\nOpened FDs: " << fd_logger; @@ -666,7 +682,9 @@ ndk::ScopedAStatus Artd::mergeProfiles(const std::vector<ProfilePath>& in_profil } ProfmanResult::ProcessingResult expected_result = - in_options.forceMerge ? ProfmanResult::kSuccess : ProfmanResult::kCompile; + (in_options.forceMerge || in_options.dumpOnly || in_options.dumpClassesAndMethods) ? + ProfmanResult::kSuccess : + ProfmanResult::kCompile; if (result.value() != expected_result) { return NonFatal("profman returned an unexpected code: {}"_format(result.value())); } diff --git a/artd/artd_test.cc b/artd/artd_test.cc index e28342b2da..8eab8add1f 100644 --- a/artd/artd_test.cc +++ b/artd/artd_test.cc @@ -1614,7 +1614,7 @@ TEST_F(ArtdTest, mergeProfilesProfilesDontExist) { EXPECT_THAT(output_profile.profilePath.tmpPath, IsEmpty()); } -TEST_F(ArtdTest, mergeProfilesWithOptions) { +TEST_F(ArtdTest, mergeProfilesWithOptionsForceMerge) { PrimaryCurProfilePath profile_0_path{ .userId = 0, .packageName = "com.android.foo", .profileName = "primary"}; std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path)); @@ -1649,6 +1649,82 @@ TEST_F(ArtdTest, mergeProfilesWithOptions) { EXPECT_THAT(output_profile.profilePath.tmpPath, Not(IsEmpty())); } +TEST_F(ArtdTest, mergeProfilesWithOptionsDumpOnly) { + PrimaryCurProfilePath profile_0_path{ + .userId = 0, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path)); + CreateFile(profile_0_file, "def"); + + OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(), + .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + output_profile.profilePath.id = ""; + output_profile.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + _, + AllOf(Contains("--dump-only"), + Not(Contains(Flag("--reference-profile-file-fd=", _))))), + _, + _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--dump-output-to-fd=", "dump")), + Return(ProfmanResult::kSuccess))); + + bool result; + EXPECT_TRUE(artd_ + ->mergeProfiles({profile_0_path}, + std::nullopt, + &output_profile, + {dex_file_}, + {.dumpOnly = true}, + &result) + .isOk()); + EXPECT_TRUE(result); + EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty())); + CheckContent(output_profile.profilePath.tmpPath, "dump"); +} + +TEST_F(ArtdTest, mergeProfilesWithOptionsDumpClassesAndMethods) { + PrimaryCurProfilePath profile_0_path{ + .userId = 0, .packageName = "com.android.foo", .profileName = "primary"}; + std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path)); + CreateFile(profile_0_file, "def"); + + OutputProfile output_profile{.profilePath = profile_path_->get<ProfilePath::tmpProfilePath>(), + .fsPermission = FsPermission{.uid = -1, .gid = -1}}; + output_profile.profilePath.id = ""; + output_profile.profilePath.tmpPath = ""; + + CreateFile(dex_file_); + + EXPECT_CALL(*mock_exec_utils_, + DoExecAndReturnCode( + WhenSplitBy("--", + _, + AllOf(Contains("--dump-classes-and-methods"), + Not(Contains(Flag("--reference-profile-file-fd=", _))))), + _, + _)) + .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--dump-output-to-fd=", "dump")), + Return(ProfmanResult::kSuccess))); + + bool result; + EXPECT_TRUE(artd_ + ->mergeProfiles({profile_0_path}, + std::nullopt, + &output_profile, + {dex_file_}, + {.dumpClassesAndMethods = true}, + &result) + .isOk()); + EXPECT_TRUE(result); + EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty())); + CheckContent(output_profile.profilePath.tmpPath, "dump"); +} + } // namespace } // namespace artd } // namespace art diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl index 237f8a9f04..4e456574f7 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -88,6 +88,11 @@ interface IArtd { * writes the merge result to `outputProfile` and fills `outputProfile.profilePath.id` and * `outputProfile.profilePath.tmpPath` if a merge has been performed. * + * When `options.forceMerge`, `options.dumpOnly`, or `options.dumpClassesAndMethods` is set, + * `referenceProfile` must not be set. I.e., all inputs must be provided by `profiles`. This is + * because the merge will always happen, and hence no reference profile is needed to calculate + * the diff. + * * Throws fatal and non-fatal errors. */ boolean mergeProfiles(in List<com.android.server.art.ProfilePath> profiles, diff --git a/artd/binder/com/android/server/art/MergeProfileOptions.aidl b/artd/binder/com/android/server/art/MergeProfileOptions.aidl index fb7db80ba9..2d007f9406 100644 --- a/artd/binder/com/android/server/art/MergeProfileOptions.aidl +++ b/artd/binder/com/android/server/art/MergeProfileOptions.aidl @@ -32,4 +32,8 @@ parcelable MergeProfileOptions { boolean forceMerge; /** --boot-image-merge */ boolean forBootImage; + /** --dump-only */ + boolean dumpOnly; + /** --dump-classes-and-methods */ + boolean dumpClassesAndMethods; } diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java index bf779891fd..61cfc16daa 100644 --- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java @@ -549,6 +549,32 @@ public final class ArtManagerLocal { public ParcelFileDescriptor snapshotAppProfile( @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, @Nullable String splitName) throws SnapshotProfileException { + var options = new MergeProfileOptions(); + options.forceMerge = true; + return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options); + } + + /** + * Same as above, but outputs in text format. + * + * @hide + */ + @NonNull + public ParcelFileDescriptor dumpAppProfile( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, + @Nullable String splitName, boolean dumpClassesAndMethods) + throws SnapshotProfileException { + var options = new MergeProfileOptions(); + options.dumpOnly = !dumpClassesAndMethods; + options.dumpClassesAndMethods = dumpClassesAndMethods; + return snapshotOrDumpAppProfile(snapshot, packageName, splitName, options); + } + + @NonNull + private ParcelFileDescriptor snapshotOrDumpAppProfile( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName, + @Nullable String splitName, @NonNull MergeProfileOptions options) + throws SnapshotProfileException { PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); PrimaryDexInfo dexInfo = PrimaryDexUtils.getDexInfoBySplitName(pkg, splitName); @@ -561,8 +587,7 @@ public final class ArtManagerLocal { OutputProfile output = PrimaryDexUtils.buildOutputProfile( pkgState, dexInfo, Process.SYSTEM_UID, Process.SYSTEM_UID, false /* isPublic */); - return mergeProfilesAndGetFd( - profiles, output, List.of(dexInfo.dexPath()), false /* forBootImage */); + return mergeProfilesAndGetFd(profiles, output, List.of(dexInfo.dexPath()), options); } /** @@ -619,7 +644,10 @@ public final class ArtManagerLocal { .flatMap(classpath -> Arrays.stream(classpath.split(":"))) .collect(Collectors.toList()); - return mergeProfilesAndGetFd(profiles, output, dexPaths, true /* forBootImage */); + var options = new MergeProfileOptions(); + options.forceMerge = true; + options.forBootImage = true; + return mergeProfilesAndGetFd(profiles, output, dexPaths, options); } /** @@ -733,13 +761,9 @@ public final class ArtManagerLocal { @NonNull private ParcelFileDescriptor mergeProfilesAndGetFd(@NonNull List<ProfilePath> profiles, - @NonNull OutputProfile output, @NonNull List<String> dexPaths, boolean forBootImage) - throws SnapshotProfileException { + @NonNull OutputProfile output, @NonNull List<String> dexPaths, + @NonNull MergeProfileOptions options) throws SnapshotProfileException { try { - var options = new MergeProfileOptions(); - options.forceMerge = true; - options.forBootImage = forBootImage; - boolean hasContent = false; try { hasContent = mInjector.getArtd().mergeProfiles( diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java index c282e8cf04..378412c5c5 100644 --- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java +++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java @@ -19,6 +19,7 @@ package com.android.server.art; import static android.os.ParcelFileDescriptor.AutoCloseInputStream; import static com.android.server.art.ArtManagerLocal.SnapshotProfileException; +import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo; import static com.android.server.art.model.ArtFlags.OptimizeFlags; import static com.android.server.art.model.OptimizationStatus.DexContainerFileOptimizationStatus; import static com.android.server.art.model.OptimizeResult.DexContainerFileOptimizeResult; @@ -31,6 +32,10 @@ import android.os.Build; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.os.Process; +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructStat; +import android.util.Log; import androidx.annotation.RequiresApi; @@ -43,6 +48,8 @@ import com.android.server.art.model.OptimizationStatus; import com.android.server.art.model.OptimizeParams; import com.android.server.art.model.OptimizeResult; import com.android.server.pm.PackageManagerLocal; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; import libcore.io.Streams; @@ -51,6 +58,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -66,6 +75,9 @@ import java.util.stream.Collectors; public final class ArtShellCommand extends BasicShellCommandHandler { private static final String TAG = "ArtShellCommand"; + /** The default location for profile dumps. */ + private final static String PROFILE_DEBUG_LOCATION = "/data/misc/profman"; + private final ArtManagerLocal mArtManagerLocal; private final PackageManagerLocal mPackageManagerLocal; private final DexUseManagerLocal mDexUseManager; @@ -270,26 +282,66 @@ public final class ArtShellCommand extends BasicShellCommandHandler { } } case "snapshot-app-profile": { - String outputPath = getNextArgRequired(); + String packageName = getNextArgRequired(); + String splitName = getNextArg(); + String outputRelativePath = String.format("%s%s.prof", packageName, + splitName != null ? String.format("-split_%s.apk", splitName) : ""); ParcelFileDescriptor fd; try { - fd = mArtManagerLocal.snapshotAppProfile( - snapshot, getNextArgRequired(), getNextOption()); + fd = mArtManagerLocal.snapshotAppProfile(snapshot, packageName, splitName); } catch (SnapshotProfileException e) { throw new RuntimeException(e); } - writeFdContentsToFile(fd, outputPath); + writeProfileFdContentsToFile(fd, outputRelativePath); return 0; } case "snapshot-boot-image-profile": { - String outputPath = getNextArgRequired(); + String outputRelativePath = "android.prof"; ParcelFileDescriptor fd; try { fd = mArtManagerLocal.snapshotBootImageProfile(snapshot); } catch (SnapshotProfileException e) { throw new RuntimeException(e); } - writeFdContentsToFile(fd, outputPath); + writeProfileFdContentsToFile(fd, outputRelativePath); + return 0; + } + case "dump-profiles": { + boolean dumpClassesAndMethods = false; + String opt; + while ((opt = getNextOption()) != null) { + switch (opt) { + case "--dump-classes-and-methods": { + dumpClassesAndMethods = true; + break; + } + default: + pw.println("Error: Unknown option: " + opt); + return 1; + } + } + String packageName = getNextArgRequired(); + PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) { + if (!dexInfo.hasCode()) { + continue; + } + String profileName = PrimaryDexUtils.getProfileName(dexInfo.splitName()); + // The path is intentionally inconsistent with the one for + // "snapshot-app-profile". The is to match the behavior of the legacy PM + // shell command. + String outputRelativePath = + String.format("%s-%s.prof.txt", packageName, profileName); + ParcelFileDescriptor fd; + try { + fd = mArtManagerLocal.dumpAppProfile(snapshot, packageName, + dexInfo.splitName(), dumpClassesAndMethods); + } catch (SnapshotProfileException e) { + throw new RuntimeException(e); + } + writeProfileFdContentsToFile(fd, outputRelativePath); + } return 0; } default: @@ -375,11 +427,22 @@ public final class ArtShellCommand extends BasicShellCommandHandler { pw.println(" This state will be lost when the system_server process exits."); pw.println(" --enable: Enable the background dexopt job to be started by the job"); pw.println(" scheduler again, if previously disabled by --disable."); - pw.println(" snapshot-app-profile OUTPUT_PATH PACKAGE_NAME [SPLIT_NAME]"); - pw.println(" Snapshot the profile of the given app and save it to the output path."); - pw.println(" If SPLIT_NAME is empty, the command snapshots the base APK."); - pw.println(" snapshot-boot-image-profile OUTPUT_PATH"); - pw.println(" Snapshot the boot image profile and save it to the output path."); + pw.println(" snapshot-app-profile PACKAGE_NAME [SPLIT_NAME]"); + pw.println(" Snapshot the profile of the given app and save it to"); + pw.println(" '" + PROFILE_DEBUG_LOCATION + "'."); + pw.println(" If SPLIT_NAME is empty, the command is for the base APK, and the output"); + pw.println(" filename is 'PACKAGE_NAME.prof'. Otherwise, the command is for the given"); + pw.println(" split, and the output filename is"); + pw.println(" 'PACKAGE_NAME-split_SPLIT_NAME.apk.prof'."); + pw.println(" snapshot-boot-image-profile"); + pw.println(" Snapshot the boot image profile and save it to"); + pw.println(" '" + PROFILE_DEBUG_LOCATION + "/android.prof'."); + pw.println(" dump-profiles [--dump-classes-and-methods] PACKAGE_NAME"); + pw.println(" Dump the profiles of the given app in text format and save the outputs to"); + pw.println(" '" + PROFILE_DEBUG_LOCATION + "'."); + pw.println(" The profile of the base APK is dumped to 'PACKAGE_NAME-primary.prof.txt'"); + pw.println(" The profile of a split APK is dumped to"); + pw.println(" 'PACKAGE_NAME-SPLIT_NAME.split.prof.txt'"); } private void enforceRoot() { @@ -423,12 +486,30 @@ public final class ArtShellCommand extends BasicShellCommandHandler { } } - private void writeFdContentsToFile( - @NonNull ParcelFileDescriptor fd, @NonNull String outputPath) { + private void writeProfileFdContentsToFile( + @NonNull ParcelFileDescriptor fd, @NonNull String outputRelativePath) { + try { + StructStat st = Os.stat(PROFILE_DEBUG_LOCATION); + if (st.st_uid != Process.SYSTEM_UID || st.st_gid != Process.SHELL_UID + || (st.st_mode & 0007) != 0) { + throw new RuntimeException( + String.format("%s has wrong permissions: uid=%d, gid=%d, mode=%o", + PROFILE_DEBUG_LOCATION, st.st_uid, st.st_gid, st.st_mode)); + } + } catch (ErrnoException e) { + throw new RuntimeException("Unable to stat " + PROFILE_DEBUG_LOCATION, e); + } + Path outputPath = Paths.get(PROFILE_DEBUG_LOCATION, outputRelativePath); try (InputStream inputStream = new AutoCloseInputStream(fd); - OutputStream outputStream = new FileOutputStream(outputPath)) { + FileOutputStream outputStream = new FileOutputStream(outputPath.toFile())) { + // The system server doesn't have the permission to chown the file to "shell", so we + // make it readable by everyone and put it in a directory that is only accessible by + // "shell", which is created by system/core/rootdir/init.rc. The permissions are + // verified by the code above. + Os.fchmod(outputStream.getFD(), 0644); Streams.copy(inputStream, outputStream); - } catch (IOException e) { + } catch (IOException | ErrnoException e) { + Utils.deleteIfExistsSafe(outputPath); throw new RuntimeException(e); } } diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java index 5af9f0af25..ada6488ab7 100644 --- a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java +++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java @@ -324,7 +324,7 @@ public class PrimaryDexUtils { } @NonNull - private static String getProfileName(@Nullable String splitName) { + public static String getProfileName(@Nullable String splitName) { return splitName == null ? "primary" : splitName + ".split"; } diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java index 64a33ab8c5..f104f9893e 100644 --- a/libartservice/service/java/com/android/server/art/Utils.java +++ b/libartservice/service/java/com/android/server/art/Utils.java @@ -23,6 +23,7 @@ import android.os.ServiceManager; import android.os.SystemProperties; import android.os.UserManager; import android.text.TextUtils; +import android.util.Log; import android.util.SparseArray; import com.android.server.art.model.OptimizeParams; @@ -35,6 +36,9 @@ import dalvik.system.VMRuntime; import com.google.auto.value.AutoValue; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -47,6 +51,7 @@ import java.util.stream.Collectors; /** @hide */ public final class Utils { + public static final String TAG = "ArtServiceUtils"; public static final String PLATFORM_PACKAGE_NAME = "android"; private Utils() {} @@ -284,6 +289,14 @@ public final class Utils { return Math.max(lastUsedAtMs, lastFirstInstallTimeMs); } + public static void deleteIfExistsSafe(@NonNull Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + Log.e(TAG, "Failed to delete file '" + path + "'", e); + } + } + @AutoValue public abstract static class Abi { static @NonNull Abi create( diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java index 44b04983bf..f23644ec18 100644 --- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java +++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java @@ -641,6 +641,30 @@ public class ArtManagerLocalTest { } @Test + public void testDumpAppProfile() throws Exception { + var options = new MergeProfileOptions(); + options.dumpOnly = true; + + when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options))) + .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`. + + ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile( + mSnapshot, PKG_NAME, null /* splitName */, false /* dumpClassesAndMethods */); + } + + @Test + public void testDumpAppProfileDumpClassesAndMethods() throws Exception { + var options = new MergeProfileOptions(); + options.dumpClassesAndMethods = true; + + when(mArtd.mergeProfiles(any(), isNull(), any(), any(), deepEq(options))) + .thenReturn(false); // A non-empty merge is tested in `testSnapshotAppProfile`. + + ParcelFileDescriptor fd = mArtManagerLocal.dumpAppProfile( + mSnapshot, PKG_NAME, null /* splitName */, true /* dumpClassesAndMethods */); + } + + @Test public void testSnapshotBootImageProfile() throws Exception { // `lenient()` is required to allow mocking the same method multiple times. lenient().when(Constants.getenv("BOOTCLASSPATH")).thenReturn("bcp0:bcp1"); |