Revert^2 "Add unit tests for odrefresh - step 2"

This reverts commit b89649bd95cffec48f6f6746054553f71ab3e807.

Reason for revert: Fixed the fugu build breakage

The test failed on fugu because fugu is running Android O, while the
test depends on a system property introduced in S. Since the whole
odrefresh program is for S and later, we don't need to run the test on
older platforms. This failure is fixed by adding a check on the API
level and skipping the test if the API level is below S.

Bug: 196188549
Test: atest art_standalone_odrefresh_tests

Change-Id: I484696d065d715da65ae262a5aa8b3e1b30ffdcf
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
index 2f973a4..acfd97a 100644
--- a/odrefresh/Android.bp
+++ b/odrefresh/Android.bp
@@ -25,8 +25,6 @@
 
 cc_defaults {
     name: "odrefresh-defaults",
-    host_supported: true,
-    defaults: ["art_defaults"],
     srcs: [
         "odrefresh.cc",
         "odr_common.cc",
@@ -45,15 +43,9 @@
     shared_libs: [
         "libartpalette",
         "libbase",
-        "libdexfile",
         "liblog",
     ],
     static_libs: ["libtinyxml2"],
-    target: {
-        android: {
-            compile_multilib: "first",
-        },
-    },
     tidy: true,
     tidy_flags: [
         "-format-style=file",
@@ -61,6 +53,24 @@
     ],
 }
 
+cc_defaults {
+    name: "odrefresh_binary_defaults",
+    host_supported: true,
+    defaults: [
+        "art_defaults",
+        "odrefresh-defaults",
+    ],
+    srcs: ["odrefresh_main.cc"],
+    shared_libs: [
+        "libdexfile",
+    ],
+    target: {
+        android: {
+            compile_multilib: "first",
+        },
+    },
+}
+
 cc_library_headers {
     name: "odrefresh_headers",
     export_include_dirs: ["include"],
@@ -87,8 +97,7 @@
 
 art_cc_binary {
     name: "odrefresh",
-    defaults: ["odrefresh-defaults"],
-    srcs: ["odrefresh_main.cc"],
+    defaults: ["odrefresh_binary_defaults"],
     required: [
         "dexoptanalyzer",
         "dex2oat",
@@ -107,9 +116,8 @@
     name: "odrefreshd",
     defaults: [
         "art_debug_defaults",
-        "odrefresh-defaults",
+        "odrefresh_binary_defaults",
     ],
-    srcs: ["odrefresh_main.cc"],
     required: [
         "dexoptanalyzerd",
         "dex2oatd",
@@ -132,7 +140,6 @@
     defaults: ["art_defaults"],
     host_supported: true,
     export_include_dirs: ["include"],
-
     local_include_dirs: ["include"],
     shared_libs: ["libartbase"],
     target: {
@@ -157,21 +164,19 @@
 
 art_cc_defaults {
     name: "art_odrefresh_tests_defaults",
-    generated_sources: ["art-odrefresh-operator-srcs"],
+    defaults: ["odrefresh-defaults"],
     header_libs: ["odrefresh_headers"],
     srcs: [
         "odr_artifacts_test.cc",
-        "odr_compilation_log.cc",
         "odr_compilation_log_test.cc",
-        "odr_fs_utils.cc",
         "odr_fs_utils_test.cc",
-        "odr_metrics.cc",
         "odr_metrics_test.cc",
-        "odr_metrics_record.cc",
         "odr_metrics_record_test.cc",
         "odrefresh_test.cc",
     ],
-    shared_libs: ["libbase"],
+    static_libs: [
+        "libgmock",
+    ],
 }
 
 // Version of ART gtest `art_odrefresh_tests` bundled with the ART APEX on target.
@@ -182,6 +187,10 @@
         "art_gtest_defaults",
         "art_odrefresh_tests_defaults",
     ],
+    shared_libs: [
+        "libdexfiled",
+    ],
+    test_config_template: "art_odrefresh_tests.xml",
 }
 
 // Standalone version of ART gtest `art_odrefresh_tests`, not bundled with the ART APEX on target.
@@ -191,6 +200,10 @@
         "art_standalone_gtest_defaults",
         "art_odrefresh_tests_defaults",
     ],
+    shared_libs: [
+        "libdexfile",
+    ],
+    test_config_template: "art_odrefresh_tests.xml",
 }
 
 genrule {
diff --git a/odrefresh/art_odrefresh_tests.xml b/odrefresh/art_odrefresh_tests.xml
new file mode 100644
index 0000000..f9d6df5
--- /dev/null
+++ b/odrefresh/art_odrefresh_tests.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Note: This test config file for {MODULE} is generated from a template. -->
+<configuration description="Runs {MODULE}.">
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/nativetest/{MODULE}" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp/nativetest" />
+        <option name="module-name" value="{MODULE}" />
+        <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
+        <option name="ld-library-path-64" value="/apex/com.android.art/lib64" />
+    </test>
+
+    <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+         one of the Mainline modules below is present on the device used for testing. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <!-- ART Mainline Module (internal version). -->
+        <option name="mainline-module-package-name" value="com.google.android.art" />
+        <!-- ART Mainline Module (external (AOSP) version). -->
+        <option name="mainline-module-package-name" value="com.android.art" />
+    </object>
+</configuration>
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index 23d5cb6..6764e2c 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -57,6 +57,10 @@
   std::string compilation_os_address_;
   std::string boot_classpath_;
 
+  // Staging directory for artifacts. The directory must exist and will be automatically removed
+  // after compilation. If empty, use the default directory.
+  std::string staging_dir_;
+
  public:
   explicit OdrConfig(const char* program_name)
     : dry_run_(false),
@@ -120,7 +124,12 @@
   const std::string& GetSystemServerClasspath() const { return system_server_classpath_; }
   const std::string& GetUpdatableBcpPackagesFile() const { return updatable_bcp_packages_file_; }
   bool UseCompilationOs() const { return !compilation_os_address_.empty(); }
-  std::string GetCompilationOsAddress() const { return compilation_os_address_; }
+  const std::string& GetCompilationOsAddress() const {
+    return compilation_os_address_;
+  }
+  const std::string& GetStagingDir() const {
+    return staging_dir_;
+  }
 
   void SetApexInfoListFile(const std::string& file_path) { apex_info_list_file_ = file_path; }
   void SetArtBinDir(const std::string& art_bin_dir) { art_bin_dir_ = art_bin_dir; }
@@ -144,6 +153,10 @@
 
   void SetBootClasspath(const std::string& classpath) { boot_classpath_ = classpath; }
 
+  void SetStagingDir(const std::string& staging_dir) {
+    staging_dir_ = staging_dir;
+  }
+
  private:
   // Returns a pair for the possible instruction sets for the configured instruction set
   // architecture. The first item is the 32-bit architecture and the second item is the 64-bit
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index 5b003c3..9503159 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -459,9 +459,17 @@
 }  // namespace
 
 OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config)
+    : OnDeviceRefresh(config,
+                      Concatenate({kOdrefreshArtifactDirectory, "/", kCacheInfoFile}),
+                      std::make_unique<ExecUtils>()) {}
+
+OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config,
+                                 const std::string& cache_info_filename,
+                                 std::unique_ptr<ExecUtils> exec_utils)
     : config_{config},
-      cache_info_filename_{Concatenate({kOdrefreshArtifactDirectory, "/", kCacheInfoFile})},
-      start_time_{time(nullptr)} {
+      cache_info_filename_{cache_info_filename},
+      start_time_{time(nullptr)},
+      exec_utils_{std::move(exec_utils)} {
   for (const std::string& jar : android::base::Split(config_.GetDex2oatBootClasspath(), ":")) {
     // Boot class path extensions are those not in the ART APEX. Updatable APEXes should not
     // have DEX files in the DEX2OATBOOTCLASSPATH. At the time of writing i18n is a non-updatable
@@ -1088,7 +1096,8 @@
   std::string error_msg;
   bool timed_out = false;
   const time_t timeout = GetSubprocessTimeout();
-  const int dexoptanalyzer_result = ExecAndReturnCode(args, timeout, &timed_out, &error_msg);
+  const int dexoptanalyzer_result =
+      exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, &error_msg);
   if (dexoptanalyzer_result == -1) {
     LOG(ERROR) << "Unexpected exit from dexoptanalyzer: " << error_msg;
     if (timed_out) {
@@ -1184,7 +1193,8 @@
     std::string error_msg;
     bool timed_out = false;
     const time_t timeout = GetSubprocessTimeout();
-    const int dexoptanalyzer_result = ExecAndReturnCode(args, timeout, &timed_out, &error_msg);
+    const int dexoptanalyzer_result =
+        exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, &error_msg);
     if (dexoptanalyzer_result == -1) {
       LOG(ERROR) << "Unexpected exit from dexoptanalyzer: " << error_msg;
       if (timed_out) {
@@ -1363,7 +1373,7 @@
   }
 
   bool timed_out = false;
-  int dex2oat_exit_code = ExecAndReturnCode(args, timeout, &timed_out, error_msg);
+  int dex2oat_exit_code = exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, error_msg);
   if (dex2oat_exit_code != 0) {
     if (timed_out) {
       metrics.SetStatus(OdrMetrics::Status::kTimeLimitExceeded);
@@ -1513,7 +1523,7 @@
     }
 
     bool timed_out = false;
-    int dex2oat_exit_code = ExecAndReturnCode(args, timeout, &timed_out, error_msg);
+    int dex2oat_exit_code = exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, error_msg);
     if (dex2oat_exit_code != 0) {
       if (timed_out) {
         metrics.SetStatus(OdrMetrics::Status::kTimeLimitExceeded);
@@ -1544,10 +1554,14 @@
   const char* staging_dir = nullptr;
   metrics.SetStage(OdrMetrics::Stage::kPreparation);
 
-  // Create staging area and assign label for generating compilation artifacts.
-  if (PaletteCreateOdrefreshStagingDirectory(&staging_dir) != PALETTE_STATUS_OK) {
-    metrics.SetStatus(OdrMetrics::Status::kStagingFailed);
-    return ExitCode::kCleanupFailed;
+  if (!config_.GetStagingDir().empty()) {
+    staging_dir = config_.GetStagingDir().c_str();
+  } else {
+    // Create staging area and assign label for generating compilation artifacts.
+    if (PaletteCreateOdrefreshStagingDirectory(&staging_dir) != PALETTE_STATUS_OK) {
+      metrics.SetStatus(OdrMetrics::Status::kStagingFailed);
+      return ExitCode::kCleanupFailed;
+    }
   }
 
   // Emit cache info before compiling. This can be used to throttle compilation attempts later.
diff --git a/odrefresh/odrefresh.h b/odrefresh/odrefresh.h
index c1f7d71..2a7164b 100644
--- a/odrefresh/odrefresh.h
+++ b/odrefresh/odrefresh.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,12 +18,14 @@
 #define ART_ODREFRESH_ODREFRESH_H_
 
 #include <ctime>
+#include <memory>
 #include <optional>
 #include <string>
 #include <vector>
 
 #include "com_android_apex.h"
 #include "com_android_art.h"
+#include "exec_utils.h"
 #include "odr_artifacts.h"
 #include "odr_config.h"
 #include "odr_metrics.h"
@@ -36,6 +38,11 @@
  public:
   explicit OnDeviceRefresh(const OdrConfig& config);
 
+  // Constructor with injections. For testing and internal use only.
+  OnDeviceRefresh(const OdrConfig& config,
+                  const std::string& cache_info_filename,
+                  std::unique_ptr<ExecUtils> exec_utils);
+
   // Returns the exit code, a list of ISAs that boot extensions should be compiled for, and a
   // boolean indicating whether the system server should be compiled.
   WARN_UNUSED ExitCode
@@ -171,6 +178,8 @@
 
   time_t max_child_process_seconds_;
 
+  std::unique_ptr<ExecUtils> exec_utils_;
+
   DISALLOW_COPY_AND_ASSIGN(OnDeviceRefresh);
 };
 
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index a09accb..a6682ec 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -14,19 +14,230 @@
  * limitations under the License.
  */
 
-#include "odrefresh/odrefresh.h"
+#include "odrefresh.h"
 
+#include <functional>
+#include <memory>
+#include <string_view>
+
+#ifdef __ANDROID__
+#include <android/api-level.h>
+#endif
+
+#include "android-base/properties.h"
+#include "android-base/scopeguard.h"
+#include "android-base/strings.h"
+#include "arch/instruction_set.h"
 #include "base/common_art_test.h"
 #include "base/file_utils.h"
+#include "exec_utils.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "odr_common.h"
+#include "odr_config.h"
+#include "odr_fs_utils.h"
+#include "odr_metrics.h"
+#include "odrefresh/odrefresh.h"
 
 namespace art {
 namespace odrefresh {
 
-TEST(OdRefreshTest, OdrefreshArtifactDirectory) {
-    // odrefresh.h defines kOdrefreshArtifactDirectory for external callers of odrefresh. This is
-    // where compilation artifacts end up.
-    ScopedUnsetEnvironmentVariable no_env("ART_APEX_DATA");
-    EXPECT_EQ(kOdrefreshArtifactDirectory, GetArtApexData() + "/dalvik-cache");
+using ::testing::AllOf;
+using ::testing::Contains;
+using ::testing::HasSubstr;
+using ::testing::Not;
+using ::testing::Return;
+
+constexpr int kReplace = 1;
+
+void CreateEmptyFile(const std::string& name) {
+  File* file = OS::CreateEmptyFile(name.c_str());
+  ASSERT_TRUE(file != nullptr);
+  file->Release();
+  delete file;
+}
+
+android::base::ScopeGuard<std::function<void()>> ScopedSetProperty(const std::string& key,
+                                                                   const std::string& value) {
+  std::string old_value = android::base::GetProperty(key, /*default_value=*/{});
+  android::base::SetProperty(key, value);
+  return android::base::ScopeGuard([=]() { android::base::SetProperty(key, old_value); });
+}
+
+class MockExecUtils : public ExecUtils {
+ public:
+  // A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead
+  // to a conflict between gmock and android-base/logging.h (b/132668253).
+  int ExecAndReturnCode(std::vector<std::string>& arg_vector,
+                        time_t,
+                        bool*,
+                        std::string*) const override {
+    return DoExecAndReturnCode(arg_vector);
+  }
+
+  MOCK_METHOD(int, DoExecAndReturnCode, (std::vector<std::string> & arg_vector), (const));
+};
+
+class OdRefreshTest : public CommonArtTest {
+ public:
+  OdRefreshTest() : config_("odrefresh") {}
+
+ protected:
+  void SetUp() override {
+    CommonArtTest::SetUp();
+
+    temp_dir_ = std::make_unique<ScratchDir>();
+    std::string_view temp_dir_path = temp_dir_->GetPath();
+    android::base::ConsumeSuffix(&temp_dir_path, "/");
+
+    std::string android_root_path = Concatenate({temp_dir_path, "/system"});
+    ASSERT_TRUE(EnsureDirectoryExists(android_root_path));
+    android_root_env_ = std::make_unique<ScopedUnsetEnvironmentVariable>("ANDROID_ROOT");
+    setenv("ANDROID_ROOT", android_root_path.c_str(), kReplace);
+
+    std::string android_art_root_path = Concatenate({temp_dir_path, "/apex/com.android.art"});
+    ASSERT_TRUE(EnsureDirectoryExists(android_art_root_path));
+    android_art_root_env_ = std::make_unique<ScopedUnsetEnvironmentVariable>("ANDROID_ART_ROOT");
+    setenv("ANDROID_ART_ROOT", android_art_root_path.c_str(), kReplace);
+
+    std::string art_apex_data_path = Concatenate({temp_dir_path, kArtApexDataDefaultPath});
+    ASSERT_TRUE(EnsureDirectoryExists(art_apex_data_path));
+    art_apex_data_env_ = std::make_unique<ScopedUnsetEnvironmentVariable>("ART_APEX_DATA");
+    setenv("ART_APEX_DATA", art_apex_data_path.c_str(), kReplace);
+
+    std::string dalvik_cache_dir = art_apex_data_path + "/dalvik-cache";
+    ASSERT_TRUE(EnsureDirectoryExists(dalvik_cache_dir));
+
+    std::string framework_dir = android_root_path + "/framework";
+    framework_jar_ = framework_dir + "/framework.jar";
+    location_provider_jar_ = framework_dir + "/com.android.location.provider.jar";
+    services_jar_ = framework_dir + "/services.jar";
+    std::string services_jar_prof = framework_dir + "/services.jar.prof";
+    std::string javalib_dir = android_art_root_path + "/javalib";
+    std::string boot_art = javalib_dir + "/boot.art";
+
+    // Create placeholder files.
+    ASSERT_TRUE(EnsureDirectoryExists(framework_dir));
+    CreateEmptyFile(framework_jar_);
+    CreateEmptyFile(location_provider_jar_);
+    CreateEmptyFile(services_jar_);
+    CreateEmptyFile(services_jar_prof);
+    ASSERT_TRUE(EnsureDirectoryExists(javalib_dir));
+    CreateEmptyFile(boot_art);
+
+    config_.SetApexInfoListFile(Concatenate({temp_dir_path, "/apex-info-list.xml"}));
+    config_.SetArtBinDir(Concatenate({temp_dir_path, "/bin"}));
+    config_.SetBootClasspath(framework_jar_);
+    config_.SetDex2oatBootclasspath(framework_jar_);
+    config_.SetSystemServerClasspath(Concatenate({location_provider_jar_, ":", services_jar_}));
+    config_.SetIsa(InstructionSet::kX86_64);
+    config_.SetZygoteKind(ZygoteKind::kZygote64_32);
+
+    std::string staging_dir = dalvik_cache_dir + "/staging";
+    ASSERT_TRUE(EnsureDirectoryExists(staging_dir));
+    config_.SetStagingDir(staging_dir);
+
+    auto mock_exec_utils = std::make_unique<MockExecUtils>();
+    mock_exec_utils_ = mock_exec_utils.get();
+
+    metrics_ = std::make_unique<OdrMetrics>(dalvik_cache_dir);
+    odrefresh_ = std::make_unique<OnDeviceRefresh>(
+        config_, dalvik_cache_dir + "/cache-info.xml", std::move(mock_exec_utils));
+  }
+
+  void TearDown() override {
+    metrics_.reset();
+    temp_dir_.reset();
+    android_root_env_.reset();
+    android_art_root_env_.reset();
+    art_apex_data_env_.reset();
+
+    CommonArtTest::TearDown();
+  }
+
+  std::unique_ptr<ScratchDir> temp_dir_;
+  std::unique_ptr<ScopedUnsetEnvironmentVariable> android_root_env_;
+  std::unique_ptr<ScopedUnsetEnvironmentVariable> android_art_root_env_;
+  std::unique_ptr<ScopedUnsetEnvironmentVariable> art_apex_data_env_;
+  OdrConfig config_;
+  MockExecUtils* mock_exec_utils_;
+  std::unique_ptr<OdrMetrics> metrics_;
+  std::unique_ptr<OnDeviceRefresh> odrefresh_;
+  std::string framework_jar_;
+  std::string location_provider_jar_;
+  std::string services_jar_;
+};
+
+TEST_F(OdRefreshTest, OdrefreshArtifactDirectory) {
+  // odrefresh.h defines kOdrefreshArtifactDirectory for external callers of odrefresh. This is
+  // where compilation artifacts end up.
+  ScopedUnsetEnvironmentVariable no_env("ART_APEX_DATA");
+  EXPECT_EQ(kOdrefreshArtifactDirectory, GetArtApexData() + "/dalvik-cache");
+}
+
+TEST_F(OdRefreshTest, CompileSetsCompilerFilter) {
+#ifdef __ANDROID__
+  // This test depends on a system property introduced in S. Since the whole odrefresh program is
+  // for S and later, we don't need to run the test on older platforms.
+  if (android_get_device_api_level() < __ANDROID_API_S__) {
+    return;
+  }
+#endif
+
+  {
+    // Defaults to "speed".
+    EXPECT_CALL(
+        *mock_exec_utils_,
+        DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
+                                  Not(Contains(HasSubstr("--profile-file-fd="))),
+                                  Contains("--compiler-filter=speed"))))
+        .WillOnce(Return(0));
+    EXPECT_CALL(*mock_exec_utils_,
+                DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
+                                          Not(Contains(HasSubstr("--profile-file-fd="))),
+                                          Contains("--compiler-filter=speed"))))
+        .WillOnce(Return(0));
+    EXPECT_EQ(odrefresh_->Compile(
+                  *metrics_, /*compile_boot_extensions=*/{}, /*compile_system_server=*/true),
+              ExitCode::kCompilationSuccess);
+  }
+
+  {
+    auto guard = ScopedSetProperty("dalvik.vm.systemservercompilerfilter", "speed-profile");
+    // services.jar has a profile, while location.provider.jar does not.
+    EXPECT_CALL(
+        *mock_exec_utils_,
+        DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
+                                  Not(Contains(HasSubstr("--profile-file-fd="))),
+                                  Contains("--compiler-filter=speed"))))
+        .WillOnce(Return(0));
+    EXPECT_CALL(*mock_exec_utils_,
+                DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
+                                          Contains(HasSubstr("--profile-file-fd=")),
+                                          Contains("--compiler-filter=speed-profile"))))
+        .WillOnce(Return(0));
+    EXPECT_EQ(odrefresh_->Compile(
+                  *metrics_, /*compile_boot_extensions=*/{}, /*compile_system_server=*/true),
+              ExitCode::kCompilationSuccess);
+  }
+
+  {
+    auto guard = ScopedSetProperty("dalvik.vm.systemservercompilerfilter", "verify");
+    EXPECT_CALL(
+        *mock_exec_utils_,
+        DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
+                                  Not(Contains(HasSubstr("--profile-file-fd="))),
+                                  Contains("--compiler-filter=verify"))))
+        .WillOnce(Return(0));
+    EXPECT_CALL(*mock_exec_utils_,
+                DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
+                                          Not(Contains(HasSubstr("--profile-file-fd="))),
+                                          Contains("--compiler-filter=verify"))))
+        .WillOnce(Return(0));
+    EXPECT_EQ(odrefresh_->Compile(
+                  *metrics_, /*compile_boot_extensions=*/{}, /*compile_system_server=*/true),
+              ExitCode::kCompilationSuccess);
+  }
 }
 
 }  // namespace odrefresh
diff --git a/runtime/exec_utils.h b/runtime/exec_utils.h
index 5e22639..e011c82 100644
--- a/runtime/exec_utils.h
+++ b/runtime/exec_utils.h
@@ -37,6 +37,28 @@
                       /*out*/ bool* timed_out,
                       /*out*/ std::string* error_msg);
 
+// A wrapper class to make the functions above mockable.
+class ExecUtils {
+ public:
+  virtual ~ExecUtils() = default;
+
+  virtual bool Exec(std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg) const {
+    return art::Exec(arg_vector, error_msg);
+  }
+
+  virtual int ExecAndReturnCode(std::vector<std::string>& arg_vector,
+                                /*out*/ std::string* error_msg) const {
+    return art::ExecAndReturnCode(arg_vector, error_msg);
+  }
+
+  virtual int ExecAndReturnCode(std::vector<std::string>& arg_vector,
+                                time_t timeout_secs,
+                                /*out*/ bool* timed_out,
+                                /*out*/ std::string* error_msg) const {
+    return art::ExecAndReturnCode(arg_vector, timeout_secs, timed_out, error_msg);
+  }
+};
+
 }  // namespace art
 
 #endif  // ART_RUNTIME_EXEC_UTILS_H_