Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  oat_file_assistant_test.cc

  Sprache: C
 

/*
 * Copyright (C) 2014 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 "oat_file_assistant.h"

#include <fcntl.h>
#include <gtest/gtest.h>
#include <sys/param.h>

#include <functional>
#include <iterator>
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <vector>

#include "android-base/scopeguard.h"
#include "android-base/strings.h"
#include "arch/instruction_set.h"
#include "art_field-inl.h"
#include "base/file_utils.h"
#include "base/os.h"
#include "base/utils.h"
#include "class_linker.h"
#include "class_loader_context.h"
#include "common_runtime_test.h"
#include "dexopt_test.h"
#include "oat.h"
#include "oat_file.h"
#include "oat_file_assistant_context.h"
#include "oat_file_manager.h"
#include "obj_ptr.h"
#include "scoped_thread_state_change.h"
#include "thread.h"

namespace art HIDDEN {

class OatFileAssistantBaseTest : public DexoptTest {};

class OatFileAssistantTest : public OatFileAssistantBaseTest,
                             public ::testing::WithParamInterface<bool> {
 public:
  void SetUp() override {
    DexoptTest::SetUp();
    with_runtime_ = GetParam();
    ofa_context_ = CreateOatFileAssistantContext();
  }

  // Verifies all variants of `GetOptimizationStatus`.
  //
  // `expected_filter` can be either a value of `CompilerFilter::Filter` or a string.
  // If `check_context` is true, only verifies the variants that checks class loader context.
  template <typename T>
  void VerifyOptimizationStatus(const std::string& file,
                                ClassLoaderContext* context,
                                const T& expected_filter,
                                const std::string& expected_reason,
                                const std::string& expected_odex_status,
                                OatFileAssistant::Location expected_location,
                                bool expected_is_backed_by_vdex_only,
                                bool check_context = false) {
    std::string expected_filter_name;
    if constexpr (std::is_same_v<T, CompilerFilter::Filter>) {
      expected_filter_name = CompilerFilter::NameOfFilter(expected_filter);
    } else {
      expected_filter_name = expected_filter;
    }

    // Verify the static method (called from PM for dumpsys).
    // This variant does not check class loader context.
    if (!check_context) {
      std::string compilation_filter;
      std::string compilation_reason;

      OatFileAssistant::GetOptimizationStatus(file,
                                              kRuntimeISA,
                                              &compilation_filter,
                                              &compilation_reason,
                                              MaybeGetOatFileAssistantContext());

      EXPECT_EQ(expected_filter_name, compilation_filter);
      EXPECT_EQ(expected_reason, compilation_reason);
    }

    // Verify the instance methods (called at runtime and from artd).
    OatFileAssistant assistant = CreateOatFileAssistant(file.c_str(), context);
    VerifyOptimizationStatusWithInstance(&assistant,
                                         expected_filter_name,
                                         expected_reason,
                                         expected_odex_status,
                                         expected_location,
                                         expected_is_backed_by_vdex_only);
  }

  void VerifyOptimizationStatusWithInstance(OatFileAssistant* assistant,
                                            const std::string& expected_filter,
                                            const std::string& expected_reason,
                                            const std::string& expected_odex_status,
                                            OatFileAssistant::Location expected_location,
                                            bool expected_is_backed_by_vdex_only) {
    std::string odex_location;  // ignored
    std::string compilation_filter;
    std::string compilation_reason;
    std::string odex_status;
    OatFileAssistant::Location location;
    bool is_backed_by_vdex_only;

    assistant->GetOptimizationStatus(&odex_location,
                                     &compilation_filter,
                                     &compilation_reason,
                                     &odex_status,
                                     &location,
                                     &is_backed_by_vdex_only);

    EXPECT_EQ(expected_filter, compilation_filter);
    EXPECT_EQ(expected_reason, compilation_reason);
    EXPECT_EQ(expected_odex_status, odex_status);
    EXPECT_EQ(expected_location, location);
    EXPECT_EQ(expected_is_backed_by_vdex_only, is_backed_by_vdex_only);
  }

  bool InsertNewBootClasspathEntry(const std::string& src, std::string* error_msg) {
    std::vector<std::unique_ptr<const DexFile>> dex_files;
    ArtDexFileLoader dex_file_loader(src);
    if (!dex_file_loader.Open(/*verify=*/true,
                              /*verify_checksum=*/false,
                              error_msg,
                              &dex_files)) {
      return false;
    }

    runtime_->AppendToBootClassPath(src, src, dex_files);
    std::move(dex_files.begin(), dex_files.end(), std::back_inserter(opened_dex_files_));

    return true;
  }

  // Verifies the current version of `GetDexOptNeeded` (called from artd).
  void VerifyGetDexOptNeeded(OatFileAssistant* assistant,
                             CompilerFilter::Filter compiler_filter,
                             const OatFileAssistant::DexoptTrigger& dexopt_trigger,
                             bool expected_dexopt_needed,
                             bool expected_is_vdex_usable,
                             OatFileAssistant::Location expected_location) {
    OatFileAssistant::DexOptStatus status;
    EXPECT_EQ(assistant->GetDexOptNeeded(compiler_filter, dexopt_trigger, &status),
              expected_dexopt_needed);
    EXPECT_EQ(status.IsVdexUsable(), expected_is_vdex_usable);
    EXPECT_EQ(status.GetLocation(), expected_location);
  }

  // Verifies all versions of `GetDexOptNeeded` with the default dexopt trigger.
  void VerifyGetDexOptNeededDefault(OatFileAssistant* assistant,
                                    CompilerFilter::Filter compiler_filter,
                                    bool expected_dexopt_needed,
                                    bool expected_is_vdex_usable,
                                    OatFileAssistant::Location expected_location,
                                    int expected_legacy_result) {
    // Verify the current version (called from artd).
    VerifyGetDexOptNeeded(assistant,
                          compiler_filter,
                          default_trigger_,
                          expected_dexopt_needed,
                          expected_is_vdex_usable,
                          expected_location);

    // Verify the legacy version (called from PM).
    EXPECT_EQ(
        assistant->GetDexOptNeeded(compiler_filter, /*profile_changed=*/false, /*downgrade=*/false),
        expected_legacy_result);
  }

  static std::unique_ptr<ClassLoaderContext> InitializeDefaultContext() {
    auto context = ClassLoaderContext::Default();
    context->OpenDexFiles();
    return context;
  }

  // Temporarily disables the pointer to the current runtime if `with_runtime_` is false.
  // Essentially simulates an environment where there is no active runtime.
  android::base::ScopeGuard<std::function<void()>> ScopedMaybeWithoutRuntime() {
    if (!with_runtime_) {
      Runtime::TestOnlySetCurrent(nullptr);
    }
    return android::base::make_scope_guard(
        [this]() { Runtime::TestOnlySetCurrent(runtime_.get()); });
  }

  std::unique_ptr<OatFileAssistantContext> CreateOatFileAssistantContext() {
    return std::make_unique<OatFileAssistantContext>(
        std::make_unique<OatFileAssistantContext::RuntimeOptions>(
            OatFileAssistantContext::RuntimeOptions{
                .image_locations = runtime_->GetImageLocations(),
                .boot_class_path = runtime_->GetBootClassPath(),
                .boot_class_path_locations = runtime_->GetBootClassPathLocations(),
                .boot_class_path_files = !runtime_->GetBootClassPathFiles().empty()
                                             ? runtime_->GetBootClassPathFiles()
                                             : std::optional<ArrayRef<File>>(),
                .deny_art_apex_data_files = runtime_->DenyArtApexDataFiles(),
                .sdk_version = runtime_->GetSdkVersion(),
            }));
  }

  OatFileAssistantContext* MaybeGetOatFileAssistantContext() {
    return with_runtime_ ? nullptr : ofa_context_.get();
  }

  // A helper function to create OatFileAssistant with some default arguments.
  OatFileAssistant CreateOatFileAssistant(const char* dex_location,
                                          ClassLoaderContext* context = nullptr,
                                          bool load_executable = false,
                                          int vdex_fd = -1,
                                          int oat_fd = -1,
                                          int zip_fd = -1) {
    return OatFileAssistant(dex_location,
                            kRuntimeISA,
                            context != nullptr ? context : default_context_.get(),
                            load_executable,
                            /*only_load_trusted_executable=*/false,
                            MaybeGetOatFileAssistantContext(),
                            vdex_fd,
                            oat_fd,
                            zip_fd);
  }

  void ExpectHasDexFiles(OatFileAssistant* oat_file_assistant, bool expected_value) {
    std::string error_msg;
    std::optional<bool> has_dex_files = oat_file_assistant->HasDexFiles(&error_msg);
    ASSERT_TRUE(has_dex_files.has_value()) << error_msg;
    EXPECT_EQ(*has_dex_files, expected_value);
  }

  void SetRuntimeSdkVersion(uint32_t sdk_version) { runtime_->SetSdkVersion(sdk_version); }

  std::unique_ptr<ClassLoaderContext> default_context_ = InitializeDefaultContext();
  bool with_runtime_;
  const OatFileAssistant::DexoptTrigger default_trigger_{
      .dexopt_comparators = {OatFileAssistant::DexoptComparator::kComparingCompilerFilter,
                             OatFileAssistant::DexoptComparator::kComparingPrimaryBootImageStatus,
                             OatFileAssistant::DexoptComparator::kComparingExtractionStatus},
      .custom_comparator_reason = std::nullopt};
  std::unique_ptr<OatFileAssistantContext> ofa_context_;
  std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
};

// Case: We have a MultiDEX file and up-to-date ODEX file for it with relative
// encoded dex locations.
// Expect: The oat file status is kNoDexOptNeeded.
TEST_P(OatFileAssistantTest, RelativeEncodedDexLocation) {
  std::string dex_location = GetScratchDir() + "/RelativeEncodedDexLocation.jar";
  std::string odex_location = GetOdexDir() + "/RelativeEncodedDexLocation.odex";

  // Create the dex file
  Copy(GetMultiDexSrc1(), dex_location);

  // Create the oat file with relative encoded dex location.
  std::vector<std::string> args = {
      "--dex-file=" + dex_location,
      "--dex-location=" + std::string("RelativeEncodedDexLocation.jar"),
      "--oat-file=" + odex_location,
      "--compiler-filter=speed",
  };

  std::string error_msg;
  ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Verify we can load both dex files.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/true);

  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_TRUE(oat_file.get() != nullptr);
  if (with_runtime_) {
    EXPECT_TRUE(oat_file->IsExecutable());
  }
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
  EXPECT_EQ(2u, dex_files.size());
}

TEST_P(OatFileAssistantTest, MakeUpToDateWithContext) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  std::string context_location = GetScratchDir() + "/ContextDex.jar";
  Copy(GetDexSrc1(), dex_location);
  Copy(GetDexSrc2(), context_location);

  std::string context_str = "PCL[" + context_location + "]";
  std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str);
  ASSERT_TRUE(context != nullptr);
  ASSERT_TRUE(context->OpenDexFiles());

  std::string error_msg;
  std::vector<std::string> args;
  args.push_back("--dex-file=" + dex_location);
  args.push_back("--oat-file=" + odex_location);
  args.push_back("--class-loader-context=" + context_str);
  ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(), context.get());

  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_NE(nullptr, oat_file.get());
  ASSERT_NE(nullptr, oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
  EXPECT_EQ(context->EncodeContextForOatFile(""),
            oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
}

TEST_P(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  std::string context_location = GetScratchDir() + "/ContextDex.jar";
  Copy(GetDexSrc1(), dex_location);
  Copy(GetDexSrc2(), context_location);

  // A relative context simulates a dependent split context.
  std::unique_ptr<ClassLoaderContext> relative_context =
      ClassLoaderContext::Create("PCL[ContextDex.jar]");
  ASSERT_TRUE(relative_context != nullptr);
  std::vector<int> context_fds;
  ASSERT_TRUE(relative_context->OpenDexFiles(GetScratchDir(), context_fds));

  std::string error_msg;
  std::vector<std::string> args;
  args.push_back("--dex-file=" + dex_location);
  args.push_back("--oat-file=" + odex_location);
  args.push_back("--class-loader-context=PCL[" + context_location + "]");
  ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant =
      CreateOatFileAssistant(dex_location.c_str(), relative_context.get());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kDefaultCompilerFilter,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
}

// Case: We have a DEX file, but no OAT file for it.
// Expect: The status is kDex2OatNeeded.
TEST_P(OatFileAssistantTest, DexNoOat) {
  std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
  Copy(GetDexSrc1(), dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeedProfile,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);

  VerifyOptimizationStatus(dex_location,
                           default_context_.get(),
                           "run-from-apk",
                           "unknown",
                           "io-error-no-oat",
                           OatFileAssistant::kLocationNoneOrError,
                           /*is_backed_by_vdex_only=*/false);
}

// Case: We have no DEX file and no OAT file.
// Expect: Status is kNoDexOptNeeded. Loading should fail, but not crash.
TEST_P(OatFileAssistantTest, NoDexNoOat) {
  std::string dex_location = GetScratchDir() + "/NoDexNoOat.jar";

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  std::string error_msg_ignored;
  EXPECT_FALSE(oat_file_assistant.HasDexFiles(&error_msg_ignored).has_value());

  // Trying to get the best oat file should fail, but not crash.
  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  EXPECT_EQ(nullptr, oat_file.get());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "unknown",
                                       "unknown",
                                       "io-error-no-apk",
                                       OatFileAssistant::kLocationNoneOrError,
                                       /*is_backed_by_vdex_only=*/false);
}

// Case: We have a DEX file and an ODEX file, but no OAT file.
// Expect: The status is kNoDexOptNeeded.
TEST_P(OatFileAssistantTest, OdexUpToDate) {
  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Force the use of oat location by making the dex parent not writable.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kEverything,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);

  VerifyOptimizationStatus(dex_location,
                           default_context_.get(),
                           CompilerFilter::kSpeed,
                           "install",
                           "up-to-date",
                           OatFileAssistant::kLocationOdex,
                           /*is_backed_by_vdex_only=*/false);
}

// Case: We have an ODEX file compiled against partial boot image.
// Expect: The status is kNoDexOptNeeded.
TEST_P(OatFileAssistantTest, OdexUpToDatePartialBootImage) {
  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");

  // Insert an extra dex file to the boot class path.
  std::string error_msg;
  ASSERT_TRUE(InsertNewBootClasspathEntry(GetMultiDexSrc1(), &error_msg)) << error_msg;

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Force the use of oat location by making the dex parent not writable.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kEverything,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);

  VerifyOptimizationStatus(dex_location,
                           default_context_.get(),
                           CompilerFilter::kSpeed,
                           "install",
                           "up-to-date",
                           OatFileAssistant::kLocationOdex,
                           /*is_backed_by_vdex_only=*/false);
}

// Case: We have a DEX file and a PIC ODEX file, but no OAT file. We load the dex
// file via a symlink.
// Expect: The status is kNoDexOptNeeded.
TEST_P(OatFileAssistantTest, OdexUpToDateSymLink) {
  std::string scratch_dir = GetScratchDir();
  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";

  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);

  // Now replace the dex location with a symlink.
  std::string link = scratch_dir + "/link";
  ASSERT_EQ(0, symlink(scratch_dir.c_str(), link.c_str()));
  dex_location = link + "/OdexUpToDate.jar";

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kEverything,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);
}

// Case: We have a DEX file and up-to-date OAT file for it.
// Expect: The status is kNoDexOptNeeded.
TEST_P(OatFileAssistantTest, OatUpToDate) {
  std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOat,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOat,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kEverything,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOat,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);

  VerifyOptimizationStatus(dex_location,
                           default_context_.get(),
                           CompilerFilter::kSpeed,
                           "unknown",
                           "up-to-date",
                           OatFileAssistant::kLocationOat,
                           /*is_backed_by_vdex_only=*/false);
}

// Case: Passing valid file descriptors of updated odex/vdex files along with the dex file.
// Expect: The status is kNoDexOptNeeded.
TEST_P(OatFileAssistantTest, GetDexOptNeededWithFd) {
  std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
  std::string odex_location = GetScratchDir() + "/OatUpToDate.odex";
  std::string vdex_location = GetScratchDir() + "/OatUpToDate.vdex";

  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location,
                     odex_location,
                     CompilerFilter::kSpeed,
                     /* with_alternate_image= */ false);

  android::base::unique_fd odex_fd(open(odex_location.c_str(), O_RDONLY | O_CLOEXEC));
  android::base::unique_fd vdex_fd(open(vdex_location.c_str(), O_RDONLY | O_CLOEXEC));
  android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/false,
                                                               vdex_fd.get(),
                                                               odex_fd.get(),
                                                               zip_fd.get());
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kEverything,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);
}

// Case: Passing invalid odex fd and valid vdex and zip fds.
// Expect: The status should be kDex2OatForBootImage.
TEST_P(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexFd) {
  std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
  std::string odex_location = GetScratchDir() + "/OatUpToDate.odex";
  std::string vdex_location = GetScratchDir() + "/OatUpToDate.vdex";

  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location,
                     odex_location,
                     CompilerFilter::kSpeed,
                     /* with_alternate_image= */ false);

  android::base::unique_fd vdex_fd(open(vdex_location.c_str(), O_RDONLY | O_CLOEXEC));
  android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/false,
                                                               vdex_fd.get(),
                                                               /*oat_fd=*/-1,
                                                               zip_fd.get());
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kEverything,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);
}

// Case: Passing invalid vdex fd and valid odex and zip fds.
// Expect: The status should be kDex2OatFromScratch.
TEST_P(OatFileAssistantTest, GetDexOptNeededWithInvalidVdexFd) {
  std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
  std::string odex_location = GetScratchDir() + "/OatUpToDate.odex";

  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location,
                     odex_location,
                     CompilerFilter::kSpeed,
                     /* with_alternate_image= */ false);

  android::base::unique_fd odex_fd(open(odex_location.c_str(), O_RDONLY | O_CLOEXEC));
  android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/false,
                                                               /*vdex_fd=*/-1,
                                                               odex_fd.get(),
                                                               zip_fd.get());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);
}

// Case: Passing invalid vdex and odex fd with valid zip fd.
// Expect: The status is kDex2oatFromScratch.
TEST_P(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexVdexFd) {
  std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";

  Copy(GetDexSrc1(), dex_location);

  android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/false,
                                                               /*vdex_fd=*/-1,
                                                               /*oat_fd=*/-1,
                                                               zip_fd);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
}

// Case: We have a DEX file and up-to-date VDEX file for it, but no ODEX file, and the DEX file is
// compressed.
TEST_P(OatFileAssistantTest, VdexUpToDateNoOdex) {
  std::string dex_location = GetScratchDir() + "/VdexUpToDateNoOdex.jar";
  std::string odex_location = GetOdexDir() + "/VdexUpToDateNoOdex.oat";

  Copy(GetDexSrc1(), dex_location);

  // Generating and deleting the oat file should have the side effect of
  // creating an up-to-date vdex file.
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
  ASSERT_EQ(0, unlink(odex_location.c_str()));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);

  VerifyOptimizationStatus(dex_location,
                           default_context_.get(),
                           "verify",
                           "vdex",
                           "up-to-date",
                           OatFileAssistant::kLocationOdex,
                           /*is_backed_by_vdex_only=*/true);
}

// Case: We have a DEX file and empty VDEX and ODEX files.
TEST_P(OatFileAssistantTest, EmptyVdexOdex) {
  std::string dex_location = GetScratchDir() + "/EmptyVdexOdex.jar";
  std::string odex_location = GetOdexDir() + "/EmptyVdexOdex.oat";
  std::string vdex_location = GetOdexDir() + "/EmptyVdexOdex.vdex";

  Copy(GetDexSrc1(), dex_location);
  ScratchFile vdex_file(vdex_location);
  ScratchFile odex_file(odex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
}

// Case: We have a DEX file and up-to-date (OAT) VDEX file for it, but no OAT
// file.
TEST_P(OatFileAssistantTest, VdexUpToDateNoOat) {
  std::string dex_location = GetScratchDir() + "/VdexUpToDateNoOat.jar";
  std::string oat_location;
  std::string error_msg;
  ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
      dex_location, kRuntimeISA, /*deny_art_apex_data_files=*/false, &oat_location, &error_msg))
      << error_msg;

  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
  ASSERT_EQ(0, unlink(oat_location.c_str()));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOat,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);
}

// Case: We have a DEX file and speed-profile OAT file for it.
// Expect: The status is kNoDexOptNeeded if the profile hasn't changed, but
// kDex2Oat if the profile has changed.
TEST_P(OatFileAssistantTest, ProfileOatUpToDate) {
  std::string dex_location = GetScratchDir() + "/ProfileOatUpToDate.jar";
  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeedProfile);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOat);
  EXPECT_EQ(
      OatFileAssistant::kNoDexOptNeeded,
      oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile, /*profile_changed=*/false));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOat);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify, /*profile_changed=*/false));

  OatFileAssistant::DexoptTrigger profile_changed_trigger = {
      .dexopt_comparators = {OatFileAssistant::DexoptComparator::kComparingCompilerFilter,
                             OatFileAssistant::DexoptComparator::kCustomTargetIsBetterThanCurrent},
      .custom_comparator_reason = "profile changed"};

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        profile_changed_trigger,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOat);
  EXPECT_EQ(
      OatFileAssistant::kDex2OatForFilter,
      oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile, /*profile_changed=*/true));

  // We should not recompile even if `profile_changed` is true because the compiler filter should
  // not be downgraded.
  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        profile_changed_trigger,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOat);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify, /*profile_changed=*/true));

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);
}

// Case: We have a MultiDEX file and up-to-date OAT file for it.
// Expect: The status is kNoDexOptNeeded and we load all dex files.
TEST_P(OatFileAssistantTest, MultiDexOatUpToDate) {
  std::string dex_location = GetScratchDir() + "/MultiDexOatUpToDate.jar";
  Copy(GetMultiDexSrc1(), dex_location);
  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/true);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOat,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  ExpectHasDexFiles(&oat_file_assistant, true);

  // Verify we can load both dex files.
  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_TRUE(oat_file.get() != nullptr);
  if (with_runtime_) {
    EXPECT_TRUE(oat_file->IsExecutable());
  }
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
  EXPECT_EQ(2u, dex_files.size());
}

// Case: We have a MultiDEX file where the non-main multdex entry is out of date.
// Expect: The status is kDex2OatNeeded.
TEST_P(OatFileAssistantTest, MultiDexNonMainOutOfDate) {
  std::string dex_location = GetScratchDir() + "/MultiDexNonMainOutOfDate.jar";

  // Compile code for GetMultiDexSrc1.
  Copy(GetMultiDexSrc1(), dex_location);
  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);

  // Now overwrite the dex file with GetMultiDexSrc2 so the non-main checksum
  // is out of date.
  Copy(GetMultiDexSrc2(), dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
  ExpectHasDexFiles(&oat_file_assistant, true);
}

// Case: We have a DEX file and an OAT file out of date with respect to the
// dex checksum.
TEST_P(OatFileAssistantTest, OatDexOutOfDate) {
  std::string dex_location = GetScratchDir() + "/OatDexOutOfDate.jar";

  // We create a dex, generate an oat for it, then overwrite the dex with a
  // different dex to make the oat out of date.
  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
  Copy(GetDexSrc2(), dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatDexOutOfDate, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);

  VerifyOptimizationStatus(dex_location,
                           default_context_.get(),
                           "run-from-apk-fallback",
                           "unknown",
                           "apk-more-recent",
                           OatFileAssistant::kLocationNoneOrError,
                           /*is_backed_by_vdex_only=*/false);
}

// Case: We have a DEX file and an (ODEX) VDEX file out of date with respect
// to the dex checksum, but no ODEX file.
TEST_P(OatFileAssistantTest, VdexDexOutOfDate) {
  std::string dex_location = GetScratchDir() + "/VdexDexOutOfDate.jar";
  std::string odex_location = GetOdexDir() + "/VdexDexOutOfDate.oat";

  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
  ASSERT_EQ(0, unlink(odex_location.c_str()));
  Copy(GetDexSrc2(), dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
}

// Case: We have a MultiDEX (ODEX) VDEX file where the non-main multidex entry
// is out of date and there is no corresponding ODEX file.
TEST_P(OatFileAssistantTest, VdexMultiDexNonMainOutOfDate) {
  std::string dex_location = GetScratchDir() + "/VdexMultiDexNonMainOutOfDate.jar";
  std::string odex_location = GetOdexDir() + "/VdexMultiDexNonMainOutOfDate.odex";

  Copy(GetMultiDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
  ASSERT_EQ(0, unlink(odex_location.c_str()));
  Copy(GetMultiDexSrc2(), dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
}

// Case: We have a DEX file and an OAT file out of date with respect to the
// boot image.
TEST_P(OatFileAssistantTest, OatImageOutOfDate) {
  std::string dex_location = GetScratchDir() + "/OatImageOutOfDate.jar";

  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location.c_str(),
                     CompilerFilter::kSpeed,
                     /* with_alternate_image= */ true);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOat,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOat,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatBootImageOutOfDate, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);

  VerifyOptimizationStatus(dex_location,
                           default_context_.get(),
                           "verify",
                           "vdex",
                           "up-to-date",
                           OatFileAssistant::kLocationOat,
                           /*is_backed_by_vdex_only=*/true);
}

TEST_P(OatFileAssistantTest, OatContextOutOfDate) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";

  std::string context_location = GetScratchDir() + "/ContextDex.jar";
  Copy(GetDexSrc1(), dex_location);
  Copy(GetDexSrc2(), context_location);

  std::string error_msg;
  std::vector<std::string> args;
  args.push_back("--dex-file=" + dex_location);
  args.push_back("--oat-file=" + odex_location);
  args.push_back("--class-loader-context=PCL[" + context_location + "]");
  ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;

  // Update the context by overriding the jar file.
  Copy(GetMultiDexSrc2(), context_location);

  std::unique_ptr<ClassLoaderContext> context =
      ClassLoaderContext::Create("PCL[" + context_location + "]");
  ASSERT_TRUE(context != nullptr);
  ASSERT_TRUE(context->OpenDexFiles());

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  VerifyOptimizationStatus(dex_location,
                           context.get(),
                           "verify",
                           "vdex",
                           "up-to-date",
                           OatFileAssistant::kLocationOdex,
                           /*is_backed_by_vdex_only=*/true,
                           /*check_context=*/true);
}

// Case: We have a DEX file and an ODEX file, but no OAT file.
TEST_P(OatFileAssistantTest, DexOdexNoOat) {
  std::string dex_location = GetScratchDir() + "/DexOdexNoOat.jar";
  std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex";

  // Create the dex and odex files
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Verify the status.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOdex,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);

  // We should still be able to get the non-executable odex file to run from.
  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_TRUE(oat_file.get() != nullptr);
}

// Case: We have a resource-only DEX file, no ODEX file and no
// OAT file. Expect: The status is kNoDexOptNeeded.
TEST_P(OatFileAssistantTest, ResourceOnlyDex) {
  std::string dex_location = GetScratchDir() + "/ResourceOnlyDex.jar";

  Copy(GetResourceOnlySrc1(), dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Verify the status.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kVerify,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, false);

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, false);

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "unknown",
                                       "unknown",
                                       "no-dex-code",
                                       OatFileAssistant::kLocationNoneOrError,
                                       /*is_backed_by_vdex_only=*/false);
}

// Case: We have a DEX file, an ODEX file and an OAT file.
// Expect: It shouldn't crash. We should load the oat file executable.
TEST_P(OatFileAssistantTest, OdexOatOverlap) {
  std::string dex_location = GetScratchDir() + "/OdexOatOverlap.jar";
  std::string odex_location = GetOdexDir() + "/OdexOatOverlap.odex";

  // Create the dex, the odex and the oat files.
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Verify things don't go bad.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/true);

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/true,
                               /*expected_location=*/OatFileAssistant::kLocationOat,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OatFileStatus());
  ExpectHasDexFiles(&oat_file_assistant, true);

  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_TRUE(oat_file.get() != nullptr);

  if (with_runtime_) {
    EXPECT_TRUE(oat_file->IsExecutable());
  }
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
  EXPECT_EQ(1u, dex_files.size());
}

// Case: We have a DEX file and up-to-date OAT file for it.
// Expect: We should load an executable dex file.
TEST_P(OatFileAssistantTest, LoadOatUpToDate) {
  std::string dex_location = GetScratchDir() + "/LoadOatUpToDate.jar";

  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Load the oat using an oat file assistant.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/true);

  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_TRUE(oat_file.get() != nullptr);
  if (with_runtime_) {
    EXPECT_TRUE(oat_file->IsExecutable());
  }
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
  EXPECT_EQ(1u, dex_files.size());
}

// Case: We have a DEX file and up-to-date quicken OAT file for it.
// Expect: We should still load the oat file as executable.
TEST_P(OatFileAssistantTest, LoadExecInterpretOnlyOatUpToDate) {
  std::string dex_location = GetScratchDir() + "/LoadExecInterpretOnlyOatUpToDate.jar";

  Copy(GetDexSrc1(), dex_location);
  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kVerify);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Load the oat using an oat file assistant.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/true);

  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_TRUE(oat_file.get() != nullptr);
  if (with_runtime_) {
    EXPECT_TRUE(oat_file->IsExecutable());
  }
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
  EXPECT_EQ(1u, dex_files.size());
}

// Case: We have a DEX file and up-to-date OAT file for it.
// Expect: Loading non-executable should load the oat non-executable.
TEST_P(OatFileAssistantTest, LoadNoExecOatUpToDate) {
  std::string dex_location = GetScratchDir() + "/LoadNoExecOatUpToDate.jar";

  Copy(GetDexSrc1(), dex_location);

  GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Load the oat using an oat file assistant.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/true);

  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_TRUE(oat_file.get() != nullptr);
  if (with_runtime_) {
    EXPECT_TRUE(oat_file->IsExecutable());
  }
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
  EXPECT_EQ(1u, dex_files.size());
}

// Turn an absolute path into a path relative to the current working
// directory.
static std::string MakePathRelative(const std::string& target) {
  char buf[MAXPATHLEN];
  std::string cwd = getcwd(buf, MAXPATHLEN);

  // Split the target and cwd paths into components.
  std::vector<std::string> target_path;
  std::vector<std::string> cwd_path;
  Split(target, '/', &target_path);
  Split(cwd, '/', &cwd_path);

  // Reverse the path components, so we can use pop_back().
  std::reverse(target_path.begin(), target_path.end());
  std::reverse(cwd_path.begin(), cwd_path.end());

  // Drop the common prefix of the paths. Because we reversed the path
  // components, this becomes the common suffix of target_path and cwd_path.
  while (!target_path.empty() && !cwd_path.empty() && target_path.back() == cwd_path.back()) {
    target_path.pop_back();
    cwd_path.pop_back();
  }

  // For each element of the remaining cwd_path, add '..' to the beginning
  // of the target path. Because we reversed the path components, we add to
  // the end of target_path.
  for (unsigned int i = 0; i < cwd_path.size(); i++) {
    target_path.push_back("..");
  }

  // Reverse again to get the right path order, and join to get the result.
  std::reverse(target_path.begin(), target_path.end());
  return android::base::Join(target_path, '/');
}

// Case: Non-absolute path to Dex location.
// Expect: Not sure, but it shouldn't crash.
TEST_P(OatFileAssistantTest, NonAbsoluteDexLocation) {
  std::string abs_dex_location = GetScratchDir() + "/NonAbsoluteDexLocation.jar";
  Copy(GetDexSrc1(), abs_dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  std::string dex_location = MakePathRelative(abs_dex_location);
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
}

// Case: Very short, non-existent Dex location.
// Expect: kNoDexOptNeeded.
TEST_P(OatFileAssistantTest, ShortDexLocation) {
  std::string dex_location = "/xx";

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
  std::string error_msg_ignored;
  EXPECT_FALSE(oat_file_assistant.HasDexFiles(&error_msg_ignored).has_value());
}

// Case: Non-standard extension for dex file.
// Expect: The status is kDex2OatNeeded.
TEST_P(OatFileAssistantTest, LongDexExtension) {
  std::string dex_location = GetScratchDir() + "/LongDexExtension.jarx";
  Copy(GetDexSrc1(), dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/true,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);

  EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
  EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
}

// Case: Mismatch between assumed values for SDK_INT and runtime SDK_INT.
// Expect: kOatAssumedValuesOutOfDate when 1) mismatched SDK versions, and 2) feature flag enabled.
TEST_P(OatFileAssistantTest, AssumedValuesOutOfDate) {
  std::string dex_location = GetScratchDir() + "/AssumedValuesOutOfDate.jar";
  std::string odex_location = GetOdexDir() + "/AssumedValuesOutOfDate.odex";
  Copy(GetDexSrc1(), dex_location);

  GenerateOdexForTest(dex_location,
                      odex_location,
                      CompilerFilter::kSpeed,
                      /*compilation_reason=*/nullptr,
                      /*extra_args=*/{"--assume-value=Landroid/os/Build$VERSION;->SDK_INT:76"});

  // Ensure both unset and differing runtime SDK versions yield out-of-date dexopt status.
  for (uint32_t runtime_sdk_version : {static_cast<uint32_t>(SdkVersion::kUnset), 77u}) {
    SCOPED_TRACE("Runtime SDK version: " + std::to_string(runtime_sdk_version));
    // Override the runtime SDK version and recreate the OFA context to reflect this.
    SetRuntimeSdkVersion(runtime_sdk_version);
    ofa_context_ = CreateOatFileAssistantContext();

    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

    OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
    // When the runtime SDK_INT differs from the compiled SDK_INT, reject the ODEX file.
    // Note that the VDEX remains usable.
    EXPECT_EQ(OatFileAssistant::kOatAssumedValuesOutOfDate, oat_file_assistant.OdexFileStatus());
    EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
    VerifyGetDexOptNeededDefault(&oat_file_assistant,
                                 CompilerFilter::kSpeed,
                                 /*expected_dexopt_needed=*/true,
                                 /*expected_is_vdex_usable=*/true,
                                 /*expected_location=*/OatFileAssistant::kLocationOdex,
                                 /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
  }
}

// A task to generate a dex location. Used by the RaceToGenerate test.
class RaceGenerateTask : public Task {
 public:
  RaceGenerateTask(OatFileAssistantBaseTest& test,
                   const std::string& dex_location,
                   const std::string& oat_location,
                   Mutex* lock)
      : test_(test),
        dex_location_(dex_location),
        oat_location_(oat_location),
        lock_(lock),
        loaded_oat_file_(nullptr) {}

  void Run([[maybe_unused]] Thread* self) override {
    // Load the dex files, and save a pointer to the loaded oat file, so that
    // we can verify only one oat file was loaded for the dex location.
    std::vector<std::unique_ptr<const DexFile>> dex_files;
    std::vector<std::string> error_msgs;
    const OatFile* oat_file = nullptr;
    {
      MutexLock mu(Thread::Current(), *lock_);
      // Create the oat file.
      std::vector<std::string> args;
      args.push_back("--dex-file=" + dex_location_);
      args.push_back("--oat-file=" + oat_location_);
      std::string error_msg;
      ASSERT_TRUE(test_.Dex2Oat(args, &error_msg)) << error_msg;
    }

    dex_files = Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(
        dex_location_.c_str(),
        Runtime::Current()->GetSystemClassLoader(),
        /*dex_elements=*/nullptr,
        &oat_file,
        &error_msgs);
    CHECK(!dex_files.empty()) << android::base::Join(error_msgs, '\n');
    if (dex_files[0]->GetOatDexFile() != nullptr) {
      loaded_oat_file_ = dex_files[0]->GetOatDexFile()->GetOatFile();
    }
    CHECK_EQ(loaded_oat_file_, oat_file);
  }

  const OatFile* GetLoadedOatFile() const { return loaded_oat_file_; }

 private:
  OatFileAssistantBaseTest& test_;
  std::string dex_location_;
  std::string oat_location_;
  Mutex* lock_;
  const OatFile* loaded_oat_file_;
};

// Test the case where dex2oat invocations race with multiple processes trying to
// load the oat file.
TEST_F(OatFileAssistantBaseTest, RaceToGenerate) {
  std::string dex_location = GetScratchDir() + "/RaceToGenerate.jar";
  std::string oat_location = GetOdexDir() + "/RaceToGenerate.oat";

  // Start the runtime to initialize the system's class loader.
  Thread::Current()->TransitionFromSuspendedToRunnable();
  runtime_->Start();

  // We use the lib core dex file, because it's large, and hopefully should
  // take a while to generate.
  Copy(GetLibCoreDexFileNames()[0], dex_location);

  const size_t kNumThreads = 16;
  Thread* self = Thread::Current();
  std::unique_ptr<ThreadPool> thread_pool(
      ThreadPool::Create("Oat file assistant test thread pool", kNumThreads));
  std::vector<std::unique_ptr<RaceGenerateTask>> tasks;
  Mutex lock("RaceToGenerate");
  for (size_t i = 0; i < kNumThreads; i++) {
    std::unique_ptr<RaceGenerateTask> task(
        new RaceGenerateTask(*this, dex_location, oat_location, &lock));
    thread_pool->AddTask(self, task.get());
    tasks.push_back(std::move(task));
  }
  thread_pool->StartWorkers(self);
  thread_pool->Wait(self, /* do_work= */ true, /* may_hold_locks= */ false);

  // Verify that tasks which got an oat file got a unique one.
  std::set<const OatFile*> oat_files;
  for (auto& task : tasks) {
    const OatFile* oat_file = task->GetLoadedOatFile();
    if (oat_file != nullptr) {
      EXPECT_TRUE(oat_files.find(oat_file) == oat_files.end());
      oat_files.insert(oat_file);
    }
  }
}

// Case: We have a DEX file and an ODEX file, and no OAT file,
// Expect: We should load the odex file executable.
TEST_P(OatFileAssistantTest, LoadDexOdexNoOat) {
  std::string dex_location = GetScratchDir() + "/LoadDexOdexNoOat.jar";
  std::string odex_location = GetOdexDir() + "/LoadDexOdexNoOat.odex";

  // Create the dex and odex files
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Load the oat using an executable oat file assistant.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/true);

  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_TRUE(oat_file.get() != nullptr);
  if (with_runtime_) {
    EXPECT_TRUE(oat_file->IsExecutable());
  }
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
  EXPECT_EQ(1u, dex_files.size());
}

// Case: We have a MultiDEX file and an ODEX file, and no OAT file.
// Expect: We should load the odex file executable.
TEST_P(OatFileAssistantTest, LoadMultiDexOdexNoOat) {
  std::string dex_location = GetScratchDir() + "/LoadMultiDexOdexNoOat.jar";
  std::string odex_location = GetOdexDir() + "/LoadMultiDexOdexNoOat.odex";

  // Create the dex and odex files
  Copy(GetMultiDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  // Load the oat using an executable oat file assistant.
  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
                                                               /*context=*/nullptr,
                                                               /*load_executable=*/true);

  std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
  ASSERT_TRUE(oat_file.get() != nullptr);
  if (with_runtime_) {
    EXPECT_TRUE(oat_file->IsExecutable());
  }
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
  EXPECT_EQ(2u, dex_files.size());
}

TEST(OatFileAssistantUtilsTest, DexLocationToOdexFilename) {
  std::string error_msg;
  std::string odex_file;

  EXPECT_TRUE(OatFileAssistant::DexLocationToOdexFilename(
      "/foo/bar/baz.jar", InstructionSet::kArm, &odex_file, &error_msg))
      << error_msg;
  EXPECT_EQ("/foo/bar/oat/arm/baz.odex", odex_file);

  EXPECT_TRUE(OatFileAssistant::DexLocationToOdexFilename(
      "/foo/bar/baz.funnyext", InstructionSet::kArm, &odex_file, &error_msg))
      << error_msg;
  EXPECT_EQ("/foo/bar/oat/arm/baz.odex", odex_file);

  EXPECT_FALSE(OatFileAssistant::DexLocationToOdexFilename(
      "nopath.jar", InstructionSet::kArm, &odex_file, &error_msg));

  EXPECT_TRUE(OatFileAssistant::DexLocationToOdexFilename(
      "/foo/bar/baz_noext", InstructionSet::kArm, &odex_file, &error_msg));
  EXPECT_EQ("/foo/bar/oat/arm/baz_noext.odex", odex_file);
}

// Verify the dexopt status values from dalvik.system.DexFile
// match the OatFileAssistant::DexOptStatus values.
TEST_F(OatFileAssistantBaseTest, DexOptStatusValues) {
  std::pair<OatFileAssistant::DexOptNeeded, const char*> mapping[] = {
      {OatFileAssistant::kNoDexOptNeeded, "NO_DEXOPT_NEEDED"},
      {OatFileAssistant::kDex2OatFromScratch, "DEX2OAT_FROM_SCRATCH"},
      {OatFileAssistant::kDex2OatForBootImage, "DEX2OAT_FOR_BOOT_IMAGE"},
      {OatFileAssistant::kDex2OatForFilter, "DEX2OAT_FOR_FILTER"},
  };

  ScopedObjectAccess soa(Thread::Current());
  StackHandleScope<1> hs(soa.Self());
  ClassLinker* linker = Runtime::Current()->GetClassLinker();
  Handle<mirror::Class> dexfile(
      hs.NewHandle(linker->FindSystemClass(soa.Self(), "Ldalvik/system/DexFile;")));
  ASSERT_FALSE(dexfile == nullptr);
  linker->EnsureInitialized(soa.Self(), dexfile, truetrue);

  for (std::pair<OatFileAssistant::DexOptNeeded, const char*> field : mapping) {
    ArtField* art_field = dexfile->FindStaticField(field.second, "I");
    ASSERT_FALSE(art_field == nullptr);
    EXPECT_EQ(art_field->GetTypeAsPrimitiveType(), Primitive::kPrimInt);
    EXPECT_EQ(field.first, art_field->GetInt(dexfile.Get()));
  }
}

TEST_P(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";

  std::string context_location = GetScratchDir() + "/ContextDex.jar";
  Copy(GetDexSrc1(), dex_location);
  Copy(GetDexSrc2(), context_location);

  std::string context_str = "PCL[" + context_location + "]";

  std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(context_str);
  ASSERT_TRUE(context != nullptr);
  ASSERT_TRUE(context->OpenDexFiles());

  std::string error_msg;
  std::vector<std::string> args;
  args.push_back("--dex-file=" + dex_location);
  args.push_back("--oat-file=" + odex_location);
  args.push_back("--class-loader-context=" + context_str);
  ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;

  // Update the context by overriding the jar file.
  Copy(GetMultiDexSrc2(), context_location);

  {
    std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str);
    ASSERT_TRUE(updated_context != nullptr);
    std::vector<int> context_fds;
    ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds, /*only_read_checksums=*/true));

    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

    OatFileAssistant oat_file_assistant =
        CreateOatFileAssistant(dex_location.c_str(), updated_context.get());
    // DexOptNeeded should advise compilation for filter when the context changes.
    VerifyGetDexOptNeededDefault(&oat_file_assistant,
                                 CompilerFilter::kDefaultCompilerFilter,
                                 /*expected_dexopt_needed=*/true,
                                 /*expected_is_vdex_usable=*/true,
                                 /*expected_location=*/OatFileAssistant::kLocationOdex,
                                 /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
  }
  {
    std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str);
    ASSERT_TRUE(updated_context != nullptr);
    std::vector<int> context_fds;
    ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds, /*only_read_checksums*/ true));
    args.push_back("--compiler-filter=verify");
    ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;

    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

    OatFileAssistant oat_file_assistant =
        CreateOatFileAssistant(dex_location.c_str(), updated_context.get());
    // Now check that DexOptNeeded does not advise compilation if we only verify the file.
    VerifyGetDexOptNeededDefault(&oat_file_assistant,
                                 CompilerFilter::kVerify,
                                 /*expected_dexopt_needed=*/false,
                                 /*expected_is_vdex_usable=*/true,
                                 /*expected_location=*/OatFileAssistant::kLocationOdex,
                                 /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
  }
}

// Case: We have a DEX file and speed-profile ODEX file for it. The caller's intention is to
// downgrade the compiler filter.
// Expect: Dexopt should be performed only if the target compiler filter is worse than the current
// one.
TEST_P(OatFileAssistantTest, Downgrade) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
  OatFileAssistant::DexoptTrigger downgrade_trigger{
      .dexopt_comparators = {OatFileAssistant::DexoptComparator::kComparingCompilerFilterReversed},
      .custom_comparator_reason = std::nullopt};

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        downgrade_trigger,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(
                CompilerFilter::kSpeed, /*profile_changed=*/false, /*downgrade=*/true));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        downgrade_trigger,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(
                CompilerFilter::kSpeedProfile, /*profile_changed=*/false, /*downgrade=*/true));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        downgrade_trigger,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
            oat_file_assistant.GetDexOptNeeded(
                CompilerFilter::kVerify, /*profile_changed=*/false, /*downgrade=*/true));
}

// Case: We have a DEX file but we don't have an ODEX file for it. The caller's intention is to
// downgrade the compiler filter.
// Expect: Dexopt should never be performed regardless of the target compiler filter.
TEST_P(OatFileAssistantTest, DowngradeNoOdex) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  Copy(GetDexSrc1(), dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
  OatFileAssistant::DexoptTrigger downgrade_trigger{
      .dexopt_comparators = {OatFileAssistant::DexoptComparator::kComparingCompilerFilterReversed},
      .custom_comparator_reason = std::nullopt};

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        downgrade_trigger,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/false,
                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(
                CompilerFilter::kSpeed, /*profile_changed=*/false, /*downgrade=*/true));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        downgrade_trigger,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/false,
                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(
                CompilerFilter::kSpeedProfile, /*profile_changed=*/false, /*downgrade=*/true));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        downgrade_trigger,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/false,
                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(
                CompilerFilter::kVerify, /*profile_changed=*/false, /*downgrade=*/true));
}

// Case: We have a DEX file and speed-profile ODEX file for it. The legacy version is called with
// both `profile_changed` and `downgrade` being true. This won't happen in the real case. Just to be
// complete.
// Expect: The behavior should be as `profile_changed` is false and `downgrade` is true.
TEST_P(OatFileAssistantTest, ProfileChangedDowngrade) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(
                CompilerFilter::kSpeed, /*profile_changed=*/true, /*downgrade=*/true));

  EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(
                CompilerFilter::kSpeedProfile, /*profile_changed=*/true, /*downgrade=*/true));

  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
            oat_file_assistant.GetDexOptNeeded(
                CompilerFilter::kVerify, /*profile_changed=*/true, /*downgrade=*/true));
}

// Case: We have a DEX file and speed-profile ODEX file for it. The caller's intention is to force
// the compilation.
// Expect: Dexopt should be performed regardless of the target compiler filter. The VDEX file is
// usable.
//
// The legacy version does not support this case. Historically, Package Manager does not take the
// result from OatFileAssistant for forced compilation. It uses an arbitrary non-zero value instead.
// Therefore, we don't test the legacy version here.
TEST_P(OatFileAssistantTest, Force) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
  OatFileAssistant::DexoptTrigger force_trigger{
      .dexopt_comparators = {OatFileAssistant::DexoptComparator::kCustomTargetIsBetterThanCurrent},
      .custom_comparator_reason = "force recompilation"};

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        force_trigger,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        force_trigger,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        force_trigger,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
}

// Case: We have a DEX file but we don't have an ODEX file for it. The caller's intention is to
// force the compilation.
// Expect: Dexopt should be performed regardless of the target compiler filter. No VDEX file is
// usable.
//
// The legacy version does not support this case. Historically, Package Manager does not take the
// result from OatFileAssistant for forced compilation. It uses an arbitrary non-zero value instead.
// Therefore, we don't test the legacy version here.
TEST_P(OatFileAssistantTest, ForceNoOdex) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  Copy(GetDexSrc1(), dex_location);

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
  OatFileAssistant::DexoptTrigger force_trigger{
      .dexopt_comparators = {OatFileAssistant::DexoptComparator::kCustomTargetIsBetterThanCurrent},
      .custom_comparator_reason = "force recompilation"};

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        force_trigger,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/false,
                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        force_trigger,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/false,
                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        force_trigger,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/false,
                        /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
}

// Case: We have a DEX file and a DM file for it, and the DEX file is uncompressed.
// Expect: Dexopt should be performed if the compiler filter is better than "verify". The location
// should be kLocationDm.
//
// The legacy version should return kDex2OatFromScratch if the target compiler filter is better than
// "verify".
TEST_P(OatFileAssistantTest, DmUpToDateDexUncompressed) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string dm_location = GetScratchDir() + "/TestDex.dm";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  std::string vdex_location = GetOdexDir() + "/TestDex.vdex";
  Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location);

  // Generate temporary ODEX and VDEX files in order to create the DM file from.
  GenerateOdexForTest(
      dex_location, odex_location, CompilerFilter::kVerify, "install", {"--copy-dex-files=false"});

  CreateDexMetadata(vdex_location, dm_location);

  // Cleanup the temporary files.
  ASSERT_EQ(0, unlink(odex_location.c_str()));
  ASSERT_EQ(0, unlink(vdex_location.c_str()));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "verify",
                                       "vdex-dm",
                                       "up-to-date",
                                       OatFileAssistant::kLocationDm,
                                       /*is_backed_by_vdex_only=*/true);

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationDm);
  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationDm);
  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationDm);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
}

// Case: We have a DEX file and a DM file for it, and the DEX file is compressed.
// Expect: Dexopt should be performed regardless of the compiler filter. The location
// should be kLocationDm.
TEST_P(OatFileAssistantTest, DmUpToDateDexCompressed) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string dm_location = GetScratchDir() + "/TestDex.dm";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  std::string vdex_location = GetOdexDir() + "/TestDex.vdex";
  Copy(GetMultiDexSrc1(), dex_location);

  // Generate temporary ODEX and VDEX files in order to create the DM file from.
  GenerateOdexForTest(
      dex_location, odex_location, CompilerFilter::kVerify, "install", {"--copy-dex-files=false"});

  CreateDexMetadata(vdex_location, dm_location);

  // Cleanup the temporary files.
  ASSERT_EQ(0, unlink(odex_location.c_str()));
  ASSERT_EQ(0, unlink(vdex_location.c_str()));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "verify",
                                       "vdex-dm",
                                       "up-to-date",
                                       OatFileAssistant::kLocationDm,
                                       /*is_backed_by_vdex_only=*/true);

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationDm);
  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationDm);
  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationDm);
  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
}

// Case: We have an ODEX file, but the DEX file is gone.
// Expect: No dexopt is needed, as there's nothing we can do.
TEST_P(OatFileAssistantTest, OdexNoDex) {
  std::string dex_location = GetScratchDir() + "/OdexNoDex.jar";
  std::string odex_location = GetOdexDir() + "/OdexNoDex.oat";

  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
  ASSERT_EQ(0, unlink(dex_location.c_str()));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "unknown",
                                       "unknown",
                                       "io-error-no-apk",
                                       OatFileAssistant::kLocationNoneOrError,
                                       /*is_backed_by_vdex_only=*/false);
}

// Case: We have a VDEX file, but the DEX file is gone.
// Expect: No dexopt is needed, as there's nothing we can do.
TEST_P(OatFileAssistantTest, VdexNoDex) {
  std::string dex_location = GetScratchDir() + "/VdexNoDex.jar";
  std::string odex_location = GetOdexDir() + "/VdexNoDex.oat";

  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
  ASSERT_EQ(0, unlink(odex_location.c_str()));
  ASSERT_EQ(0, unlink(dex_location.c_str()));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeededDefault(&oat_file_assistant,
                               CompilerFilter::kSpeed,
                               /*expected_dexopt_needed=*/false,
                               /*expected_is_vdex_usable=*/false,
                               /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
                               /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "unknown",
                                       "unknown",
                                       "io-error-no-apk",
                                       OatFileAssistant::kLocationNoneOrError,
                                       /*is_backed_by_vdex_only=*/false);
}

// Case: We have a VDEX file, generated without a boot image, and we now have a boot image.
// Expect: Dexopt only if the target compiler filter >= "speed-profile".
TEST_P(OatFileAssistantTest, ShouldRecompileForImageFromVdex) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  std::string vdex_location = GetOdexDir() + "/TestDex.vdex";
  Copy(GetMultiDexSrc1(), dex_location);

  // Compile without a boot image.
  GenerateOdexForTest(dex_location,
                      odex_location,
                      CompilerFilter::kVerify,
                      "install",
                      {"--boot-image=/nonx/boot.art"});

  // Delete the odex file and only keep the vdex.
  ASSERT_EQ(0, unlink(odex_location.c_str()));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
}

// Case: We have an ODEX file, generated without a boot image (filter: "verify"), and we now have a
// boot image.
// Expect: Dexopt only if the target compiler filter >= "speed-profile".
TEST_P(OatFileAssistantTest, ShouldRecompileForImageFromVerify) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  std::string vdex_location = GetOdexDir() + "/TestDex.vdex";
  Copy(GetMultiDexSrc1(), dex_location);

  // Compile without a boot image.
  GenerateOdexForTest(dex_location,
                      odex_location,
                      CompilerFilter::kVerify,
                      "install",
                      {"--boot-image=/nonx/boot.art"});

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
}

// Case: We have an ODEX file, generated without a boot image (filter: "speed-profile"), and we now
// have a boot image.
// Expect: Dexopt only if the target compiler filter >= "speed-profile".
TEST_P(OatFileAssistantTest, ShouldRecompileForImageFromSpeedProfile) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  std::string vdex_location = GetOdexDir() + "/TestDex.vdex";
  Copy(GetMultiDexSrc1(), dex_location);

  // Compile without a boot image.
  GenerateOdexForTest(dex_location,
                      odex_location,
                      CompilerFilter::kSpeedProfile,
                      "install",
                      {"--boot-image=/nonx/boot.art"});

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationOdex);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
}

// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, and the SDC file is in odex
// location.
// Expect: The best artifact location should be kLocationSdmOdex. Dexopt should be performed only if
// the compiler filter is better than "speed-profile".
//
// The legacy version should return kDex2OatFromScratch if the target compiler filter is better than
// "verify".
TEST_P(OatFileAssistantTest, SdmUpToDate) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string sdm_location =
      GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA));
  std::string dm_location = GetScratchDir() + "/TestDex.dm";
  std::string sdc_location = GetOdexDir() + "/TestDex.sdc";
  Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location);

  ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location,
                                               sdm_location,
                                               dm_location,
                                               CompilerFilter::kSpeedProfile,
                                               /*include_app_image=*/true,
                                               /*compilation_reason=*/"cloud"));
  ASSERT_NO_FATAL_FAILURE(
      CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "speed-profile",
                                       "cloud",
                                       "up-to-date",
                                       OatFileAssistant::kLocationSdmOdex,
                                       /*is_backed_by_vdex_only=*/false);

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeed,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationSdmOdex);
  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationSdmOdex);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationSdmOdex);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
}

// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, and the SDC file is in oat
// location.
// Expect: The best artifact location should be kLocationSdmOat.
TEST_P(OatFileAssistantTest, SdmUpToDateSdcInOatLocation) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string sdm_location =
      GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA));
  std::string dm_location = GetScratchDir() + "/TestDex.dm";
  Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location);

  std::string oat_location;
  std::string error_msg;
  ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
      dex_location, kRuntimeISA, &oat_location, &error_msg))
      << error_msg;
  std::string sdc_location = GetSdcFilename(oat_location);

  ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location,
                                               sdm_location,
                                               dm_location,
                                               CompilerFilter::kSpeedProfile,
                                               /*include_app_image=*/true,
                                               /*compilation_reason=*/"cloud"));
  ASSERT_NO_FATAL_FAILURE(
      CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "speed-profile",
                                       "cloud",
                                       "up-to-date",
                                       OatFileAssistant::kLocationSdmOat,
                                       /*is_backed_by_vdex_only=*/false);
}

// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, and the SDM file contains no
// ART file.
// Expect: The best artifact location should be kLocationSdmOdex.
TEST_P(OatFileAssistantTest, SdmUpToDateNoArt) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string sdm_location =
      GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA));
  std::string dm_location = GetScratchDir() + "/TestDex.dm";
  std::string sdc_location = GetOdexDir() + "/TestDex.sdc";
  Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location);

  ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location,
                                               sdm_location,
                                               dm_location,
                                               CompilerFilter::kSpeedProfile,
                                               /*include_app_image=*/false,
                                               /*compilation_reason=*/"cloud"));
  ASSERT_NO_FATAL_FAILURE(
      CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "speed-profile",
                                       "cloud",
                                       "up-to-date",
                                       OatFileAssistant::kLocationSdmOdex,
                                       /*is_backed_by_vdex_only=*/false);
}

// Case: We have SDM, DM, and SDC files for an uncompressed DEX file. Meanwhile, we have an ODEX
// file that is also up to date.
// Expect: The ODEX file is preferred over the SDM file. The best artifact location should be
// kLocationOdex.
TEST_P(OatFileAssistantTest, SdmAndOdexUpToDate) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string sdm_location =
      GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA));
  std::string dm_location = GetScratchDir() + "/TestDex.dm";
  std::string sdc_location = GetOdexDir() + "/TestDex.sdc";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location);

  ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location,
                                               sdm_location,
                                               dm_location,
                                               CompilerFilter::kSpeedProfile,
                                               /*include_app_image=*/true,
                                               /*compilation_reason=*/"cloud"));
  ASSERT_NO_FATAL_FAILURE(
      CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location));

  ASSERT_NO_FATAL_FAILURE(GenerateOdexForTest(dex_location,
                                              odex_location,
                                              CompilerFilter::kSpeedProfile,
                                              /*compilation_reason=*/"bg-dexopt"));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "speed-profile",
                                       "bg-dexopt",
                                       "up-to-date",
                                       OatFileAssistant::kLocationOdex,
                                       /*is_backed_by_vdex_only=*/false);
}

// Case: We have SDM, DM, and SDC files for an uncompressed DEX file. Meanwhile, we have a VDEX
// file that is also up to date.
// Expect: The SDM file is preferred over the VDEX file. The best artifact location should be
// kLocationSdmOdex.
TEST_P(OatFileAssistantTest, SdmAndVdexUpToDate) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string sdm_location =
      GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA));
  std::string dm_location = GetScratchDir() + "/TestDex.dm";
  std::string sdc_location = GetOdexDir() + "/TestDex.sdc";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location);

  ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location,
                                               sdm_location,
                                               dm_location,
                                               CompilerFilter::kSpeedProfile,
                                               /*include_app_image=*/true,
                                               /*compilation_reason=*/"cloud"));
  ASSERT_NO_FATAL_FAILURE(
      CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location));

  ASSERT_NO_FATAL_FAILURE(GenerateOdexForTest(dex_location,
                                              odex_location,
                                              CompilerFilter::kSpeedProfile,
                                              /*compilation_reason=*/"bg-dexopt"));
  ASSERT_EQ(0, unlink(odex_location.c_str()));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "speed-profile",
                                       "cloud",
                                       "up-to-date",
                                       OatFileAssistant::kLocationSdmOdex,
                                       /*is_backed_by_vdex_only=*/false);
}

// Case: We have SDM, DM, and SDC files for a compressed DEX file.
// Expect: The SDM file is still picked. Dexopt should be performed if the compiler filter is
// "speed-profile" or above.
TEST_P(OatFileAssistantTest, SdmUpToDateCompressedDex) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string sdm_location =
      GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA));
  std::string dm_location = GetScratchDir() + "/TestDex.dm";
  std::string sdc_location = GetOdexDir() + "/TestDex.sdc";
  Copy(GetMultiDexSrc1(), dex_location);

  ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location,
                                               sdm_location,
                                               dm_location,
                                               CompilerFilter::kSpeedProfile,
                                               /*include_app_image=*/true,
                                               /*compilation_reason=*/"cloud",
                                               /*extra_args=*/{"--copy-dex-files=false"}));
  ASSERT_NO_FATAL_FAILURE(
      CreateSecureDexMetadataCompanion(sdm_location, runtime_->GetApexVersions(), sdc_location));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "speed-profile",
                                       "cloud",
                                       "up-to-date",
                                       OatFileAssistant::kLocationSdmOdex,
                                       /*is_backed_by_vdex_only=*/false);

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpeedProfile,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationSdmOdex);
  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationSdmOdex);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
}

// Case: We have SDM, DM, and SDC files for an uncompressed DEX file, but the SDC file contains the
// wrong APEX versions.
// Expect: The SDM file is rejected, while the DM file is still picked. Dexopt should be performed
// if the compiler filter is better than "verify".
TEST_P(OatFileAssistantTest, SdmApexVersionMismatch) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string sdm_location =
      GetScratchDir() + ART_FORMAT("/TestDex.{}.sdm", GetInstructionSetString(kRuntimeISA));
  std::string dm_location = GetScratchDir() + "/TestDex.dm";
  std::string sdc_location = GetOdexDir() + "/TestDex.sdc";
  Copy(GetMultiDexUncompressedAlignedSrc1(), dex_location);

  ASSERT_NO_FATAL_FAILURE(GenerateSdmDmForTest(dex_location,
                                               sdm_location,
                                               dm_location,
                                               CompilerFilter::kSpeedProfile,
                                               /*include_app_image=*/true,
                                               /*compilation_reason=*/"cloud"));
  ASSERT_NO_FATAL_FAILURE(
      CreateSecureDexMetadataCompanion(sdm_location, "wrong-apex-version", sdc_location));

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());

  VerifyOptimizationStatusWithInstance(&oat_file_assistant,
                                       "verify",
                                       "vdex-dm",
                                       "up-to-date",
                                       OatFileAssistant::kLocationDm,
                                       /*is_backed_by_vdex_only=*/true);

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kSpaceProfile,
                        default_trigger_,
                        /*expected_dexopt_needed=*/true,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationDm);
  EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpaceProfile));

  VerifyGetDexOptNeeded(&oat_file_assistant,
                        CompilerFilter::kVerify,
                        default_trigger_,
                        /*expected_dexopt_needed=*/false,
                        /*expected_is_vdex_usable=*/true,
                        /*expected_location=*/OatFileAssistant::kLocationDm);
  EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
            oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
}

class CollectDexCacheVisitor : public DexCacheVisitor {
 public:
  explicit CollectDexCacheVisitor(
      std::unordered_map<std::string, ObjPtr<mirror::DexCache>>& dex_caches)
      : dex_caches_(dex_caches) {}

  void Visit(ObjPtr<mirror::DexCache> dex_cache)
      REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) override {
    dex_caches_[dex_cache->GetDexFile()->GetLocation()] = dex_cache;
  }

 private:
  std::unordered_map<std::string, ObjPtr<mirror::DexCache>>& dex_caches_;
};

// Test that, no matter the dex file is backed by an oat file or not, the location fields in
// DexFile, OatDexFile, and DexCache are the same as the actual dex location.
TEST_F(OatFileAssistantBaseTest, GetDexLocation) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string dex_location_multidex = dex_location + "!classes2.dex";
  std::string oat_location = GetOdexDir() + "/TestDex.odex";
  std::string art_location = GetOdexDir() + "/TestDex.art";

  // Start the runtime to initialize the system's class loader.
  Thread::Current()->TransitionFromSuspendedToRunnable();
  runtime_->Start();

  Copy(GetMultiDexSrc1(), dex_location);

  std::vector<std::unique_ptr<const DexFile>> dex_files;
  std::vector<std::string> error_msgs;
  const OatFile* oat_file = nullptr;

  dex_files = Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(
      dex_location.c_str(),
      Runtime::Current()->GetSystemClassLoader(),
      /*dex_elements=*/nullptr,
      &oat_file,
      &error_msgs);
  ASSERT_EQ(dex_files.size(), 2u) << android::base::Join(error_msgs, "\n");
  EXPECT_EQ(oat_file, nullptr);
  EXPECT_EQ(dex_files[0]->GetLocation(), dex_location);
  EXPECT_EQ(dex_files[1]->GetLocation(), dex_location_multidex);

  {
    // Create the oat file.
    std::vector<std::string> args;
    args.push_back("--dex-file=" + dex_location);
    args.push_back("--dex-location=TestDex.jar");
    args.push_back("--oat-file=" + oat_location);
    args.push_back("--app-image-file=" + art_location);
    std::string error_msg;
    ASSERT_TRUE(DexoptTest::Dex2Oat(args, &error_msg)) << error_msg;
  }
  dex_files = Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(
      dex_location.c_str(),
      Runtime::Current()->GetSystemClassLoader(),
      /*dex_elements=*/nullptr,
      &oat_file,
      &error_msgs);
  ASSERT_EQ(dex_files.size(), 2u) << android::base::Join(error_msgs, "\n");
  ASSERT_NE(oat_file, nullptr);
  EXPECT_EQ(dex_files[0]->GetLocation(), dex_location);
  EXPECT_EQ(dex_files[1]->GetLocation(), dex_location_multidex);
  EXPECT_EQ(oat_file->GetOatDexFiles()[0]->GetLocation(), dex_location);
  EXPECT_EQ(oat_file->GetOatDexFiles()[1]->GetLocation(), dex_location_multidex);

  std::unordered_map<std::string, ObjPtr<mirror::DexCache>> dex_caches;
  CollectDexCacheVisitor visitor(dex_caches);
  ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
  {
    ScopedObjectAccess soa(Thread::Current());
    ReaderMutexLock mu(Thread::Current(), *Locks::dex_lock_);
    class_linker->VisitDexCaches(&visitor);
    EXPECT_EQ(dex_caches[dex_location]->GetLocation()->ToModifiedUtf8(), dex_location);
    EXPECT_EQ(dex_caches[dex_location_multidex]->GetLocation()->ToModifiedUtf8(),
              dex_location_multidex);
  }
}

// Test that a dex file on the platform location gets the right hiddenapi domain,
// regardless of whether it has a backing oat file.
TEST_F(OatFileAssistantBaseTest, SystemFrameworkDir) {
  // Host tests rely on ANDROID_ROOT, but it's not supported by
  // OatFileManager::OpenDexFilesFromOat.
  TEST_DISABLED_FOR_HOST();

  std::string filebase = "OatFileAssistantTestSystemFrameworkDir";
  std::string dex_location = GetAndroidRoot() + "/framework/" + filebase + ".jar";
  Copy(GetDexSrc1(), dex_location);

  std::string odex_dir = GetAndroidRoot() + "/framework/oat/";
  mkdir(odex_dir.c_str(), 0700);
  odex_dir = odex_dir + std::string(GetInstructionSetString(kRuntimeISA));
  mkdir(odex_dir.c_str(), 0700);
  std::string oat_location = odex_dir + "/" + filebase + ".odex";
  std::string vdex_location = odex_dir + "/" + filebase + ".vdex";
  std::string art_location = odex_dir + "/" + filebase + ".art";
  // Clean up in case previous run crashed.
  remove(oat_location.c_str());
  remove(vdex_location.c_str());
  remove(art_location.c_str());

  // Start the runtime to initialize the system's class loader.
  Thread::Current()->TransitionFromSuspendedToRunnable();
  runtime_->Start();

  std::vector<std::unique_ptr<const DexFile>> dex_files_first;
  std::vector<std::unique_ptr<const DexFile>> dex_files_second;
  std::vector<std::string> error_msgs;
  const OatFile* oat_file = nullptr;

  dex_files_first = Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(
      dex_location.c_str(),
      Runtime::Current()->GetSystemClassLoader(),
      /*dex_elements=*/nullptr,
      &oat_file,
      &error_msgs);
  ASSERT_EQ(dex_files_first.size(), 1u) << android::base::Join(error_msgs, "\n");
  EXPECT_EQ(oat_file, nullptr) << dex_location;
  EXPECT_EQ(dex_files_first[0]->GetOatDexFile(), nullptr);

  // Register the dex file to get a domain.
  {
    ScopedObjectAccess soa(Thread::Current());
    Runtime::Current()->GetClassLinker()->RegisterDexFile(
        *dex_files_first[0],
        soa.Decode<mirror::ClassLoader>(Runtime::Current()->GetSystemClassLoader()));
  }
  std::string stored_dex_location = dex_files_first[0]->GetLocation();
  EXPECT_EQ(dex_files_first[0]->GetHiddenapiDomain(), hiddenapi::Domain::kPlatform);
  {
    // Create the oat file.
    std::vector<std::string> args;
    args.push_back("--dex-file=" + dex_location);
    args.push_back("--dex-location=" + filebase + ".jar");
    args.push_back("--oat-file=" + oat_location);
    args.push_back("--app-image-file=" + art_location);
    std::string error_msg;
    ASSERT_TRUE(DexoptTest::Dex2Oat(args, &error_msg)) << error_msg;
  }
  dex_files_second = Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(
      dex_location.c_str(),
      Runtime::Current()->GetSystemClassLoader(),
      /*dex_elements=*/nullptr,
      &oat_file,
      &error_msgs);
  ASSERT_EQ(dex_files_second.size(), 1u) << android::base::Join(error_msgs, "\n");
  ASSERT_NE(oat_file, nullptr);
  EXPECT_NE(dex_files_second[0]->GetOatDexFile(), nullptr);
  EXPECT_NE(dex_files_second[0]->GetOatDexFile()->GetOatFile(), nullptr);

  // Register the dex file to get a domain.
  {
    ScopedObjectAccess soa(Thread::Current());
    Runtime::Current()->GetClassLinker()->RegisterDexFile(
        *dex_files_second[0],
        soa.Decode<mirror::ClassLoader>(Runtime::Current()->GetSystemClassLoader()));
  }
  std::string oat_stored_dex_location = dex_files_second[0]->GetLocation();
  EXPECT_EQ(oat_stored_dex_location, stored_dex_location);
  EXPECT_EQ(dex_files_second[0]->GetHiddenapiDomain(), hiddenapi::Domain::kPlatform);
  EXPECT_EQ(0, remove(oat_location.c_str()));
}

// Make sure OAT files that require app images are not loaded as executable.
TEST_F(OatFileAssistantBaseTest, LoadOatNoArt) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  std::string art_location = GetOdexDir() + "/TestDex.art";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location,
                      odex_location,
                      CompilerFilter::kSpeed,
                      "install",
                      {
                          "--app-image-file=" + art_location,
                      });

  unlink(art_location.c_str());

  std::vector<std::string> error_msgs;
  const OatFile* oat_file = nullptr;

  // Start the runtime to initialize the system's class loader.
  Thread::Current()->TransitionFromSuspendedToRunnable();
  runtime_->Start();

  const auto dex_files = Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(
      dex_location.c_str(),
      Runtime::Current()->GetSystemClassLoader(),
      /*dex_elements=*/nullptr,
      &oat_file,
      &error_msgs);

  EXPECT_FALSE(dex_files.empty());
  ASSERT_NE(oat_file, nullptr);
  EXPECT_FALSE(oat_file->IsExecutable());
}

TEST_P(OatFileAssistantTest, GetDexOptNeededWithApexVersions) {
  std::string dex_location = GetScratchDir() + "/TestDex.jar";
  std::string odex_location = GetOdexDir() + "/TestDex.odex";
  Copy(GetDexSrc1(), dex_location);

  // Test that using the current's runtime apex versions works.
  {
    std::string error_msg;
    std::vector<std::string> args;
    args.push_back("--dex-file=" + dex_location);
    args.push_back("--oat-file=" + odex_location);
    args.push_back("--apex-versions=" + Runtime::Current()->GetApexVersions());
    ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;

    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

    OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
    EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
  }

  // Test that a subset of apex versions works.
  {
    std::string error_msg;
    std::vector<std::string> args;
    args.push_back("--dex-file=" + dex_location);
    args.push_back("--oat-file=" + odex_location);
    args.push_back("--apex-versions=" + Runtime::Current()->GetApexVersions().substr(01));
    ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;

    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

    OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
    EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
  }

  // Test that different apex versions require to recompile.
  {
    std::string error_msg;
    std::vector<std::string> args;
    args.push_back("--dex-file=" + dex_location);
    args.push_back("--oat-file=" + odex_location);
    args.push_back("--apex-versions=/1/2/3/4");
    ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;

    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

    OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
    EXPECT_EQ(OatFileAssistant::kOatBootImageOutOfDate, oat_file_assistant.OdexFileStatus());
  }
}

TEST_P(OatFileAssistantTest, Create) {
  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  std::unique_ptr<ClassLoaderContext> context;
  std::string error_msg;
  std::unique_ptr<OatFileAssistant> oat_file_assistant =
      OatFileAssistant::Create(dex_location,
                               GetInstructionSetString(kRuntimeISA),
                               default_context_->EncodeContextForDex2oat(/*base_dir=*/""),
                               /*load_executable=*/false,
                               /*only_load_trusted_executable=*/true,
                               MaybeGetOatFileAssistantContext(),
                               &context,
                               &error_msg);
  ASSERT_NE(oat_file_assistant, nullptr);

  // Verify that the created instance is usable.
  VerifyOptimizationStatusWithInstance(oat_file_assistant.get(),
                                       "speed",
                                       "install",
                                       "up-to-date",
                                       OatFileAssistant::kLocationOdex,
                                       /*is_backed_by_vdex_only=*/false);
}

TEST_P(OatFileAssistantTest, CreateWithNullContext) {
  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  std::unique_ptr<ClassLoaderContext> context;
  std::string error_msg;
  std::unique_ptr<OatFileAssistant> oat_file_assistant =
      OatFileAssistant::Create(dex_location,
                               GetInstructionSetString(kRuntimeISA),
                               /*context_str=*/std::nullopt,
                               /*load_executable=*/false,
                               /*only_load_trusted_executable=*/true,
                               MaybeGetOatFileAssistantContext(),
                               &context,
                               &error_msg);
  ASSERT_NE(oat_file_assistant, nullptr);
  ASSERT_EQ(context, nullptr);

  // Verify that the created instance is usable.
  VerifyOptimizationStatusWithInstance(oat_file_assistant.get(),
                                       "speed",
                                       "install",
                                       "up-to-date",
                                       OatFileAssistant::kLocationOdex,
                                       /*is_backed_by_vdex_only=*/false);
}

TEST_P(OatFileAssistantTest, ErrorOnInvalidIsaString) {
  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  std::unique_ptr<ClassLoaderContext> context;
  std::string error_msg;
  EXPECT_EQ(OatFileAssistant::Create(dex_location,
                                     /*isa_str=*/"foo",
                                     default_context_->EncodeContextForDex2oat(/*base_dir=*/""),
                                     /*load_executable=*/false,
                                     /*only_load_trusted_executable=*/true,
                                     MaybeGetOatFileAssistantContext(),
                                     &context,
                                     &error_msg),
            nullptr);
  EXPECT_EQ(error_msg, "Instruction set 'foo' is invalid");
}

TEST_P(OatFileAssistantTest, ErrorOnInvalidContextString) {
  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  std::unique_ptr<ClassLoaderContext> context;
  std::string error_msg;
  EXPECT_EQ(OatFileAssistant::Create(dex_location,
                                     GetInstructionSetString(kRuntimeISA),
                                     /*context_str=*/"foo",
                                     /*load_executable=*/false,
                                     /*only_load_trusted_executable=*/true,
                                     MaybeGetOatFileAssistantContext(),
                                     &context,
                                     &error_msg),
            nullptr);
  EXPECT_EQ(error_msg, "Class loader context 'foo' is invalid");
}

TEST_P(OatFileAssistantTest, ErrorOnInvalidContextFile) {
  std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
  std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
  Copy(GetDexSrc1(), dex_location);
  GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");

  // Create a broken context file.
  std::string context_location = GetScratchDir() + "/BrokenContext.jar";
  std::ofstream output(context_location);
  output.close();

  auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();

  std::unique_ptr<ClassLoaderContext> context;
  std::string error_msg;
  EXPECT_EQ(OatFileAssistant::Create(dex_location,
                                     GetInstructionSetString(kRuntimeISA),
                                     /*context_str=*/"PCL[" + context_location + "]",
                                     /*load_executable=*/false,
                                     /*only_load_trusted_executable=*/true,
                                     MaybeGetOatFileAssistantContext(),
                                     &context,
                                     &error_msg),
            nullptr);
  EXPECT_EQ(error_msg,
            "Failed to load class loader context files for '" + dex_location +
                "' with context 'PCL[" + context_location + "]'");
}

// Verifies that `OatFileAssistant::ValidateBootClassPathChecksums` accepts the checksum string
// produced by `gc::space::ImageSpace::GetBootClassPathChecksums`.
TEST_P(OatFileAssistantTest, ValidateBootClassPathChecksums) {
  std::string error_msg;
  auto create_and_verify = [&]() {
    std::string checksums = gc::space::ImageSpace::GetBootClassPathChecksums(
        ArrayRef<gc::space::ImageSpace* const>(runtime_->GetHeap()->GetBootImageSpaces()),
        ArrayRef<const DexFile* const>(runtime_->GetClassLinker()->GetBootClassPath()));
    std::string bcp_locations = android::base::Join(runtime_->GetBootClassPathLocations(), ':');

    ofa_context_ = CreateOatFileAssistantContext();
    auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
    return OatFileAssistant::ValidateBootClassPathChecksums(
        ofa_context_.get(), kRuntimeISA, checksums, bcp_locations, &error_msg);
  };

  ASSERT_TRUE(create_and_verify()) << error_msg;

  for (const std::string& src : {GetDexSrc1(), GetDexSrc2()}) {
    ASSERT_TRUE(InsertNewBootClasspathEntry(src, &error_msg)) << error_msg;
    ASSERT_TRUE(create_and_verify()) << error_msg;
  }
}

// TODO: More Tests:
//  * Test class linker falls back to unquickened dex for DexNoOat
//  * Test class linker falls back to unquickened dex for MultiDexNoOat
//  * Test using secondary isa
//  * Test for status of oat while oat is being generated (how?)
//  * Test case where 32 and 64 bit boot class paths differ,
//      and we ask IsInBootClassPath for a class in exactly one of the 32 or
//      64 bit boot class paths.
//  * Test unexpected scenarios (?):
//    - Dex is stripped, don't have odex.
//    - Oat file corrupted after status check, before reload unexecutable
//    because it's unrelocated and no dex2oat

INSTANTIATE_TEST_SUITE_P(WithOrWithoutRuntime,
                         OatFileAssistantTest,
                         ::testing::Values(truefalse));

}  // namespace art

Messung V0.5 in Prozent
C=91 H=95 G=92

¤ Dauer der Verarbeitung: 0.52 Sekunden  (vorverarbeitet am  2026-06-29) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik