/* * Copyright (C) 2021 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 "odrefresh.h" #include #include #include #include #include #include #include "android-base/file.h" #include "android-base/parseint.h" #include "android-base/scopeguard.h" #include "android-base/stringprintf.h" #include "android-base/strings.h" #include "arch/instruction_set.h" #include "base/common_art_test.h" #include "base/file_utils.h" #include "base/stl_util.h" #include "exec_utils.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "odr_artifacts.h" #include "odr_common.h" #include "odr_config.h" #include "odr_fs_utils.h" #include "odr_metrics.h" #include "odrefresh/odrefresh.h" namespace art { namespace odrefresh { using ::testing::_; using ::testing::AllOf; using ::testing::Contains; using ::testing::HasSubstr; using ::testing::Not; using ::testing::Return; constexpr int kReplace = 1; void CreateEmptyFile(const std::string& name) { File* file = OS::CreateEmptyFile(name.c_str()); ASSERT_TRUE(file != nullptr) << "Cannot create file " << name; file->Release(); delete file; } android::base::ScopeGuard> ScopedCreateEmptyFile(const std::string& name) { CreateEmptyFile(name); return android::base::ScopeGuard([=]() { unlink(name.c_str()); }); } 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). int ExecAndReturnCode(std::vector& arg_vector, time_t, bool*, std::string*) const override { return DoExecAndReturnCode(arg_vector); } MOCK_METHOD(int, DoExecAndReturnCode, (std::vector & arg_vector), (const)); }; // Matches a flag that starts with `flag` and is a colon-separated list that contains an element // that matches `matcher`. MATCHER_P2(FlagContains, flag, matcher, "") { std::string_view value = arg; if (!android::base::ConsumePrefix(&value, flag)) { return false; } for (std::string_view s : SplitString(value, ':')) { if (ExplainMatchResult(matcher, s, result_listener)) { return true; } } return false; } // Matches an FD of a file whose path matches `matcher`. MATCHER_P(FdOf, matcher, "") { char path[PATH_MAX]; int fd; if (!android::base::ParseInt(std::string{arg}, &fd)) { return false; } std::string proc_path = android::base::StringPrintf("/proc/self/fd/%d", fd); ssize_t len = readlink(proc_path.c_str(), path, sizeof(path)); if (len < 0) { return false; } std::string path_str{path, static_cast(len)}; return ExplainMatchResult(matcher, path_str, result_listener); } void WriteFakeApexInfoList(const std::string& filename) { std::string content = R"xml( )xml"; android::base::WriteStringToFile(content, filename); } class OdRefreshTest : public CommonArtTest { public: OdRefreshTest() : config_("odrefresh") {} protected: void SetUp() override { CommonArtTest::SetUp(); temp_dir_ = std::make_unique(); std::string_view temp_dir_path = temp_dir_->GetPath(); android::base::ConsumeSuffix(&temp_dir_path, "/"); std::string android_root_path = Concatenate({temp_dir_path, "/system"}); ASSERT_TRUE(EnsureDirectoryExists(android_root_path)); android_root_env_ = std::make_unique("ANDROID_ROOT"); setenv("ANDROID_ROOT", android_root_path.c_str(), kReplace); std::string android_art_root_path = Concatenate({temp_dir_path, "/apex/com.android.art"}); ASSERT_TRUE(EnsureDirectoryExists(android_art_root_path)); android_art_root_env_ = std::make_unique("ANDROID_ART_ROOT"); setenv("ANDROID_ART_ROOT", android_art_root_path.c_str(), kReplace); std::string art_apex_data_path = Concatenate({temp_dir_path, kArtApexDataDefaultPath}); ASSERT_TRUE(EnsureDirectoryExists(art_apex_data_path)); art_apex_data_env_ = std::make_unique("ART_APEX_DATA"); setenv("ART_APEX_DATA", art_apex_data_path.c_str(), kReplace); dalvik_cache_dir_ = art_apex_data_path + "/dalvik-cache"; ASSERT_TRUE(EnsureDirectoryExists(dalvik_cache_dir_ + "/x86_64")); std::string system_etc_dir = Concatenate({android_root_path, "/etc"}); ASSERT_TRUE(EnsureDirectoryExists(system_etc_dir)); framework_profile_ = system_etc_dir + "/boot-image.prof"; CreateEmptyFile(framework_profile_); std::string art_etc_dir = Concatenate({android_art_root_path, "/etc"}); ASSERT_TRUE(EnsureDirectoryExists(art_etc_dir)); art_profile_ = art_etc_dir + "/boot-image.prof"; CreateEmptyFile(art_profile_); framework_dir_ = android_root_path + "/framework"; framework_jar_ = framework_dir_ + "/framework.jar"; location_provider_jar_ = framework_dir_ + "/com.android.location.provider.jar"; services_jar_ = framework_dir_ + "/services.jar"; services_foo_jar_ = framework_dir_ + "/services-foo.jar"; services_bar_jar_ = framework_dir_ + "/services-bar.jar"; std::string services_jar_prof = framework_dir_ + "/services.jar.prof"; art_javalib_dir_ = android_art_root_path + "/javalib"; core_oj_jar_ = art_javalib_dir_ + "/core-oj.jar"; // Create placeholder files. ASSERT_TRUE(EnsureDirectoryExists(framework_dir_ + "/x86_64")); CreateEmptyFile(framework_jar_); CreateEmptyFile(location_provider_jar_); CreateEmptyFile(services_jar_); CreateEmptyFile(services_foo_jar_); CreateEmptyFile(services_bar_jar_); CreateEmptyFile(services_jar_prof); ASSERT_TRUE(EnsureDirectoryExists(art_javalib_dir_)); CreateEmptyFile(core_oj_jar_); std::string apex_info_filename = Concatenate({temp_dir_path, "/apex-info-list.xml"}); WriteFakeApexInfoList(apex_info_filename); config_.SetApexInfoListFile(apex_info_filename); config_.SetArtBinDir(Concatenate({temp_dir_path, "/bin"})); config_.SetBootClasspath(Concatenate({core_oj_jar_, ":", framework_jar_})); config_.SetDex2oatBootclasspath(Concatenate({core_oj_jar_, ":", framework_jar_})); config_.SetSystemServerClasspath(Concatenate({location_provider_jar_, ":", services_jar_})); config_.SetStandaloneSystemServerJars(Concatenate({services_foo_jar_, ":", services_bar_jar_})); config_.SetIsa(InstructionSet::kX86_64); config_.SetZygoteKind(ZygoteKind::kZygote64_32); config_.SetSystemServerCompilerFilter("speed"); // specify a default config_.SetArtifactDirectory(dalvik_cache_dir_); std::string staging_dir = dalvik_cache_dir_ + "/staging"; ASSERT_TRUE(EnsureDirectoryExists(staging_dir)); config_.SetStagingDir(staging_dir); auto mock_exec_utils = std::make_unique(); mock_exec_utils_ = mock_exec_utils.get(); metrics_ = std::make_unique(dalvik_cache_dir_); odrefresh_ = std::make_unique( config_, dalvik_cache_dir_ + "/cache-info.xml", std::move(mock_exec_utils)); } void TearDown() override { metrics_.reset(); temp_dir_.reset(); android_root_env_.reset(); android_art_root_env_.reset(); art_apex_data_env_.reset(); CommonArtTest::TearDown(); } std::unique_ptr temp_dir_; std::unique_ptr android_root_env_; std::unique_ptr android_art_root_env_; std::unique_ptr art_apex_data_env_; OdrConfig config_; MockExecUtils* mock_exec_utils_; std::unique_ptr odrefresh_; std::unique_ptr metrics_; std::string core_oj_jar_; std::string framework_jar_; std::string location_provider_jar_; std::string services_jar_; std::string services_foo_jar_; std::string services_bar_jar_; std::string dalvik_cache_dir_; std::string framework_dir_; std::string art_javalib_dir_; std::string framework_profile_; std::string art_profile_; }; TEST_F(OdRefreshTest, BootClasspathJars) { EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(AllOf( Contains(Concatenate({"--dex-file=", core_oj_jar_})), Contains(Concatenate({"--dex-file=", framework_jar_})), Contains(FlagContains("--dex-fd=", FdOf(core_oj_jar_))), Contains(FlagContains("--dex-fd=", FdOf(framework_jar_))), Contains(FlagContains("--profile-file-fd=", FdOf(art_profile_))), Contains(FlagContains("--profile-file-fd=", FdOf(framework_profile_))), Contains(Concatenate({"--oat-location=", dalvik_cache_dir_, "/x86_64/boot.oat"})), Contains(HasSubstr("--base=")), Contains("--compiler-filter=speed-profile")))) .WillOnce(Return(0)); EXPECT_EQ(odrefresh_->Compile(*metrics_, CompilationOptions{ .compile_boot_classpath_for_isas = {InstructionSet::kX86_64}, }), ExitCode::kCompilationSuccess); } TEST_F(OdRefreshTest, BootClasspathJarsFallback) { // Simulate the case where dex2oat fails when generating the full boot image. EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", core_oj_jar_})), Contains(Concatenate({"--dex-file=", framework_jar_}))))) .Times(2) .WillRepeatedly(Return(1)); // It should fall back to generating a minimal boot image. EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", core_oj_jar_})), Not(Contains(Concatenate({"--dex-file=", framework_jar_})))))) .Times(2) .WillOnce(Return(0)); EXPECT_EQ( odrefresh_->Compile( *metrics_, CompilationOptions{ .compile_boot_classpath_for_isas = {InstructionSet::kX86, InstructionSet::kX86_64}, .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(), }), ExitCode::kCompilationFailed); } TEST_F(OdRefreshTest, AllSystemServerJars) { EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(AllOf( Contains(Concatenate({"--dex-file=", location_provider_jar_})), Contains("--class-loader-context=PCL[]")))) .WillOnce(Return(0)); EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(AllOf( Contains(Concatenate({"--dex-file=", services_jar_})), Contains(Concatenate({"--class-loader-context=PCL[", location_provider_jar_, "]"}))))) .WillOnce(Return(0)); EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(AllOf( Contains(Concatenate({"--dex-file=", services_foo_jar_})), Contains(Concatenate({"--class-loader-context=PCL[];PCL[", location_provider_jar_, ":", services_jar_, "]"}))))) .WillOnce(Return(0)); EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(AllOf( Contains(Concatenate({"--dex-file=", services_bar_jar_})), Contains(Concatenate({"--class-loader-context=PCL[];PCL[", location_provider_jar_, ":", services_jar_, "]"}))))) .WillOnce(Return(0)); EXPECT_EQ( odrefresh_->Compile(*metrics_, CompilationOptions{ .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(), }), ExitCode::kCompilationSuccess); } TEST_F(OdRefreshTest, PartialSystemServerJars) { EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(AllOf( Contains(Concatenate({"--dex-file=", services_jar_})), Contains(Concatenate({"--class-loader-context=PCL[", location_provider_jar_, "]"}))))) .WillOnce(Return(0)); EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(AllOf( Contains(Concatenate({"--dex-file=", services_bar_jar_})), Contains(Concatenate({"--class-loader-context=PCL[];PCL[", location_provider_jar_, ":", services_jar_, "]"}))))) .WillOnce(Return(0)); EXPECT_EQ( odrefresh_->Compile(*metrics_, CompilationOptions{ .system_server_jars_to_compile = {services_jar_, services_bar_jar_}, }), ExitCode::kCompilationSuccess); } // Verifies that odrefresh can run properly when the STANDALONE_SYSTEM_SERVER_JARS variable is // missing, which is expected on Android S. TEST_F(OdRefreshTest, MissingStandaloneSystemServerJars) { config_.SetStandaloneSystemServerJars(""); EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(_)).WillRepeatedly(Return(0)); EXPECT_EQ( odrefresh_->Compile(*metrics_, CompilationOptions{ .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(), }), ExitCode::kCompilationSuccess); } TEST_F(OdRefreshTest, CompileSetsCompilerFilterToSpeed) { // Test setup: use "speed" compiler filter. config_.SetSystemServerCompilerFilter("speed"); // Uninteresting calls. EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(_)) .Times(odrefresh_->AllSystemServerJars().size() - 2) .WillRepeatedly(Return(0)); EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})), Not(Contains(HasSubstr("--profile-file-fd="))), Contains("--compiler-filter=speed")))) .WillOnce(Return(0)); EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})), Not(Contains(HasSubstr("--profile-file-fd="))), Contains("--compiler-filter=speed")))) .WillOnce(Return(0)); EXPECT_EQ( odrefresh_->Compile(*metrics_, CompilationOptions{ .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(), }), ExitCode::kCompilationSuccess); } TEST_F(OdRefreshTest, CompileSetsCompilerFilterToSpeedProfile) { // Test setup: with "speed-profile" compiler filter in the request, only apply if there is a // profile, otherwise fallback to speed. config_.SetSystemServerCompilerFilter("speed-profile"); // Uninteresting calls. EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(_)) .Times(odrefresh_->AllSystemServerJars().size() - 2) .WillRepeatedly(Return(0)); // services.jar has a profile, while location.provider.jar does not. EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})), Not(Contains(HasSubstr("--profile-file-fd="))), Contains("--compiler-filter=speed")))) .WillOnce(Return(0)); EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})), Contains(HasSubstr("--profile-file-fd=")), Contains("--compiler-filter=speed-profile")))) .WillOnce(Return(0)); EXPECT_EQ( odrefresh_->Compile(*metrics_, CompilationOptions{ .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(), }), ExitCode::kCompilationSuccess); } TEST_F(OdRefreshTest, CompileSetsCompilerFilterToVerify) { // Test setup: use "speed" compiler filter. config_.SetSystemServerCompilerFilter("verify"); // Uninteresting calls. EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(_)) .Times(odrefresh_->AllSystemServerJars().size() - 2) .WillRepeatedly(Return(0)); EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})), Not(Contains(HasSubstr("--profile-file-fd="))), Contains("--compiler-filter=verify")))) .WillOnce(Return(0)); EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})), Not(Contains(HasSubstr("--profile-file-fd="))), Contains("--compiler-filter=verify")))) .WillOnce(Return(0)); EXPECT_EQ( odrefresh_->Compile(*metrics_, CompilationOptions{ .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(), }), ExitCode::kCompilationSuccess); } TEST_F(OdRefreshTest, OutputFilesAndIsa) { EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains("--instruction-set=x86_64"), Contains(HasSubstr("--image-fd=")), Contains(HasSubstr("--output-vdex-fd=")), Contains(HasSubstr("--oat-fd="))))) .WillOnce(Return(0)); EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf(Contains("--instruction-set=x86_64"), Contains(HasSubstr("--app-image-fd=")), Contains(HasSubstr("--output-vdex-fd=")), Contains(HasSubstr("--oat-fd="))))) .Times(odrefresh_->AllSystemServerJars().size()) .WillRepeatedly(Return(0)); EXPECT_EQ( odrefresh_->Compile(*metrics_, CompilationOptions{ .compile_boot_classpath_for_isas = {InstructionSet::kX86_64}, .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(), }), ExitCode::kCompilationSuccess); } TEST_F(OdRefreshTest, CompileChoosesBootImage_OnData) { // Boot image is on /data. OdrArtifacts artifacts = OdrArtifacts::ForBootImage(dalvik_cache_dir_ + "/x86_64/boot.art"); auto file1 = ScopedCreateEmptyFile(artifacts.ImagePath()); auto file2 = ScopedCreateEmptyFile(artifacts.VdexPath()); auto file3 = ScopedCreateEmptyFile(artifacts.OatPath()); EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf( Contains(Concatenate({"--boot-image=", dalvik_cache_dir_, "/boot.art"})), Contains(FlagContains("-Xbootclasspathimagefds:", FdOf(artifacts.ImagePath()))), Contains(FlagContains("-Xbootclasspathvdexfds:", FdOf(artifacts.VdexPath()))), Contains(FlagContains("-Xbootclasspathoatfds:", FdOf(artifacts.OatPath())))))) .Times(odrefresh_->AllSystemServerJars().size()) .WillRepeatedly(Return(0)); EXPECT_EQ( odrefresh_->Compile(*metrics_, CompilationOptions{ .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(), }), ExitCode::kCompilationSuccess); } TEST_F(OdRefreshTest, CompileChoosesBootImage_OnSystem) { // Boot image is on /system. OdrArtifacts artifacts = OdrArtifacts::ForBootImage(framework_dir_ + "/x86_64/boot-framework.art"); auto file1 = ScopedCreateEmptyFile(artifacts.ImagePath()); auto file2 = ScopedCreateEmptyFile(artifacts.VdexPath()); auto file3 = ScopedCreateEmptyFile(artifacts.OatPath()); // Ignore the execution for compiling the boot classpath. EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(Contains(HasSubstr("--image-fd=")))) .WillRepeatedly(Return(0)); EXPECT_CALL( *mock_exec_utils_, DoExecAndReturnCode(AllOf( Contains(Concatenate({"--boot-image=", GetPrebuiltPrimaryBootImageDir(), "/boot.art:", framework_dir_, "/boot-framework.art"})), Contains(FlagContains("-Xbootclasspathimagefds:", FdOf(artifacts.ImagePath()))), Contains(FlagContains("-Xbootclasspathvdexfds:", FdOf(artifacts.VdexPath()))), Contains(FlagContains("-Xbootclasspathoatfds:", FdOf(artifacts.OatPath())))))) .Times(odrefresh_->AllSystemServerJars().size()) .WillRepeatedly(Return(0)); EXPECT_EQ( odrefresh_->Compile(*metrics_, CompilationOptions{ .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(), }), ExitCode::kCompilationSuccess); } } // namespace odrefresh } // namespace art