1962 lines
80 KiB
C++
1962 lines
80 KiB
C++
/*
|
|
* 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/stat.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 <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "aidl/com/android/server/art/ArtConstants.h"
|
|
#include "aidl/com/android/server/art/BnArtd.h"
|
|
#include "android-base/collections.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/array_ref.h"
|
|
#include "base/common_art_test.h"
|
|
#include "exec_utils.h"
|
|
#include "fmt/format.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include "oat_file.h"
|
|
#include "path_utils.h"
|
|
#include "profman/profman_result.h"
|
|
#include "testing.h"
|
|
#include "tools/system_properties.h"
|
|
|
|
namespace art {
|
|
namespace artd {
|
|
namespace {
|
|
|
|
using ::aidl::com::android::server::art::ArtConstants;
|
|
using ::aidl::com::android::server::art::ArtdDexoptResult;
|
|
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::FileVisibility;
|
|
using ::aidl::com::android::server::art::FsPermission;
|
|
using ::aidl::com::android::server::art::GetDexoptStatusResult;
|
|
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::Append;
|
|
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::AnyOf;
|
|
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::Property;
|
|
using ::testing::ResultOf;
|
|
using ::testing::Return;
|
|
using ::testing::SetArgPointee;
|
|
using ::testing::UnorderedElementsAreArray;
|
|
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 CheckOtherReadable(const std::string& path, bool expected_value) {
|
|
EXPECT_EQ((std::filesystem::status(path).permissions() & std::filesystem::perms::others_read) !=
|
|
std::filesystem::perms::none,
|
|
expected_value);
|
|
}
|
|
|
|
Result<std::vector<std::string>> GetFlagValues(ArrayRef<const std::string> args,
|
|
std::string_view flag) {
|
|
std::vector<std::string> values;
|
|
for (const std::string& arg : args) {
|
|
std::string_view value(arg);
|
|
if (android::base::ConsumePrefix(&value, flag)) {
|
|
values.emplace_back(value);
|
|
}
|
|
}
|
|
if (values.empty()) {
|
|
return Errorf("Flag '{}' not found", flag);
|
|
}
|
|
return values;
|
|
}
|
|
|
|
Result<std::string> GetFlagValue(ArrayRef<const std::string> args, std::string_view flag) {
|
|
std::vector<std::string> flag_values = OR_RETURN(GetFlagValues(args, flag));
|
|
if (flag_values.size() > 1) {
|
|
return Errorf("Duplicate flag '{}'", flag);
|
|
}
|
|
return flag_values[0];
|
|
}
|
|
|
|
void WriteToFdFlagImpl(const std::vector<std::string>& args,
|
|
std::string_view flag,
|
|
std::string_view content,
|
|
bool assume_empty) {
|
|
std::string value = OR_FAIL(GetFlagValue(ArrayRef<const std::string>(args), flag));
|
|
ASSERT_NE(value, "");
|
|
int fd;
|
|
ASSERT_TRUE(ParseInt(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));
|
|
}
|
|
|
|
// 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, "") {
|
|
std::string proc_path = "/proc/self/fd/{}"_format(arg);
|
|
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);
|
|
}
|
|
|
|
template <typename T, typename U>
|
|
Result<std::pair<ArrayRef<const T>, ArrayRef<const T>>> SplitBy(const std::vector<T>& list,
|
|
const U& separator) {
|
|
auto it = std::find(list.begin(), list.end(), separator);
|
|
if (it == list.end()) {
|
|
return Errorf("'{}' not found", separator);
|
|
}
|
|
size_t pos = it - list.begin();
|
|
return std::make_pair(ArrayRef<const T>(list).SubArray(0, pos),
|
|
ArrayRef<const T>(list).SubArray(pos + 1));
|
|
}
|
|
|
|
// 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, "") {
|
|
auto [head, tail] = OR_MISMATCH(SplitBy(arg, separator));
|
|
return ExplainMatchResult(head_matcher, head, result_listener) &&
|
|
ExplainMatchResult(tail_matcher, tail, result_listener);
|
|
}
|
|
|
|
MATCHER_P(HasKeepFdsForImpl, fd_flags, "") {
|
|
auto [head, tail] = OR_MISMATCH(SplitBy(arg, "--"));
|
|
std::string keep_fds_value = OR_MISMATCH(GetFlagValue(head, "--keep-fds="));
|
|
std::vector<std::string> keep_fds = Split(keep_fds_value, ":");
|
|
std::vector<std::string> fd_flag_values;
|
|
for (std::string_view fd_flag : fd_flags) {
|
|
for (const std::string& fd_flag_value : OR_MISMATCH(GetFlagValues(tail, fd_flag))) {
|
|
for (std::string& fd : Split(fd_flag_value, ":")) {
|
|
fd_flag_values.push_back(std::move(fd));
|
|
}
|
|
}
|
|
}
|
|
return ExplainMatchResult(UnorderedElementsAreArray(fd_flag_values), keep_fds, result_listener);
|
|
}
|
|
|
|
// Matches an argument list that has the "--keep-fds=" flag before "--", whose value is a
|
|
// semicolon-separated list that contains exactly the values of the given flags after "--".
|
|
//
|
|
// E.g., if the flags after "--" are "--foo=1", "--bar=2:3", "--baz=4", "--baz=5", and the matcher
|
|
// is `HasKeepFdsFor("--foo=", "--bar=", "--baz=")`, then it requires the "--keep-fds=" flag before
|
|
// "--" to contain exactly 1, 2, 3, 4, and 5.
|
|
template <typename... Args>
|
|
auto HasKeepFdsFor(Args&&... args) {
|
|
std::vector<std::string_view> fd_flags;
|
|
Append(fd_flags, std::forward<Args>(args)...);
|
|
return HasKeepFdsForImpl(fd_flags);
|
|
}
|
|
|
|
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(),
|
|
mock_fstat_.AsStdFunction());
|
|
scratch_dir_ = std::make_unique<ScratchDir>();
|
|
scratch_path_ = scratch_dir_->GetPath();
|
|
// Remove the trailing '/';
|
|
scratch_path_.resize(scratch_path_.length() - 1);
|
|
|
|
ON_CALL(mock_fstat_, Call).WillByDefault(fstat);
|
|
|
|
// 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);
|
|
|
|
// Use an arbitrary existing directory as Android expand.
|
|
android_expand_ = scratch_path_ + "/mnt/expand";
|
|
std::filesystem::create_directories(android_expand_);
|
|
setenv("ANDROID_EXPAND", android_expand_.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<ArtdDexoptResult> aidl_return_matcher = Field(&ArtdDexoptResult::cancelled,
|
|
false),
|
|
std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
|
|
RunDexopt(Property(&ndk::ScopedAStatus::getExceptionCode, expected_status),
|
|
std::move(aidl_return_matcher),
|
|
cancellation_signal);
|
|
}
|
|
|
|
void RunDexopt(Matcher<ndk::ScopedAStatus> status_matcher,
|
|
Matcher<ArtdDexoptResult> aidl_return_matcher = Field(&ArtdDexoptResult::cancelled,
|
|
false),
|
|
std::shared_ptr<IArtdCancellationSignal> cancellation_signal = nullptr) {
|
|
InitFilesBeforeDexopt();
|
|
if (cancellation_signal == nullptr) {
|
|
ASSERT_TRUE(artd_->createCancellationSignal(&cancellation_signal).isOk());
|
|
}
|
|
ArtdDexoptResult 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_THAT(status, std::move(status_matcher)) << 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_;
|
|
std::string android_expand_;
|
|
MockFunction<android::base::LogFunction> mock_logger_;
|
|
ScopedUnsetEnvironmentVariable art_root_env_ = ScopedUnsetEnvironmentVariable("ANDROID_ART_ROOT");
|
|
ScopedUnsetEnvironmentVariable android_data_env_ = ScopedUnsetEnvironmentVariable("ANDROID_DATA");
|
|
ScopedUnsetEnvironmentVariable android_expand_env_ =
|
|
ScopedUnsetEnvironmentVariable("ANDROID_EXPAND");
|
|
MockSystemProperties* mock_props_;
|
|
MockExecUtils* mock_exec_utils_;
|
|
MockFunction<int(pid_t, int)> mock_kill_;
|
|
MockFunction<int(int, struct stat*)> mock_fstat_;
|
|
|
|
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_;
|
|
bool dex_file_other_readable_ = true;
|
|
bool profile_other_readable_ = true;
|
|
|
|
private:
|
|
void InitFilesBeforeDexopt() {
|
|
// Required files.
|
|
CreateFile(dex_file_);
|
|
std::filesystem::permissions(dex_file_,
|
|
std::filesystem::perms::others_read,
|
|
dex_file_other_readable_ ? std::filesystem::perm_options::add :
|
|
std::filesystem::perm_options::remove);
|
|
|
|
// 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()) {
|
|
std::string path = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
|
|
CreateFile(path);
|
|
std::filesystem::permissions(path,
|
|
std::filesystem::perms::others_read,
|
|
profile_other_readable_ ? std::filesystem::perm_options::add :
|
|
std::filesystem::perm_options::remove);
|
|
}
|
|
|
|
// 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, ConstantsAreInSync) { EXPECT_EQ(ArtConstants::REASON_VDEX, kReasonVdex); }
|
|
|
|
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) {
|
|
dexopt_options_.generateAppImage = true;
|
|
|
|
EXPECT_CALL(
|
|
*mock_exec_utils_,
|
|
DoExecAndReturnCode(
|
|
AllOf(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"))))),
|
|
HasKeepFdsFor("--zip-fd=",
|
|
"--profile-file-fd=",
|
|
"--input-vdex-fd=",
|
|
"--dm-fd=",
|
|
"--oat-fd=",
|
|
"--output-vdex-fd=",
|
|
"--app-image-fd=",
|
|
"--class-loader-context-fds=",
|
|
"--swap-fd=")),
|
|
_,
|
|
_))
|
|
.WillOnce(DoAll(WithArg<0>(WriteToFdFlag("--oat-fd=", "oat")),
|
|
WithArg<0>(WriteToFdFlag("--output-vdex-fd=", "vdex")),
|
|
WithArg<0>(WriteToFdFlag("--app-image-fd=", "art")),
|
|
SetArgPointee<2>(ProcessStat{.wall_time_ms = 100, .cpu_time_ms = 400}),
|
|
Return(0)));
|
|
RunDexopt(
|
|
EX_NONE,
|
|
AllOf(Field(&ArtdDexoptResult::cancelled, false),
|
|
Field(&ArtdDexoptResult::wallTimeMs, 100),
|
|
Field(&ArtdDexoptResult::cpuTimeMs, 400),
|
|
Field(&ArtdDexoptResult::sizeBytes, strlen("art") + strlen("oat") + strlen("vdex")),
|
|
Field(&ArtdDexoptResult::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");
|
|
CheckContent(scratch_path_ + "/a/oat/arm64/b.art", "art");
|
|
CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.odex", true);
|
|
CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.vdex", true);
|
|
CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.art", true);
|
|
}
|
|
|
|
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=", "Dex2OatBackground")),
|
|
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,
|
|
.comments = "my-comments",
|
|
};
|
|
|
|
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:", _))),
|
|
Contains(Flag("--comments=", "my-comments")))),
|
|
_,
|
|
_))
|
|
.WillOnce(Return(0));
|
|
|
|
// `sizeBeforeBytes` should include the size of the old ART file even if no new ART file is
|
|
// generated.
|
|
RunDexopt(EX_NONE,
|
|
Field(&ArtdDexoptResult::sizeBeforeBytes,
|
|
strlen("old_art") + strlen("old_oat") + strlen("old_vdex")));
|
|
}
|
|
|
|
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("--app-image-fd=", _)),
|
|
Contains(Flag("-Xhidden-api-policy:", "enabled")))),
|
|
_,
|
|
_))
|
|
.WillOnce(Return(0));
|
|
|
|
RunDexopt();
|
|
}
|
|
|
|
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=", _))),
|
|
Not(Contains("--force-jit-zygote")),
|
|
Not(Contains(Flag("--boot-image=", _))))),
|
|
_,
|
|
_))
|
|
.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_props_, GetProperty("dalvik.vm.boot-image")).WillOnce(Return("boot-image"));
|
|
|
|
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")),
|
|
Not(Contains("--force-jit-zygote")),
|
|
Contains(Flag("--boot-image=", "boot-image")))),
|
|
_,
|
|
_))
|
|
.WillOnce(Return(0));
|
|
RunDexopt();
|
|
}
|
|
|
|
TEST_F(ArtdTest, dexoptFlagsForceJitZygote) {
|
|
EXPECT_CALL(*mock_props_,
|
|
GetProperty("persist.device_config.runtime_native_boot.profilebootclasspath"))
|
|
.WillOnce(Return("true"));
|
|
ON_CALL(*mock_props_, GetProperty("dalvik.vm.boot-image")).WillByDefault(Return("boot-image"));
|
|
|
|
EXPECT_CALL(*mock_exec_utils_,
|
|
DoExecAndReturnCode(WhenSplitBy("--",
|
|
_,
|
|
AllOf(Contains("--force-jit-zygote"),
|
|
Not(Contains(Flag("--boot-image=", _))))),
|
|
_,
|
|
_))
|
|
.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(&ArtdDexoptResult::sizeBytes, 0), Field(&ArtdDexoptResult::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(&ArtdDexoptResult::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(&ArtdDexoptResult::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(&ArtdDexoptResult::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, dexoptDexFileNotOtherReadable) {
|
|
dex_file_other_readable_ = false;
|
|
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
|
|
RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
|
|
Property(&ndk::ScopedAStatus::getMessage,
|
|
HasSubstr("Outputs cannot be other-readable because the dex file"))));
|
|
}
|
|
|
|
TEST_F(ArtdTest, dexoptProfileNotOtherReadable) {
|
|
profile_other_readable_ = false;
|
|
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
|
|
RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
|
|
Property(&ndk::ScopedAStatus::getMessage,
|
|
HasSubstr("Outputs cannot be other-readable because the profile"))));
|
|
}
|
|
|
|
TEST_F(ArtdTest, dexoptOutputNotOtherReadable) {
|
|
output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false;
|
|
dex_file_other_readable_ = false;
|
|
profile_other_readable_ = false;
|
|
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(0));
|
|
RunDexopt();
|
|
CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.odex", false);
|
|
CheckOtherReadable(scratch_path_ + "/a/oat/arm64/b.vdex", false);
|
|
}
|
|
|
|
TEST_F(ArtdTest, dexoptUidMismatch) {
|
|
output_artifacts_.permissionSettings.fileFsPermission.uid = 12345;
|
|
output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false;
|
|
dex_file_other_readable_ = false;
|
|
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
|
|
RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
|
|
Property(&ndk::ScopedAStatus::getMessage,
|
|
HasSubstr("Outputs' owner doesn't match the dex file"))));
|
|
}
|
|
|
|
TEST_F(ArtdTest, dexoptGidMismatch) {
|
|
output_artifacts_.permissionSettings.fileFsPermission.gid = 12345;
|
|
output_artifacts_.permissionSettings.fileFsPermission.isOtherReadable = false;
|
|
dex_file_other_readable_ = false;
|
|
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).Times(0);
|
|
RunDexopt(AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
|
|
Property(&ndk::ScopedAStatus::getMessage,
|
|
HasSubstr("Outputs' owner doesn't match the dex file"))));
|
|
}
|
|
|
|
TEST_F(ArtdTest, dexoptGidMatchesUid) {
|
|
output_artifacts_.permissionSettings.fileFsPermission = {
|
|
.uid = 123, .gid = 123, .isOtherReadable = false};
|
|
EXPECT_CALL(mock_fstat_, Call(_, _)).WillRepeatedly(fstat); // For profile.
|
|
EXPECT_CALL(mock_fstat_, Call(FdOf(dex_file_), _))
|
|
.WillOnce(DoAll(SetArgPointee<1>((struct stat){
|
|
.st_mode = S_IRUSR | S_IRGRP, .st_uid = 123, .st_gid = 456}),
|
|
Return(0)));
|
|
ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0));
|
|
// It's okay to fail on chown. This happens when the test is not run as root.
|
|
RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE),
|
|
AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
|
|
Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown")))));
|
|
}
|
|
|
|
TEST_F(ArtdTest, dexoptGidMatchesGid) {
|
|
output_artifacts_.permissionSettings.fileFsPermission = {
|
|
.uid = 123, .gid = 456, .isOtherReadable = false};
|
|
EXPECT_CALL(mock_fstat_, Call(_, _)).WillRepeatedly(fstat); // For profile.
|
|
EXPECT_CALL(mock_fstat_, Call(FdOf(dex_file_), _))
|
|
.WillOnce(DoAll(SetArgPointee<1>((struct stat){
|
|
.st_mode = S_IRUSR | S_IRGRP, .st_uid = 123, .st_gid = 456}),
|
|
Return(0)));
|
|
ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0));
|
|
// It's okay to fail on chown. This happens when the test is not run as root.
|
|
RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE),
|
|
AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
|
|
Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown")))));
|
|
}
|
|
|
|
TEST_F(ArtdTest, dexoptUidGidChangeOk) {
|
|
// The dex file is other-readable, so we don't check uid and gid.
|
|
output_artifacts_.permissionSettings.fileFsPermission = {
|
|
.uid = 12345, .gid = 12345, .isOtherReadable = false};
|
|
ON_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillByDefault(Return(0));
|
|
// It's okay to fail on chown. This happens when the test is not run as root.
|
|
RunDexopt(AnyOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_NONE),
|
|
AllOf(Property(&ndk::ScopedAStatus::getExceptionCode, EX_SERVICE_SPECIFIC),
|
|
Property(&ndk::ScopedAStatus::getMessage, HasSubstr("Failed to chown")))));
|
|
}
|
|
|
|
TEST_F(ArtdTest, dexoptNoUidGidChange) {
|
|
output_artifacts_.permissionSettings.fileFsPermission = {
|
|
.uid = -1, .gid = -1, .isOtherReadable = false};
|
|
dex_file_other_readable_ = false;
|
|
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_, _, _)).WillOnce(Return(0));
|
|
RunDexopt();
|
|
}
|
|
|
|
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(
|
|
AllOf(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_))))),
|
|
HasKeepFdsFor("--reference-profile-file-fd=", "--apk-fd=")),
|
|
_,
|
|
_))
|
|
.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(
|
|
AllOf(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_))))),
|
|
HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")),
|
|
_,
|
|
_))
|
|
.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) {
|
|
auto scoped_set_logger = ScopedSetLogger(mock_logger_.AsStdFunction());
|
|
EXPECT_CALL(mock_logger_, Call).Times(0);
|
|
|
|
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")));
|
|
|
|
std::string profile_file = OR_FATAL(BuildProfileOrDmPath(profile_path_.value()));
|
|
auto scoped_inaccessible = ScopedInaccessible(std::filesystem::path(profile_file).parent_path());
|
|
auto scoped_unroot = ScopedUnroot();
|
|
|
|
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(
|
|
AllOf(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")))),
|
|
HasKeepFdsFor("--profile-file-fd=", "--reference-profile-file-fd=", "--apk-fd=")),
|
|
_,
|
|
_))
|
|
.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, mergeProfilesWithOptionsForceMerge) {
|
|
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()));
|
|
}
|
|
|
|
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(
|
|
AllOf(WhenSplitBy("--",
|
|
_,
|
|
AllOf(Contains("--dump-only"),
|
|
Not(Contains(Flag("--reference-profile-file-fd=", _))))),
|
|
HasKeepFdsFor("--profile-file-fd=", "--apk-fd=", "--dump-output-to-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");
|
|
}
|
|
|
|
TEST_F(ArtdTest, cleanup) {
|
|
std::vector<std::string> gc_removed_files;
|
|
std::vector<std::string> gc_kept_files;
|
|
|
|
auto CreateGcRemovedFile = [&](const std::string& path) {
|
|
CreateFile(path);
|
|
gc_removed_files.push_back(path);
|
|
};
|
|
|
|
auto CreateGcKeptFile = [&](const std::string& path) {
|
|
CreateFile(path);
|
|
gc_kept_files.push_back(path);
|
|
};
|
|
|
|
// Unmanaged files.
|
|
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/1.odex");
|
|
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.odex");
|
|
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.txt");
|
|
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.txt");
|
|
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.tmp");
|
|
|
|
// Files to keep.
|
|
CreateGcKeptFile(android_data_ + "/misc/profiles/cur/1/com.android.foo/primary.prof");
|
|
CreateGcKeptFile(android_data_ + "/misc/profiles/cur/3/com.android.foo/primary.prof");
|
|
CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.dex");
|
|
CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.vdex");
|
|
CreateGcKeptFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.art");
|
|
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.vdex");
|
|
CreateGcKeptFile(
|
|
android_expand_ +
|
|
"/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.odex");
|
|
CreateGcKeptFile(
|
|
android_expand_ +
|
|
"/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.vdex");
|
|
CreateGcKeptFile(
|
|
android_expand_ +
|
|
"/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.art");
|
|
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.odex");
|
|
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.vdex");
|
|
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.art");
|
|
|
|
// Files to remove.
|
|
CreateGcRemovedFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof");
|
|
CreateGcRemovedFile(android_data_ + "/misc/profiles/cur/2/com.android.foo/primary.prof");
|
|
CreateGcRemovedFile(android_data_ + "/misc/profiles/cur/3/com.android.bar/primary.prof");
|
|
CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/extra.odex");
|
|
CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@Bar@Bar.apk@classes.dex");
|
|
CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@Bar@Bar.apk@classes.vdex");
|
|
CreateGcRemovedFile(android_data_ + "/dalvik-cache/arm64/system@app@Bar@Bar.apk@classes.art");
|
|
CreateGcRemovedFile(
|
|
android_expand_ +
|
|
"/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.odex");
|
|
CreateGcRemovedFile(
|
|
android_expand_ +
|
|
"/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.vdex");
|
|
CreateGcRemovedFile(
|
|
android_expand_ +
|
|
"/123456-7890/app/~~daewfweaf==/com.android.foo-fjuwidhia==/oat/arm64/base.art");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/1.prof");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/1.prof.123456.tmp");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.odex");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.vdex");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.art");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/1.odex.123456.tmp");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/oat/arm64/2.odex.123456.tmp");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.odex");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.art");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/1.vdex.123456.tmp");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.odex");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.vdex");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.art");
|
|
CreateGcRemovedFile(android_data_ +
|
|
"/user_de/0/com.android.foo/aaa/bbb/oat/arm64/1.art.123456.tmp");
|
|
CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.bar/aaa/oat/arm64/1.vdex");
|
|
|
|
int64_t aidl_return;
|
|
ASSERT_TRUE(
|
|
artd_
|
|
->cleanup(
|
|
{
|
|
PrimaryCurProfilePath{
|
|
.userId = 1, .packageName = "com.android.foo", .profileName = "primary"},
|
|
PrimaryCurProfilePath{
|
|
.userId = 3, .packageName = "com.android.foo", .profileName = "primary"},
|
|
},
|
|
{
|
|
ArtifactsPath{.dexPath = "/system/app/Foo/Foo.apk",
|
|
.isa = "arm64",
|
|
.isInDalvikCache = true},
|
|
ArtifactsPath{
|
|
.dexPath =
|
|
android_expand_ +
|
|
"/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/base.apk",
|
|
.isa = "arm64",
|
|
.isInDalvikCache = false},
|
|
ArtifactsPath{.dexPath = android_data_ + "/user_de/0/com.android.foo/aaa/2.apk",
|
|
.isa = "arm64",
|
|
.isInDalvikCache = false},
|
|
},
|
|
{
|
|
VdexPath{ArtifactsPath{
|
|
.dexPath = android_data_ + "/user_de/0/com.android.foo/aaa/1.apk",
|
|
.isa = "arm64",
|
|
.isInDalvikCache = false}},
|
|
},
|
|
&aidl_return)
|
|
.isOk());
|
|
|
|
for (const std::string& path : gc_removed_files) {
|
|
EXPECT_FALSE(std::filesystem::exists(path)) << "'{}' should be removed"_format(path);
|
|
}
|
|
|
|
for (const std::string& path : gc_kept_files) {
|
|
EXPECT_TRUE(std::filesystem::exists(path)) << "'{}' should be kept"_format(path);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace artd
|
|
} // namespace art
|