blob: f14a608de3f6d35de770898433f2785cdf32ae9a [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "artd.h"
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <chrono>
#include <condition_variable>
#include <csignal>
#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <type_traits>
#include <vector>
#include "aidl/com/android/server/art/BnArtd.h"
#include "android-base/errors.h"
#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/parseint.h"
#include "android-base/result.h"
#include "android-base/scopeguard.h"
#include "android-base/strings.h"
#include "android/binder_auto_utils.h"
#include "android/binder_status.h"
#include "base/common_art_test.h"
#include "exec_utils.h"
#include "fmt/format.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "path_utils.h"
#include "profman/profman_result.h"
#include "tools/system_properties.h"
namespace art {
namespace artd {
namespace {
using ::aidl::com::android::server::art::ArtifactsPath;
using ::aidl::com::android::server::art::DexMetadataPath;
using ::aidl::com::android::server::art::DexoptOptions;
using ::aidl::com::android::server::art::DexoptResult;
using ::aidl::com::android::server::art::FileVisibility;
using ::aidl::com::android::server::art::FsPermission;
using ::aidl::com::android::server::art::IArtdCancellationSignal;
using ::aidl::com::android::server::art::OutputArtifacts;
using ::aidl::com::android::server::art::OutputProfile;
using ::aidl::com::android::server::art::PriorityClass;
using ::aidl::com::android::server::art::ProfilePath;
using ::aidl::com::android::server::art::VdexPath;
using ::android::base::Error;
using ::android::base::make_scope_guard;
using ::android::base::ParseInt;
using ::android::base::ReadFdToString;
using ::android::base::ReadFileToString;
using ::android::base::Result;
using ::android::base::ScopeGuard;
using ::android::base::Split;
using ::android::base::WriteStringToFd;
using ::android::base::WriteStringToFile;
using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyNumber;
using ::testing::Contains;
using ::testing::ContainsRegex;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::Field;
using ::testing::HasSubstr;
using ::testing::IsEmpty;
using ::testing::Matcher;
using ::testing::MockFunction;
using ::testing::Not;
using ::testing::ResultOf;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::WithArg;
using PrimaryCurProfilePath = ProfilePath::PrimaryCurProfilePath;
using PrimaryRefProfilePath = ProfilePath::PrimaryRefProfilePath;
using TmpProfilePath = ProfilePath::TmpProfilePath;
using ::fmt::literals::operator""_format; // NOLINT
ScopeGuard<std::function<void()>> ScopedSetLogger(android::base::LogFunction&& logger) {
android::base::LogFunction old_logger = android::base::SetLogger(std::move(logger));
return make_scope_guard([old_logger = std::move(old_logger)]() mutable {
android::base::SetLogger(std::move(old_logger));
});
}
void CheckContent(const std::string& path, const std::string& expected_content) {
std::string actual_content;
ASSERT_TRUE(ReadFileToString(path, &actual_content));
EXPECT_EQ(actual_content, expected_content);
}
void WriteToFdFlagImpl(const std::vector<std::string>& args,
const std::string& flag,
const std::string& content,
bool assume_empty) {
for (const std::string& arg : args) {
std::string_view value(arg);
if (android::base::ConsumePrefix(&value, flag)) {
int fd;
ASSERT_TRUE(ParseInt(std::string(value), &fd));
if (assume_empty) {
ASSERT_EQ(lseek(fd, /*offset=*/0, SEEK_CUR), 0);
} else {
ASSERT_EQ(ftruncate(fd, /*length=*/0), 0);
ASSERT_EQ(lseek(fd, /*offset=*/0, SEEK_SET), 0);
}
ASSERT_TRUE(WriteStringToFd(content, fd));
return;
}
}
FAIL() << "Flag '{}' not found"_format(flag);
}
// Writes `content` to the FD specified by the `flag`.
ACTION_P(WriteToFdFlag, flag, content) {
WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/true);
}
// Clears any existing content and writes `content` to the FD specified by the `flag`.
ACTION_P(ClearAndWriteToFdFlag, flag, content) {
WriteToFdFlagImpl(arg0, flag, content, /*assume_empty=*/false);
}
// Matches a flag that starts with `flag` and whose value matches `matcher`.
MATCHER_P2(Flag, flag, matcher, "") {
std::string_view value(arg);
if (!android::base::ConsumePrefix(&value, flag)) {
return false;
}
return ExplainMatchResult(matcher, std::string(value), result_listener);
}
// Matches a flag that starts with `flag` and whose value is a colon-separated list that matches
// `matcher`. The matcher acts on an `std::vector<std::string>` of the split list argument.
MATCHER_P2(ListFlag, flag, matcher, "") {
return ExplainMatchResult(
Flag(flag, ResultOf(std::bind(Split, std::placeholders::_1, ":"), matcher)),
arg,
result_listener);
}
// Matches an FD of a file whose path matches `matcher`.
MATCHER_P(FdOf, matcher, "") {
int fd;
if (!ParseInt(arg, &fd)) {
return false;
}
std::string proc_path = "/proc/self/fd/{}"_format(fd);
char path[PATH_MAX];
ssize_t len = readlink(proc_path.c_str(), path, sizeof(path));
if (len < 0) {
return false;
}
return ExplainMatchResult(matcher, std::string(path, static_cast<size_t>(len)), result_listener);
}
// Matches an FD of a file whose content matches `matcher`.
MATCHER_P(FdHasContent, matcher, "") {
int fd;
if (!ParseInt(arg, &fd)) {
return false;
}
std::string actual_content;
if (!ReadFdToString(fd, &actual_content)) {
return false;
}
return ExplainMatchResult(matcher, actual_content, result_listener);
}
// Matches a container that, when split by `separator`, the first part matches `head_matcher`, and
// the second part matches `tail_matcher`.
MATCHER_P3(WhenSplitBy, separator, head_matcher, tail_matcher, "") {
using Value = const typename std::remove_reference<decltype(arg)>::type::value_type;
auto it = std::find(arg.begin(), arg.end(), separator);
if (it == arg.end()) {
return false;
}
size_t pos = it - arg.begin();
return ExplainMatchResult(head_matcher, ArrayRef<Value>(arg).SubArray(0, pos), result_listener) &&
ExplainMatchResult(tail_matcher, ArrayRef<Value>(arg).SubArray(pos + 1), result_listener);
}
class MockSystemProperties : public tools::SystemProperties {
public:
MOCK_METHOD(std::string, GetProperty, (const std::string& key), (const, override));
};
class MockExecUtils : public ExecUtils {
public:
// A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead
// to a conflict between gmock and android-base/logging.h (b/132668253).
ExecResult ExecAndReturnResult(const std::vector<std::string>& arg_vector,
int,
const ExecCallbacks& callbacks,
ProcessStat* stat,
std::string*) const override {
Result<int> code = DoExecAndReturnCode(arg_vector, callbacks, stat);
if (code.ok()) {
return {.status = ExecResult::kExited, .exit_code = code.value()};
}
return {.status = ExecResult::kUnknown};
}
MOCK_METHOD(Result<int>,
DoExecAndReturnCode,
(const std::vector<std::string>& arg_vector,
const ExecCallbacks& callbacks,
ProcessStat* stat),
(const));
};
class ArtdTest : public CommonArtTest {
protected:
void SetUp() override {
CommonArtTest::SetUp();
auto mock_props = std::make_unique<MockSystemProperties>();
mock_props_ = mock_props.get();
EXPECT_CALL(*mock_props_, GetProperty).Times(AnyNumber()).WillRepeatedly(Return(""));
auto mock_exec_utils = std::make_unique<MockExecUtils>();
mock_exec_utils_ = mock_exec_utils.get();
artd_ = ndk::SharedRefBase::make<Artd>(
std::move(mock_props), std::move(mock_exec_utils), mock_kill_.AsStdFunction());
scratch_dir_ = std::make_unique<ScratchDir>();
scratch_path_ = scratch_dir_->GetPath();
// Remove the trailing '/';
scratch_path_.resize(scratch_path_.length() - 1);
// Use an arbitrary existing directory as ART root.
art_root_ = scratch_path_ + "/com.android.art";
std::filesystem::create_directories(art_root_);
setenv("ANDROID_ART_ROOT", art_root_.c_str(), /*overwrite=*/1);
// Use an arbitrary existing directory as Android data.
android_data_ = scratch_path_ + "/data";
std::filesystem::create_directories(android_data_);
setenv("ANDROID_DATA", android_data_.c_str(), /*overwrite=*/1);
dex_file_ = scratch_path_ + "/a/b.apk";
isa_ = "arm64";
artifacts_path_ = ArtifactsPath{
.dexPath = dex_file_,
.isa = isa_,
.isInDalvikCache = false,
};
struct stat st;
ASSERT_EQ(stat(scratch_path_.c_str(), &st), 0);
output_artifacts_ = OutputArtifacts{
.artifactsPath = artifacts_path_,
.permissionSettings =
OutputArtifacts::PermissionSettings{
.dirFsPermission =
FsPermission{
.uid = static_cast<int32_t>(st.st_uid),
.gid = static_cast<int32_t>(st.st_gid),
.isOtherReadable = true,
.isOtherExecutable = true,
},
.fileFsPermission =
FsPermission{
.uid = static_cast<int32_t>(st.st_uid),
.gid = static_cast<int32_t>(st.st_gid),
.isOtherReadable = true,
},
},
};
clc_1_ = GetTestDexFileName("Main");
clc_2_ = GetTestDexFileName("Nested");
class_loader_context_ = "PCL[{}:{}]"_format(clc_1_, clc_2_);
compiler_filter_ = "speed";
TmpProfilePath tmp_profile_path{
.finalPath =
PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
.id = "12345"};
profile_path_ = tmp_profile_path;
vdex_path_ = artifacts_path_;
dm_path_ = DexMetadataPath{.dexPath = dex_file_};
std::filesystem::create_directories(
std::filesystem::path(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))).parent_path());
}
void TearDown() override {
scratch_dir_.reset();
CommonArtTest::TearDown();
}
void RunDexopt(binder_exception_t expected_status = EX_NONE,
Matcher<DexoptResult> aidl_return_matcher = Field(&DexoptResult::cancelled, false),
std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
InitFilesBeforeDexopt();
if (cancellation_signal == nullptr) {
ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
}
DexoptResult aidl_return;
ndk::ScopedAStatus status = artd_->dexopt(output_artifacts_,
dex_file_,
isa_,
class_loader_context_,
compiler_filter_,
profile_path_,
vdex_path_,
dm_path_,
priority_class_,
dexopt_options_,
cancellation_signal,
&aidl_return);
ASSERT_EQ(status.getExceptionCode(), expected_status) << status.getMessage();
if (status.isOk()) {
ASSERT_THAT(aidl_return, std::move(aidl_return_matcher));
}
}
void CreateFile(const std::string& filename, const std::string& content = "") {
std::filesystem::path path(filename);
std::filesystem::create_directories(path.parent_path());
ASSERT_TRUE(WriteStringToFile(content, filename));
}
std::shared_ptr<Artd> artd_;
std::unique_ptr<ScratchDir> scratch_dir_;
std::string scratch_path_;
std::string art_root_;
std::string android_data_;
MockFunction<android::base::LogFunction> mock_logger_;
ScopedUnsetEnvironmentVariable art_root_env_ = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
ScopedUnsetEnvironmentVariable android_data_env_ = ScopedUnsetEnvironmentVariable("ANDROID_DATA");
MockSystemProperties* mock_props_;
MockExecUtils* mock_exec_utils_;
MockFunction<int(pid_t, int)> mock_kill_;
std::string dex_file_;
std::string isa_;
ArtifactsPath artifacts_path_;
OutputArtifacts output_artifacts_;
std::string clc_1_;
std::string clc_2_;
std::optional<std::string> class_loader_context_;
std::string compiler_filter_;
std::optional<VdexPath> vdex_path_;
std::optional<DexMetadataPath> dm_path_;
PriorityClass priority_class_ = PriorityClass::BACKGROUND;
DexoptOptions dexopt_options_;
std::optional<ProfilePath> profile_path_;
private:
void InitFilesBeforeDexopt() {
// Required files.
CreateFile(dex_file_);
// Optional files.
if (vdex_path_.has_value()) {
CreateFile(OR_FATAL(BuildVdexPath(vdex_path_.value())), "old_vdex");
}
if (dm_path_.has_value()) {
CreateFile(OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
}
if (profile_path_.has_value()) {
CreateFile(OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
}
// Files to be replaced.
std::string oat_path = OR_FATAL(BuildOatPath(artifacts_path_));
CreateFile(oat_path, "old_oat");
CreateFile(OatPathToVdexPath(oat_path), "old_vdex");
CreateFile(OatPathToArtPath(oat_path), "old_art");
}
};
TEST_F(ArtdTest, isAlive) {
bool result = false;
artd_->isAlive(&result);
EXPECT_TRUE(result);
}
TEST_F(ArtdTest, deleteArtifacts) {
std::string oat_dir = scratch_path_ + "/a/oat/arm64";
std::filesystem::create_directories(oat_dir);
ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex")); // 4 bytes.
ASSERT_TRUE(WriteStringToFile("ab", oat_dir + "/b.vdex")); // 2 bytes.
ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art")); // 1 byte.
int64_t result = -1;
EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
EXPECT_EQ(result, 4 + 2 + 1);
EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.odex"));
EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.vdex"));
EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art"));
}
TEST_F(ArtdTest, deleteArtifactsMissingFile) {
// Missing VDEX file.
std::string oat_dir = android_data_ + "/dalvik-cache/arm64";
std::filesystem::create_directories(oat_dir);
ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/a@b.apk@classes.dex")); // 4 bytes.
ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/a@b.apk@classes.art")); // 1 byte.
auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0);
int64_t result = -1;
EXPECT_TRUE(artd_
->deleteArtifacts(
ArtifactsPath{
.dexPath = "/a/b.apk",
.isa = "arm64",
.isInDalvikCache = true,
},
&result)
.isOk());
EXPECT_EQ(result, 4 + 1);
EXPECT_FALSE(std::filesystem::exists(oat_dir + "/a@b.apk@classes.dex"));
EXPECT_FALSE(std::filesystem::exists(oat_dir + "/a@b.apk@classes.art"));
}
TEST_F(ArtdTest, deleteArtifactsNoFile) {
auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(0);
int64_t result = -1;
EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
EXPECT_EQ(result, 0);
}
TEST_F(ArtdTest, deleteArtifactsPermissionDenied) {
std::string oat_dir = scratch_path_ + "/a/oat/arm64";
std::filesystem::create_directories(oat_dir);
ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex")); // 4 bytes.
ASSERT_TRUE(WriteStringToFile("ab", oat_dir + "/b.vdex")); // 2 bytes.
ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art")); // 1 byte.
auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
EXPECT_CALL(mock_logger_, Call(_, _, _, _, _, HasSubstr("Failed to get the file size"))).Times(3);
auto scoped_inaccessible = ScopedInaccessible(oat_dir);
auto scoped_unroot = ScopedUnroot();
int64_t result = -1;
EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
EXPECT_EQ(result, 0);
}
TEST_F(ArtdTest, deleteArtifactsFileIsDir) {
// VDEX file is a directory.
std::string oat_dir = scratch_path_ + "/a/oat/arm64";
std::filesystem::create_directories(oat_dir);
std::filesystem::create_directories(oat_dir + "/b.vdex");
ASSERT_TRUE(WriteStringToFile("abcd", oat_dir + "/b.odex")); // 4 bytes.
ASSERT_TRUE(WriteStringToFile("a", oat_dir + "/b.art")); // 1 byte.
auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
EXPECT_CALL(mock_logger_,
Call(_, _, _, _, _, ContainsRegex(R"re(Failed to get the file size.*b\.vdex)re")))
.Times(1);
int64_t result = -1;
EXPECT_TRUE(artd_->deleteArtifacts(artifacts_path_, &result).isOk());
EXPECT_EQ(result, 4 + 1);
// The directory is kept because getting the file size failed.
EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.odex"));
EXPECT_TRUE(std::filesystem::exists(oat_dir + "/b.vdex"));
EXPECT_FALSE(std::filesystem::exists(oat_dir + "/b.art"));
}
TEST_F(ArtdTest, dexopt) {
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy(
"--",
AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
AllOf(
Contains(art_root_ + "/bin/dex2oat32"),
Contains(Flag("--zip-fd=", FdOf(dex_file_))),
Contains(Flag("--zip-location=", dex_file_)),
Contains(Flag("--oat-location=", scratch_path_ + "/a/oat/arm64/b.odex")),
Contains(Flag("--instruction-set=", "arm64")),
Contains(Flag("--compiler-filter=", "speed")),
Contains(Flag("--profile-file-fd=",
FdOf(android_data_ +
"/misc/profiles/ref/com.android.foo/primary.prof.12345.tmp"))),
Contains(Flag("--input-vdex-fd=", FdOf(scratch_path_ + "/a/oat/arm64/b.vdex"))),
Contains(Flag("--dm-fd=", FdOf(scratch_path_ + "/a/b.dm"))))),
_,
_))
.WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")),
SetArgPointee<2>(ProcessStat{.wall_time_ms = 100, .cpu_time_ms = 400}),
Return(0)));
RunDexopt(EX_NONE,
AllOf(Field(&DexoptResult::cancelled, false),
Field(&DexoptResult::wallTimeMs, 100),
Field(&DexoptResult::cpuTimeMs, 400),
Field(&DexoptResult::sizeBytes, strlen("oat") + strlen("vdex")),
Field(&DexoptResult::sizeBeforeBytes,
strlen("old_art") + strlen("old_oat") + strlen("old_vdex"))));
CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "oat");
CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "vdex");
}
TEST_F(ArtdTest, dexoptClassLoaderContext) {
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--",
_,
AllOf(Contains(ListFlag("--class-loader-context-fds=",
ElementsAre(FdOf(clc_1_), FdOf(clc_2_)))),
Contains(Flag("--class-loader-context=", class_loader_context_)),
Contains(Flag("--classpath-dir=", scratch_path_ + "/a")))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
TEST_F(ArtdTest, dexoptClassLoaderContextNull) {
class_loader_context_ = std::nullopt;
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(WhenSplitBy("--",
_,
AllOf(Not(Contains(Flag("--class-loader-context-fds=", _))),
Not(Contains(Flag("--class-loader-context=", _))),
Not(Contains(Flag("--classpath-dir=", _))))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
TEST_F(ArtdTest, dexoptNoOptionalInputFiles) {
profile_path_ = std::nullopt;
vdex_path_ = std::nullopt;
dm_path_ = std::nullopt;
EXPECT_CALL(*mock_exec_utils_,
DoExecAndReturnCode(WhenSplitBy("--",
_,
AllOf(Not(Contains(Flag("--profile-file-fd=", _))),
Not(Contains(Flag("--input-vdex-fd=", _))),
Not(Contains(Flag("--dm-fd=", _))))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
TEST_F(ArtdTest, dexoptPriorityClassBoot) {
priority_class_ = PriorityClass::BOOT;
EXPECT_CALL(*mock_exec_utils_,
DoExecAndReturnCode(WhenSplitBy("--",
AllOf(Not(Contains(Flag("--set-task-profile=", _))),
Not(Contains(Flag("--set-priority=", _)))),
Contains(Flag("--compact-dex-level=", "none"))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
TEST_F(ArtdTest, dexoptPriorityClassInteractive) {
priority_class_ = PriorityClass::INTERACTIVE;
EXPECT_CALL(*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--",
AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
Contains(Flag("--set-priority=", "background"))),
Contains(Flag("--compact-dex-level=", "none"))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
TEST_F(ArtdTest, dexoptPriorityClassInteractiveFast) {
priority_class_ = PriorityClass::INTERACTIVE_FAST;
EXPECT_CALL(*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--",
AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
Contains(Flag("--set-priority=", "background"))),
Contains(Flag("--compact-dex-level=", "none"))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
TEST_F(ArtdTest, dexoptPriorityClassBackground) {
priority_class_ = PriorityClass::BACKGROUND;
EXPECT_CALL(*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--",
AllOf(Contains(Flag("--set-task-profile=", "Dex2OatBootComplete")),
Contains(Flag("--set-priority=", "background"))),
Not(Contains(Flag("--compact-dex-level=", _)))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
TEST_F(ArtdTest, dexoptDexoptOptions) {
dexopt_options_ = DexoptOptions{
.compilationReason = "install",
.targetSdkVersion = 123,
.debuggable = false,
.generateAppImage = false,
.hiddenApiPolicyEnabled = false,
};
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(WhenSplitBy("--",
_,
AllOf(Contains(Flag("--compilation-reason=", "install")),
Contains(Flag("-Xtarget-sdk-version:", "123")),
Not(Contains("--debuggable")),
Not(Contains(Flag("--app-image-fd=", _))),
Not(Contains(Flag("-Xhidden-api-policy:", _))))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
TEST_F(ArtdTest, dexoptDexoptOptions2) {
dexopt_options_ = DexoptOptions{
.compilationReason = "bg-dexopt",
.targetSdkVersion = 456,
.debuggable = true,
.generateAppImage = true,
.hiddenApiPolicyEnabled = true,
};
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(WhenSplitBy("--",
_,
AllOf(Contains(Flag("--compilation-reason=", "bg-dexopt")),
Contains(Flag("-Xtarget-sdk-version:", "456")),
Contains("--debuggable"),
Contains(Flag("-Xhidden-api-policy:", "enabled")))),
_,
_))
.WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")), Return(0)));
RunDexopt();
CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "art");
}
TEST_F(ArtdTest, dexoptDefaultFlagsWhenNoSystemProps) {
dexopt_options_.generateAppImage = true;
EXPECT_CALL(*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--",
_,
AllOf(Contains(Flag("--swap-fd=", FdOf(_))),
Not(Contains(Flag("--instruction-set-features=", _))),
Not(Contains(Flag("--instruction-set-variant=", _))),
Not(Contains(Flag("--max-image-block-size=", _))),
Not(Contains(Flag("--very-large-app-threshold=", _))),
Not(Contains(Flag("--resolve-startup-const-strings=", _))),
Not(Contains("--generate-debug-info")),
Not(Contains("--generate-mini-debug-info")),
Contains("-Xdeny-art-apex-data-files"),
Not(Contains(Flag("--cpu-set=", _))),
Not(Contains(Flag("-j", _))),
Not(Contains(Flag("-Xms", _))),
Not(Contains(Flag("-Xmx", _))),
Not(Contains("--compile-individually")),
Not(Contains(Flag("--image-format=", _))))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
TEST_F(ArtdTest, dexoptFlagsFromSystemProps) {
dexopt_options_.generateAppImage = true;
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-swap")).WillOnce(Return("0"));
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.features"))
.WillOnce(Return("features"));
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.isa.arm64.variant")).WillOnce(Return("variant"));
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-max-image-block-size"))
.WillOnce(Return("size"));
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-very-large"))
.WillOnce(Return("threshold"));
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-resolve-startup-strings"))
.WillOnce(Return("strings"));
EXPECT_CALL(*mock_props_, GetProperty("debug.generate-debug-info")).WillOnce(Return("1"));
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-minidebuginfo")).WillOnce(Return("1"));
EXPECT_CALL(*mock_props_, GetProperty("odsign.verification.success")).WillOnce(Return("1"));
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xms")).WillOnce(Return("xms"));
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.dex2oat-Xmx")).WillOnce(Return("xmx"));
EXPECT_CALL(*mock_props_, GetProperty("ro.config.low_ram")).WillOnce(Return("1"));
EXPECT_CALL(*mock_props_, GetProperty("dalvik.vm.appimageformat")).WillOnce(Return("imgfmt"));
EXPECT_CALL(*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--",
_,
AllOf(Not(Contains(Flag("--swap-fd=", _))),
Contains(Flag("--instruction-set-features=", "features")),
Contains(Flag("--instruction-set-variant=", "variant")),
Contains(Flag("--max-image-block-size=", "size")),
Contains(Flag("--very-large-app-threshold=", "threshold")),
Contains(Flag("--resolve-startup-const-strings=", "strings")),
Contains("--generate-debug-info"),
Contains("--generate-mini-debug-info"),
Not(Contains("-Xdeny-art-apex-data-files")),
Contains(Flag("-Xms", "xms")),
Contains(Flag("-Xmx", "xmx")),
Contains("--compile-individually"),
Contains(Flag("--image-format=", "imgfmt")))),
_,
_))
.WillOnce(Return(0));
RunDexopt();
}
static void SetDefaultResourceControlProps(MockSystemProperties* mock_props) {
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2"));
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4"));
}
TEST_F(ArtdTest, dexoptDefaultResourceControlBoot) {
SetDefaultResourceControlProps(mock_props_);
// The default resource control properties don't apply to BOOT.
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy(
"--", _, AllOf(Not(Contains(Flag("--cpu-set=", _))), Contains(Not(Flag("-j", _))))),
_,
_))
.WillOnce(Return(0));
priority_class_ = PriorityClass::BOOT;
RunDexopt();
}
TEST_F(ArtdTest, dexoptDefaultResourceControlOther) {
SetDefaultResourceControlProps(mock_props_);
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy(
"--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))),
_,
_))
.Times(3)
.WillRepeatedly(Return(0));
priority_class_ = PriorityClass::INTERACTIVE_FAST;
RunDexopt();
priority_class_ = PriorityClass::INTERACTIVE;
RunDexopt();
priority_class_ = PriorityClass::BACKGROUND;
RunDexopt();
}
static void SetAllResourceControlProps(MockSystemProperties* mock_props) {
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-cpu-set")).WillRepeatedly(Return("0,2"));
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.dex2oat-threads")).WillRepeatedly(Return("4"));
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-cpu-set"))
.WillRepeatedly(Return("0,1,2,3"));
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.boot-dex2oat-threads"))
.WillRepeatedly(Return("8"));
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-cpu-set"))
.WillRepeatedly(Return("0,2,3"));
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.restore-dex2oat-threads"))
.WillRepeatedly(Return("6"));
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-cpu-set"))
.WillRepeatedly(Return("0"));
EXPECT_CALL(*mock_props, GetProperty("dalvik.vm.background-dex2oat-threads"))
.WillRepeatedly(Return("2"));
}
TEST_F(ArtdTest, dexoptAllResourceControlBoot) {
SetAllResourceControlProps(mock_props_);
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy(
"--", _, AllOf(Contains(Flag("--cpu-set=", "0,1,2,3")), Contains(Flag("-j", "8")))),
_,
_))
.WillOnce(Return(0));
priority_class_ = PriorityClass::BOOT;
RunDexopt();
}
TEST_F(ArtdTest, dexoptAllResourceControlInteractiveFast) {
SetAllResourceControlProps(mock_props_);
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy(
"--", _, AllOf(Contains(Flag("--cpu-set=", "0,2,3")), Contains(Flag("-j", "6")))),
_,
_))
.WillOnce(Return(0));
priority_class_ = PriorityClass::INTERACTIVE_FAST;
RunDexopt();
}
TEST_F(ArtdTest, dexoptAllResourceControlInteractive) {
SetAllResourceControlProps(mock_props_);
// INTERACTIVE always uses the default resource control properties.
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy(
"--", _, AllOf(Contains(Flag("--cpu-set=", "0,2")), Contains(Flag("-j", "4")))),
_,
_))
.WillOnce(Return(0));
priority_class_ = PriorityClass::INTERACTIVE;
RunDexopt();
}
TEST_F(ArtdTest, dexoptAllResourceControlBackground) {
SetAllResourceControlProps(mock_props_);
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--", _, AllOf(Contains(Flag("--cpu-set=", "0")), Contains(Flag("-j", "2")))),
_,
_))
.WillOnce(Return(0));
priority_class_ = PriorityClass::BACKGROUND;
RunDexopt();
}
TEST_F(ArtdTest, dexoptFailed) {
dexopt_options_.generateAppImage = true;
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
.WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
WithArg<0>(WriteToFdFlag("--app-image-fd=", "new_art")),
Return(1)));
RunDexopt(EX_SERVICE_SPECIFIC);
CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
}
TEST_F(ArtdTest, dexoptFailedToCommit) {
std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_inaccessible;
std::unique_ptr<ScopeGuard<std::function<void()>>> scoped_unroot;
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
.WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
[&](auto, auto, auto) {
scoped_inaccessible = std::make_unique<ScopeGuard<std::function<void()>>>(
ScopedInaccessible(scratch_path_ + "/a/oat/arm64"));
scoped_unroot =
std::make_unique<ScopeGuard<std::function<void()>>>(ScopedUnroot());
return 0;
}));
RunDexopt(EX_SERVICE_SPECIFIC,
AllOf(Field(&DexoptResult::sizeBytes, 0), Field(&DexoptResult::sizeBeforeBytes, 0)));
}
TEST_F(ArtdTest, dexoptCancelledBeforeDex2oat) {
std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
constexpr pid_t kPid = 123;
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
.WillOnce([&](auto, const ExecCallbacks& callbacks, auto) {
callbacks.on_start(kPid);
callbacks.on_end(kPid);
return Error();
});
EXPECT_CALL(mock_kill_, Call(kPid, SIGKILL));
cancellation_signal->cancel();
RunDexopt(EX_NONE, Field(&DexoptResult::cancelled, true), cancellation_signal);
CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
}
TEST_F(ArtdTest, dexoptCancelledDuringDex2oat) {
std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
constexpr pid_t kPid = 123;
constexpr std::chrono::duration<int> kTimeout = std::chrono::seconds(1);
std::condition_variable process_started_cv, process_killed_cv;
std::mutex mu;
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
.WillOnce([&](auto, const ExecCallbacks& callbacks, auto) {
std::unique_lock<std::mutex> lock(mu);
// Step 2.
callbacks.on_start(kPid);
process_started_cv.notify_one();
EXPECT_EQ(process_killed_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout);
// Step 5.
callbacks.on_end(kPid);
return Error();
});
EXPECT_CALL(mock_kill_, Call(kPid, SIGKILL)).WillOnce([&](auto, auto) {
// Step 4.
process_killed_cv.notify_one();
return 0;
});
std::thread t;
{
std::unique_lock<std::mutex> lock(mu);
// Step 1.
t = std::thread(
[&] { RunDexopt(EX_NONE, Field(&DexoptResult::cancelled, true), cancellation_signal); });
EXPECT_EQ(process_started_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout);
// Step 3.
cancellation_signal->cancel();
}
t.join();
// Step 6.
CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "old_oat");
CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "old_vdex");
CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "old_art");
}
TEST_F(ArtdTest, dexoptCancelledAfterDex2oat) {
std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
constexpr pid_t kPid = 123;
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
.WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "new_oat")),
WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "new_vdex")),
[&](auto, const ExecCallbacks& callbacks, auto) {
callbacks.on_start(kPid);
callbacks.on_end(kPid);
return 0;
}));
EXPECT_CALL(mock_kill_, Call).Times(0);
RunDexopt(EX_NONE, Field(&DexoptResult::cancelled, false), cancellation_signal);
// This signal should be ignored.
cancellation_signal->cancel();
CheckContent(scratch_path_ + "/a/oat/arm64/b.odex", "new_oat");
CheckContent(scratch_path_ + "/a/oat/arm64/b.vdex", "new_vdex");
EXPECT_FALSE(std::filesystem::exists(scratch_path_ + "/a/oat/arm64/b.art"));
}
TEST_F(ArtdTest, isProfileUsable) {
std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
CreateFile(profile_file);
CreateFile(dex_file_);
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--",
AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
AllOf(Contains(art_root_ + "/bin/profman"),
Contains(Flag("--reference-profile-file-fd=", FdOf(profile_file))),
Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
_,
_))
.WillOnce(Return(ProfmanResult::kSkipCompilationSmallDelta));
bool result;
EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
EXPECT_TRUE(result);
}
TEST_F(ArtdTest, isProfileUsableFalse) {
std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
CreateFile(profile_file);
CreateFile(dex_file_);
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
.WillOnce(Return(ProfmanResult::kSkipCompilationEmptyProfiles));
bool result;
EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
EXPECT_FALSE(result);
}
TEST_F(ArtdTest, isProfileUsableNotFound) {
CreateFile(dex_file_);
bool result;
EXPECT_TRUE(artd_->isProfileUsable(profile_path_.value(), dex_file_, &result).isOk());
EXPECT_FALSE(result);
}
TEST_F(ArtdTest, isProfileUsableFailed) {
std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
CreateFile(profile_file);
CreateFile(dex_file_);
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(100));
bool result;
ndk::ScopedAStatus status = artd_->isProfileUsable(profile_path_.value(), dex_file_, &result);
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100"));
}
TEST_F(ArtdTest, copyAndRewriteProfile) {
const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
CreateFile(src_file, "abc");
OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
dst.profilePath.id = "";
dst.profilePath.tmpPath = "";
CreateFile(dex_file_);
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--",
AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
AllOf(Contains(art_root_ + "/bin/profman"),
Contains("--copy-and-update-profile-key"),
Contains(Flag("--profile-file-fd=", FdOf(src_file))),
Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
_,
_))
.WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "def")),
Return(ProfmanResult::kCopyAndUpdateSuccess)));
bool result;
EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
EXPECT_TRUE(result);
EXPECT_THAT(dst.profilePath.id, Not(IsEmpty()));
std::string real_path = OR_FATAL(BuildTmpProfilePath(dst.profilePath));
EXPECT_EQ(dst.profilePath.tmpPath, real_path);
CheckContent(real_path, "def");
}
TEST_F(ArtdTest, copyAndRewriteProfileFalse) {
const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
CreateFile(src_file, "abc");
OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
dst.profilePath.id = "";
dst.profilePath.tmpPath = "";
CreateFile(dex_file_);
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _))
.WillOnce(Return(ProfmanResult::kCopyAndUpdateNoMatch));
bool result;
EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
EXPECT_FALSE(result);
EXPECT_THAT(dst.profilePath.id, IsEmpty());
EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
}
TEST_F(ArtdTest, copyAndRewriteProfileNotFound) {
CreateFile(dex_file_);
const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
dst.profilePath.id = "";
dst.profilePath.tmpPath = "";
bool result;
EXPECT_TRUE(artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result).isOk());
EXPECT_FALSE(result);
EXPECT_THAT(dst.profilePath.id, IsEmpty());
EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
}
TEST_F(ArtdTest, copyAndRewriteProfileFailed) {
const TmpProfilePath& src = profile_path_->get<ProfilePath::tmpProfilePath>();
std::string src_file = OR_FATAL(BuildTmpProfilePath(src));
CreateFile(src_file, "abc");
OutputProfile dst{.profilePath = src, .fsPermission = FsPermission{.uid = -1, .gid = -1}};
dst.profilePath.id = "";
dst.profilePath.tmpPath = "";
CreateFile(dex_file_);
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(100));
bool result;
ndk::ScopedAStatus status = artd_->copyAndRewriteProfile(src, &dst, dex_file_, &result);
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
EXPECT_THAT(status.getMessage(), HasSubstr("profman returned an unexpected code: 100"));
EXPECT_THAT(dst.profilePath.id, IsEmpty());
EXPECT_THAT(dst.profilePath.tmpPath, IsEmpty());
}
TEST_F(ArtdTest, commitTmpProfile) {
const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
std::string tmp_profile_file = OR_FATAL(BuildTmpProfilePath(tmp_profile_path));
CreateFile(tmp_profile_file);
EXPECT_TRUE(artd_->commitTmpProfile(tmp_profile_path).isOk());
EXPECT_FALSE(std::filesystem::exists(tmp_profile_file));
EXPECT_TRUE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
}
TEST_F(ArtdTest, commitTmpProfileFailed) {
const TmpProfilePath& tmp_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
ndk::ScopedAStatus status = artd_->commitTmpProfile(tmp_profile_path);
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
EXPECT_THAT(
status.getMessage(),
ContainsRegex(R"re(Failed to move .*primary\.prof\.12345\.tmp.* to .*primary\.prof)re"));
EXPECT_FALSE(std::filesystem::exists(OR_FATAL(BuildFinalProfilePath(tmp_profile_path))));
}
TEST_F(ArtdTest, deleteProfile) {
std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
CreateFile(profile_file);
EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
EXPECT_FALSE(std::filesystem::exists(profile_file));
}
TEST_F(ArtdTest, deleteProfileDoesNotExist) {
std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
}
TEST_F(ArtdTest, deleteProfileFailed) {
auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
EXPECT_CALL(
mock_logger_,
Call(_, _, _, _, _, ContainsRegex(R"re(Failed to remove .*primary\.prof\.12345\.tmp)re")));
EXPECT_TRUE(artd_->deleteProfile(profile_path_.value()).isOk());
}
class ArtdGetVisibilityTest : public ArtdTest {
protected:
template <typename PathType>
using Method = ndk::ScopedAStatus (Artd::*)(const PathType&, FileVisibility*);
template <typename PathType>
void TestGetVisibilityOtherReadable(Method<PathType> method,
const PathType& input,
const std::string& path) {
CreateFile(path);
std::filesystem::permissions(
path, std::filesystem::perms::others_read, std::filesystem::perm_options::add);
FileVisibility result;
ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
EXPECT_EQ(result, FileVisibility::OTHER_READABLE);
}
template <typename PathType>
void TestGetVisibilityNotOtherReadable(Method<PathType> method,
const PathType& input,
const std::string& path) {
CreateFile(path);
std::filesystem::permissions(
path, std::filesystem::perms::others_read, std::filesystem::perm_options::remove);
FileVisibility result;
ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
EXPECT_EQ(result, FileVisibility::NOT_OTHER_READABLE);
}
template <typename PathType>
void TestGetVisibilityNotFound(Method<PathType> method, const PathType& input) {
FileVisibility result;
ASSERT_TRUE(((*artd_).*method)(input, &result).isOk());
EXPECT_EQ(result, FileVisibility::NOT_FOUND);
}
template <typename PathType>
void TestGetVisibilityPermissionDenied(Method<PathType> method,
const PathType& input,
const std::string& path) {
CreateFile(path);
auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(path).parent_path());
auto scoped_unroot = ScopedUnroot();
FileVisibility result;
ndk::ScopedAStatus status = ((*artd_).*method)(input, &result);
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
EXPECT_THAT(status.getMessage(), HasSubstr("Failed to get status of"));
}
};
TEST_F(ArtdGetVisibilityTest, getProfileVisibilityOtherReadable) {
TestGetVisibilityOtherReadable(&Artd::getProfileVisibility,
profile_path_.value(),
OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
}
TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotOtherReadable) {
TestGetVisibilityNotOtherReadable(&Artd::getProfileVisibility,
profile_path_.value(),
OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
}
TEST_F(ArtdGetVisibilityTest, getProfileVisibilityNotFound) {
TestGetVisibilityNotFound(&Artd::getProfileVisibility, profile_path_.value());
}
TEST_F(ArtdGetVisibilityTest, getProfileVisibilityPermissionDenied) {
TestGetVisibilityPermissionDenied(&Artd::getProfileVisibility,
profile_path_.value(),
OR_FATAL(BuildProfileOrDmPath(profile_path_.value())));
}
TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityOtherReadable) {
TestGetVisibilityOtherReadable(
&Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
}
TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotOtherReadable) {
TestGetVisibilityNotOtherReadable(
&Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
}
TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityNotFound) {
TestGetVisibilityNotFound(&Artd::getArtifactsVisibility, artifacts_path_);
}
TEST_F(ArtdGetVisibilityTest, getArtifactsVisibilityPermissionDenied) {
TestGetVisibilityPermissionDenied(
&Artd::getArtifactsVisibility, artifacts_path_, OR_FATAL(BuildOatPath(artifacts_path_)));
}
TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityOtherReadable) {
TestGetVisibilityOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
}
TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotOtherReadable) {
TestGetVisibilityNotOtherReadable(&Artd::getDexFileVisibility, dex_file_, dex_file_);
}
TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityNotFound) {
TestGetVisibilityNotFound(&Artd::getDexFileVisibility, dex_file_);
}
TEST_F(ArtdGetVisibilityTest, getDexFileVisibilityPermissionDenied) {
TestGetVisibilityPermissionDenied(&Artd::getDexFileVisibility, dex_file_, dex_file_);
}
TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityOtherReadable) {
TestGetVisibilityOtherReadable(&Artd::getDmFileVisibility,
dm_path_.value(),
OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
}
TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotOtherReadable) {
TestGetVisibilityNotOtherReadable(&Artd::getDmFileVisibility,
dm_path_.value(),
OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
}
TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityNotFound) {
TestGetVisibilityNotFound(&Artd::getDmFileVisibility, dm_path_.value());
}
TEST_F(ArtdGetVisibilityTest, getDmFileVisibilityPermissionDenied) {
TestGetVisibilityPermissionDenied(&Artd::getDmFileVisibility,
dm_path_.value(),
OR_FATAL(BuildDexMetadataPath(dm_path_.value())));
}
TEST_F(ArtdTest, mergeProfiles) {
const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
CreateFile(reference_profile_file, "abc");
// Doesn't exist.
PrimaryCurProfilePath profile_0_path{
.userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
PrimaryCurProfilePath profile_1_path{
.userId = 1, .packageName = "com.android.foo", .profileName = "primary"};
std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path));
CreateFile(profile_1_file, "def");
OutputProfile output_profile{.profilePath = reference_profile_path,
.fsPermission = FsPermission{.uid = -1, .gid = -1}};
output_profile.profilePath.id = "";
output_profile.profilePath.tmpPath = "";
std::string dex_file_1 = scratch_path_ + "/a/b.apk";
std::string dex_file_2 = scratch_path_ + "/a/c.apk";
CreateFile(dex_file_1);
CreateFile(dex_file_2);
EXPECT_CALL(
*mock_exec_utils_,
DoExecAndReturnCode(
WhenSplitBy("--",
AllOf(Contains(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
AllOf(Contains(art_root_ + "/bin/profman"),
Not(Contains(Flag("--profile-file-fd=", FdOf(profile_0_file)))),
Contains(Flag("--profile-file-fd=", FdOf(profile_1_file))),
Contains(Flag("--reference-profile-file-fd=", FdHasContent("abc"))),
Contains(Flag("--apk-fd=", FdOf(dex_file_1))),
Contains(Flag("--apk-fd=", FdOf(dex_file_2))),
Not(Contains("--force-merge")),
Not(Contains("--boot-image-merge")))),
_,
_))
.WillOnce(DoAll(WithArg<0>(ClearAndWriteToFdFlag("--reference-profile-file-fd=", "merged")),
Return(ProfmanResult::kCompile)));
bool result;
EXPECT_TRUE(artd_
->mergeProfiles({profile_0_path, profile_1_path},
reference_profile_path,
&output_profile,
{dex_file_1, dex_file_2},
/*in_options=*/{},
&result)
.isOk());
EXPECT_TRUE(result);
EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
std::string real_path = OR_FATAL(BuildTmpProfilePath(output_profile.profilePath));
EXPECT_EQ(output_profile.profilePath.tmpPath, real_path);
CheckContent(real_path, "merged");
}
TEST_F(ArtdTest, mergeProfilesEmptyReferenceProfile) {
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(art_root_ + "/bin/art_exec"), Contains("--drop-capabilities")),
AllOf(Contains(art_root_ + "/bin/profman"),
Contains(Flag("--profile-file-fd=", FdOf(profile_0_file))),
Contains(Flag("--reference-profile-file-fd=", FdHasContent(""))),
Contains(Flag("--apk-fd=", FdOf(dex_file_))))),
_,
_))
.WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--reference-profile-file-fd=", "merged")),
Return(ProfmanResult::kCompile)));
bool result;
EXPECT_TRUE(artd_
->mergeProfiles({profile_0_path},
std::nullopt,
&output_profile,
{dex_file_},
/*in_options=*/{},
&result)
.isOk());
EXPECT_TRUE(result);
EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
EXPECT_THAT(output_profile.profilePath.tmpPath, Not(IsEmpty()));
}
TEST_F(ArtdTest, mergeProfilesProfilesDontExist) {
const TmpProfilePath& reference_profile_path = profile_path_->get<ProfilePath::tmpProfilePath>();
std::string reference_profile_file = OR_FATAL(BuildTmpProfilePath(reference_profile_path));
CreateFile(reference_profile_file, "abc");
// Doesn't exist.
PrimaryCurProfilePath profile_0_path{
.userId = 0, .packageName = "com.android.foo", .profileName = "primary"};
std::string profile_0_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_0_path));
// Doesn't exist.
PrimaryCurProfilePath profile_1_path{
.userId = 1, .packageName = "com.android.foo", .profileName = "primary"};
std::string profile_1_file = OR_FATAL(BuildPrimaryCurProfilePath(profile_1_path));
OutputProfile output_profile{.profilePath = reference_profile_path,
.fsPermission = FsPermission{.uid = -1, .gid = -1}};
output_profile.profilePath.id = "";
output_profile.profilePath.tmpPath = "";
CreateFile(dex_file_);
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode).Times(0);
bool result;
EXPECT_TRUE(artd_
->mergeProfiles({profile_0_path},
std::nullopt,
&output_profile,
{dex_file_},
/*in_options=*/{},
&result)
.isOk());
EXPECT_FALSE(result);
EXPECT_THAT(output_profile.profilePath.id, IsEmpty());
EXPECT_THAT(output_profile.profilePath.tmpPath, IsEmpty());
}
TEST_F(ArtdTest, mergeProfilesWithOptions) {
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("--force-merge"), Contains("--boot-image-merge"))),
_,
_))
.WillOnce(Return(ProfmanResult::kSuccess));
bool result;
EXPECT_TRUE(artd_
->mergeProfiles({profile_0_path},
std::nullopt,
&output_profile,
{dex_file_},
{.forceMerge = true, .forBootImage = true},
&result)
.isOk());
EXPECT_TRUE(result);
EXPECT_THAT(output_profile.profilePath.id, Not(IsEmpty()));
EXPECT_THAT(output_profile.profilePath.tmpPath, Not(IsEmpty()));
}
} // namespace
} // namespace artd
} // namespace art