Revert^4 "Generate a primary boot image for testing."

This reverts commit c9607e3cebb5fa942fec1d40b4a36a31ca5fb7c7.

Reason for revert: Fixed LUCI tests by not relying on any environment
variable.

Bug: 216467764
Test: Run art/tools/run-gtests.sh on Cuttlefish.
Change-Id: I20e2d27a5d7f9e0938bd09d58a2fb5b350c0c5ad
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 970fff0..5583858 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -691,6 +691,7 @@
     # Check ART test (internal) libraries.
     self._checker.check_native_library('libartd-gtest')
     self._checker.check_native_library('libartd-simulator-container')
+    self._checker.check_native_library('libartbased-testing')
 
     # Check ART test tools.
     self._checker.check_executable('signal_dumper')
diff --git a/compiler/Android.bp b/compiler/Android.bp
index e579e36..b53707a 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -494,6 +494,7 @@
         "art_standalone_gtest_defaults",
         "art_compiler_tests_defaults",
     ],
+    data: [":generate-boot-image"],
     shared_libs: [
         "libprofile",
         "libart-compiler",
diff --git a/compiler/art_standalone_compiler_tests.xml b/compiler/art_standalone_compiler_tests.xml
index f4f5236..f723971 100644
--- a/compiler/art_standalone_compiler_tests.xml
+++ b/compiler/art_standalone_compiler_tests.xml
@@ -27,6 +27,19 @@
         <option name="push" value="art-gtest-jars-MyClassNatives.jar->/data/local/tmp/art_standalone_compiler_tests/art-gtest-jars-MyClassNatives.jar" />
     </target_preparer>
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="append-bitness" value="true" />
+        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/art_standalone_compiler_tests/generate-boot-image" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/art_standalone_compiler_tests/art_boot_images" />
+        <option name="run-command" value="/data/local/tmp/art_standalone_compiler_tests/generate-boot-image --output-dir=/data/local/tmp/art_standalone_compiler_tests/art_boot_images" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/art_standalone_compiler_tests/art_boot_images" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/art_standalone_compiler_tests" />
         <option name="module-name" value="art_standalone_compiler_tests" />
diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp
index 254df02..86e9c7a 100644
--- a/dex2oat/Android.bp
+++ b/dex2oat/Android.bp
@@ -564,6 +564,7 @@
         "art_standalone_gtest_defaults",
         "art_dex2oat_tests_defaults",
     ],
+    data: [":generate-boot-image"],
     shared_libs: [
         "libartbase",
         "libart-compiler",
diff --git a/dex2oat/art_standalone_dex2oat_tests.xml b/dex2oat/art_standalone_dex2oat_tests.xml
index 6bbe69a..c117a02 100644
--- a/dex2oat/art_standalone_dex2oat_tests.xml
+++ b/dex2oat/art_standalone_dex2oat_tests.xml
@@ -49,6 +49,19 @@
         <option name="push" value="art-gtest-jars-VerifySoftFailDuringClinit.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-VerifySoftFailDuringClinit.dex" />
     </target_preparer>
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="append-bitness" value="true" />
+        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/art_standalone_dex2oat_tests/generate-boot-image" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/art_standalone_dex2oat_tests/art_boot_images" />
+        <option name="run-command" value="/data/local/tmp/art_standalone_dex2oat_tests/generate-boot-image --output-dir=/data/local/tmp/art_standalone_dex2oat_tests/art_boot_images" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/art_standalone_dex2oat_tests/art_boot_images" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/art_standalone_dex2oat_tests" />
         <option name="module-name" value="art_standalone_dex2oat_tests" />
diff --git a/dexdump/Android.bp b/dexdump/Android.bp
index a08aee3..1bc6334 100644
--- a/dexdump/Android.bp
+++ b/dexdump/Android.bp
@@ -99,4 +99,6 @@
         "art_standalone_gtest_defaults",
         "art_dexdump_tests_defaults",
     ],
+    data: [":generate-boot-image"],
+    test_config_template: ":art-gtests-target-standalone-with-boot-image-template",
 }
diff --git a/dexlist/Android.bp b/dexlist/Android.bp
index 1e69bdc..6a65ef4 100644
--- a/dexlist/Android.bp
+++ b/dexlist/Android.bp
@@ -84,4 +84,6 @@
         "art_standalone_gtest_defaults",
         "art_dexlist_tests_defaults",
     ],
+    data: [":generate-boot-image"],
+    test_config_template: ":art-gtests-target-standalone-with-boot-image-template",
 }
diff --git a/dexoptanalyzer/Android.bp b/dexoptanalyzer/Android.bp
index 385230b..1e37b8d 100644
--- a/dexoptanalyzer/Android.bp
+++ b/dexoptanalyzer/Android.bp
@@ -126,5 +126,6 @@
         "art_standalone_gtest_defaults",
         "art_dexoptanalyzer_tests_defaults",
     ],
+    data: [":generate-boot-image"],
     test_config_template: "art_standalone_dexoptanalyzer_tests.xml",
 }
diff --git a/dexoptanalyzer/art_standalone_dexoptanalyzer_tests.xml b/dexoptanalyzer/art_standalone_dexoptanalyzer_tests.xml
index 475b974..ca10751 100644
--- a/dexoptanalyzer/art_standalone_dexoptanalyzer_tests.xml
+++ b/dexoptanalyzer/art_standalone_dexoptanalyzer_tests.xml
@@ -35,6 +35,19 @@
         <option name="push" value="art-gtest-jars-VerifierDepsMulti.dex->/data/local/tmp/art_standalone_dexoptanalyzer_tests/art-gtest-jars-VerifierDepsMulti.dex" />
     </target_preparer>
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="append-bitness" value="true" />
+        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/art_standalone_dexoptanalyzer_tests/generate-boot-image" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/art_standalone_dexoptanalyzer_tests/art_boot_images" />
+        <option name="run-command" value="/data/local/tmp/art_standalone_dexoptanalyzer_tests/generate-boot-image --output-dir=/data/local/tmp/art_standalone_dexoptanalyzer_tests/art_boot_images" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/art_standalone_dexoptanalyzer_tests/art_boot_images" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/art_standalone_dexoptanalyzer_tests" />
         <option name="module-name" value="art_standalone_dexoptanalyzer_tests" />
diff --git a/libartbase/Android.bp b/libartbase/Android.bp
index 1f034e6..3c00949 100644
--- a/libartbase/Android.bp
+++ b/libartbase/Android.bp
@@ -253,6 +253,7 @@
     ],
     shared_libs: [
         "libartbase",
+        "libartbase-testing",
         "libdexfile",
     ],
 }
@@ -265,11 +266,53 @@
     ],
     shared_libs: [
         "libartbased",
+        "libartbased-testing",
         "libdexfiled",
     ],
 }
 
 art_cc_defaults {
+    name: "libartbase-testing-defaults",
+    defaults: [
+        "art_defaults",
+    ],
+    host_supported: true,
+    srcs: [
+        "base/testing.cc",
+    ],
+    shared_libs: [
+        "libbase",
+    ],
+    apex_available: [
+        "com.android.art.debug",
+        // TODO(b/183882457): This lib doesn't go into com.android.art, but
+        // apex_available lists need to be the same for internal libs to avoid
+        // stubs, and this depends on libdexfiled and others.
+        "com.android.art",
+    ],
+}
+
+art_cc_library {
+    name: "libartbase-testing",
+    defaults: [
+        "libartbase-testing-defaults",
+    ],
+    shared_libs: [
+        "libartbase",
+    ],
+}
+
+art_cc_library {
+    name: "libartbased-testing",
+    defaults: [
+        "libartbase-testing-defaults",
+    ],
+    shared_libs: [
+        "libartbased",
+    ],
+}
+
+art_cc_defaults {
     name: "art_libartbase_tests_defaults",
     srcs: [
         "arch/instruction_set_test.cc",
@@ -332,6 +375,8 @@
         "art_standalone_gtest_defaults",
         "art_libartbase_tests_defaults",
     ],
+    data: [":generate-boot-image"],
+    test_config_template: ":art-gtests-target-standalone-with-boot-image-template",
 }
 
 cc_library_headers {
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index b0f1c6c..e35cb93 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -43,6 +43,7 @@
 #include "base/runtime_debug.h"
 #include "base/stl_util.h"
 #include "base/string_view_cpp20.h"
+#include "base/testing.h"
 #include "base/unix_file/fd_file.h"
 #include "dex/art_dex_file_loader.h"
 #include "dex/dex_file-inl.h"
@@ -427,44 +428,18 @@
   android_system_ext_.clear();
 }
 
-static std::string GetDexFileName(const std::string& jar_prefix, bool host) {
-  std::string prefix(host ? GetAndroidRoot() : "");
-  const char* apexPath = (jar_prefix == "conscrypt") ? kAndroidConscryptApexDefaultPath
-    : (jar_prefix == "core-icu4j" ? kAndroidI18nApexDefaultPath
-    : kAndroidArtApexDefaultPath);
-  return StringPrintf("%s%s/javalib/%s.jar", prefix.c_str(), apexPath, jar_prefix.c_str());
-}
-
 std::vector<std::string> CommonArtTestImpl::GetLibCoreModuleNames() const {
-  // Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
-  // because that's what we use for compiling the boot.art image.
-  // It may contain additional modules from TEST_CORE_JARS.
-  return {
-      // CORE_IMG_JARS modules.
-      "core-oj",
-      "core-libart",
-      "okhttp",
-      "bouncycastle",
-      "apache-xml",
-      // Additional modules.
-      "core-icu4j",
-      "conscrypt",
-  };
+  return art::testing::GetLibCoreModuleNames();
 }
 
 std::vector<std::string> CommonArtTestImpl::GetLibCoreDexFileNames(
     const std::vector<std::string>& modules) const {
-  std::vector<std::string> result;
-  result.reserve(modules.size());
-  for (const std::string& module : modules) {
-    result.push_back(GetDexFileName(module, IsHost()));
-  }
-  return result;
+  return art::testing::GetLibCoreDexFileNames(modules);
 }
 
 std::vector<std::string> CommonArtTestImpl::GetLibCoreDexFileNames() const {
   std::vector<std::string> modules = GetLibCoreModuleNames();
-  return GetLibCoreDexFileNames(modules);
+  return art::testing::GetLibCoreDexFileNames(modules);
 }
 
 std::vector<std::string> CommonArtTestImpl::GetLibCoreDexLocations(
@@ -507,12 +482,7 @@
 std::string CommonArtTestImpl::GetTestDexFileName(const char* name) const {
   CHECK(name != nullptr);
   // The needed jar files for gtest are located next to the gtest binary itself.
-  std::string cmdline;
-  bool result = android::base::ReadFileToString("/proc/self/cmdline", &cmdline);
-  CHECK(result);
-  UniqueCPtr<char[]> executable_path(realpath(cmdline.c_str(), nullptr));
-  CHECK(executable_path != nullptr);
-  std::string executable_dir = dirname(executable_path.get());
+  std::string executable_dir = android::base::GetExecutableDirectory();
   for (auto ext : {".jar", ".dex"}) {
     std::string path = executable_dir + "/art-gtest-jars-" + name + ext;
     if (OS::FileExists(path.c_str())) {
@@ -564,7 +534,19 @@
     CHECK(host_dir != nullptr);
     return std::string(host_dir) + "/apex/art_boot_images/javalib";
   }
-  return GetPrebuiltPrimaryBootImageDir();
+  // On device, the boot image is generated by `generate-boot-image`.
+  // In a standalone test, the boot image is located next to the gtest binary itself.
+  std::string path = android::base::GetExecutableDirectory() + "/art_boot_images";
+  if (OS::DirectoryExists(path.c_str())) {
+    return path;
+  }
+  // In a chroot test, the boot image is located in a predefined location.
+  path = "/data/local/tmp/art_boot_images";
+  if (OS::DirectoryExists(path.c_str())) {
+    return path;
+  }
+  LOG(FATAL) << "Boot image not found";
+  UNREACHABLE();
 }
 
 std::string CommonArtTestImpl::GetCoreFileLocation(const char* suffix) {
diff --git a/libartbase/base/testing.cc b/libartbase/base/testing.cc
new file mode 100644
index 0000000..c260be7
--- /dev/null
+++ b/libartbase/base/testing.cc
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <string>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "base/file_utils.h"
+#include "base/globals.h"
+
+namespace art {
+namespace testing {
+
+namespace {
+
+std::string GetDexFileName(const std::string& jar_prefix, bool host) {
+  std::string prefix(host ? GetAndroidRoot() : "");
+  const char* apexPath =
+      (jar_prefix == "conscrypt") ?
+          kAndroidConscryptApexDefaultPath :
+          (jar_prefix == "core-icu4j" ? kAndroidI18nApexDefaultPath : kAndroidArtApexDefaultPath);
+  return android::base::StringPrintf(
+      "%s%s/javalib/%s.jar", prefix.c_str(), apexPath, jar_prefix.c_str());
+}
+
+}  // namespace
+
+std::vector<std::string> GetLibCoreModuleNames(bool core_only) {
+  // Note: This must start with the CORE_IMG_JARS in Android.common_path.mk because that's what we
+  // use for compiling the boot.art image. It may contain additional modules from TEST_CORE_JARS.
+
+  // CORE_IMG_JARS modules.
+  std::vector<std::string> modules{
+      "core-oj",
+      "core-libart",
+      "okhttp",
+      "bouncycastle",
+      "apache-xml",
+  };
+
+  // Additional modules.
+  if (!core_only) {
+    modules.push_back("core-icu4j");
+    modules.push_back("conscrypt");
+  }
+
+  return modules;
+}
+
+std::vector<std::string> GetLibCoreDexFileNames(const std::vector<std::string>& modules) {
+  std::vector<std::string> result;
+  result.reserve(modules.size());
+  for (const std::string& module : modules) {
+    result.push_back(GetDexFileName(module, !kIsTargetBuild));
+  }
+  return result;
+}
+
+std::vector<std::string> GetLibCoreDexFileNames(bool core_only) {
+  std::vector<std::string> modules = GetLibCoreModuleNames(core_only);
+  return GetLibCoreDexFileNames(modules);
+}
+
+}  // namespace testing
+}  // namespace art
diff --git a/libartbase/base/testing.h b/libartbase/base/testing.h
new file mode 100644
index 0000000..65bd3f6
--- /dev/null
+++ b/libartbase/base/testing.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** Utils for testing with minimal dependencies. */
+
+#ifndef ART_LIBARTBASE_BASE_TESTING_H_
+#define ART_LIBARTBASE_BASE_TESTING_H_
+
+#include <string>
+#include <vector>
+
+namespace art {
+namespace testing {
+
+// Gets the names of the libcore modules.
+// If `core_only` is true, only returns the names of CORE_IMG_JARS in Android.common_path.mk.
+std::vector<std::string> GetLibCoreModuleNames(bool core_only = false);
+
+// Gets the paths of the libcore dex files for given modules.
+std::vector<std::string> GetLibCoreDexFileNames(const std::vector<std::string>& modules);
+
+// Gets the paths of the libcore dex files.
+// If `core_only` is true, only returns the filenames of CORE_IMG_JARS in Android.common_path.mk.
+std::vector<std::string> GetLibCoreDexFileNames(bool core_only = false);
+
+}  // namespace testing
+}  // namespace art
+
+#endif  // ART_LIBARTBASE_BASE_TESTING_H_
diff --git a/oatdump/Android.bp b/oatdump/Android.bp
index 7335e09..5724280 100644
--- a/oatdump/Android.bp
+++ b/oatdump/Android.bp
@@ -235,5 +235,6 @@
         "art_standalone_gtest_defaults",
         "art_oatdump_tests_defaults",
     ],
+    data: [":generate-boot-image"],
     test_config: "art_standalone_oatdump_tests.xml",
 }
diff --git a/oatdump/art_standalone_oatdump_tests.xml b/oatdump/art_standalone_oatdump_tests.xml
index 573096b..bcd94ed 100644
--- a/oatdump/art_standalone_oatdump_tests.xml
+++ b/oatdump/art_standalone_oatdump_tests.xml
@@ -25,6 +25,20 @@
         <option name="push" value="art-gtest-jars-ProfileTestMultiDex.jar->/data/local/tmp/art_standalone_oatdump_tests/art-gtest-jars-ProfileTestMultiDex.jar" />
     </target_preparer>
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="append-bitness" value="true" />
+        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/art_standalone_oatdump_tests/generate-boot-image" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/art_standalone_oatdump_tests/art_boot_images" />
+        <!-- `compiler-filter=speed-profile` is required because this test checks the compiled code in the boot image. -->
+        <option name="run-command" value="/data/local/tmp/art_standalone_oatdump_tests/generate-boot-image --output-dir=/data/local/tmp/art_standalone_oatdump_tests/art_boot_images --compiler-filter=speed-profile" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/art_standalone_oatdump_tests/art_boot_images" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/art_standalone_oatdump_tests" />
         <option name="module-name" value="art_standalone_oatdump_tests" />
diff --git a/profman/Android.bp b/profman/Android.bp
index 8069a4b..0317fe6 100644
--- a/profman/Android.bp
+++ b/profman/Android.bp
@@ -193,6 +193,7 @@
         "art_standalone_gtest_defaults",
         "art_profman_tests_defaults",
     ],
+    data: [":generate-boot-image"],
     shared_libs: [
         "libprofile",
     ],
diff --git a/profman/art_standalone_profman_tests.xml b/profman/art_standalone_profman_tests.xml
index acf4bd7..a8d34b2 100644
--- a/profman/art_standalone_profman_tests.xml
+++ b/profman/art_standalone_profman_tests.xml
@@ -27,6 +27,19 @@
         <option name="push" value="art-gtest-jars-ProfileTestMultiDex.jar->/data/local/tmp/art_standalone_profman_tests/art-gtest-jars-ProfileTestMultiDex.jar" />
     </target_preparer>
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="append-bitness" value="true" />
+        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/art_standalone_profman_tests/generate-boot-image" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/art_standalone_profman_tests/art_boot_images" />
+        <option name="run-command" value="/data/local/tmp/art_standalone_profman_tests/generate-boot-image --output-dir=/data/local/tmp/art_standalone_profman_tests/art_boot_images" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/art_standalone_profman_tests/art_boot_images" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/art_standalone_profman_tests" />
         <option name="module-name" value="art_standalone_profman_tests" />
diff --git a/runtime/art_standalone_runtime_compiler_tests.xml b/runtime/art_standalone_runtime_compiler_tests.xml
index ca21146..3ae3a82 100644
--- a/runtime/art_standalone_runtime_compiler_tests.xml
+++ b/runtime/art_standalone_runtime_compiler_tests.xml
@@ -27,6 +27,19 @@
         <option name="push" value="art-gtest-jars-StaticLeafMethods.jar->/data/local/tmp/art_standalone_runtime_compiler_tests/art-gtest-jars-StaticLeafMethods.jar" />
     </target_preparer>
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="append-bitness" value="true" />
+        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/art_standalone_runtime_compiler_tests/generate-boot-image" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/art_standalone_runtime_compiler_tests/art_boot_images" />
+        <option name="run-command" value="/data/local/tmp/art_standalone_runtime_compiler_tests/generate-boot-image --output-dir=/data/local/tmp/art_standalone_runtime_compiler_tests/art_boot_images" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/art_standalone_runtime_compiler_tests/art_boot_images" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/art_standalone_runtime_compiler_tests" />
         <option name="module-name" value="art_standalone_runtime_compiler_tests" />
diff --git a/runtime/art_standalone_runtime_tests.xml b/runtime/art_standalone_runtime_tests.xml
index 9129a8f..5200529 100644
--- a/runtime/art_standalone_runtime_tests.xml
+++ b/runtime/art_standalone_runtime_tests.xml
@@ -61,6 +61,19 @@
         <option name="push" value="art-gtest-jars-XandY.jar->/data/local/tmp/art_standalone_runtime_tests/art-gtest-jars-XandY.jar" />
     </target_preparer>
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="append-bitness" value="true" />
+        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/art_standalone_runtime_tests/generate-boot-image" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/art_standalone_runtime_tests/art_boot_images" />
+        <option name="run-command" value="/data/local/tmp/art_standalone_runtime_tests/generate-boot-image --output-dir=/data/local/tmp/art_standalone_runtime_tests/art_boot_images" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/art_standalone_runtime_tests/art_boot_images" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.GTest" >
         <option name="native-test-device-path" value="/data/local/tmp/art_standalone_runtime_tests" />
         <option name="module-name" value="art_standalone_runtime_tests" />
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index e8c8385..a14d386 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -99,13 +99,7 @@
   options.push_back(std::make_pair(boot_class_path_string, nullptr));
   options.push_back(std::make_pair(boot_class_path_locations_string, nullptr));
   if (use_boot_image_) {
-    std::string image_location = GetImageLocation();
-    if (!IsHost()) {
-      // On target, the boot image can be outdated due to an ART update. In such case, the profile
-      // will be used for generating a boot image in memory.
-      image_location += "!/apex/com.android.art/etc/boot-image.prof";
-    }
-    options.emplace_back("-Ximage:" + image_location, nullptr);
+    options.emplace_back("-Ximage:" + GetImageLocation(), nullptr);
   }
   options.push_back(std::make_pair("-Xcheck:jni", nullptr));
   options.push_back(std::make_pair(min_heap_string, nullptr));
diff --git a/test/Android.bp b/test/Android.bp
index 07980fb..f2acf97 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -150,6 +150,13 @@
     srcs: ["art-gtests-target-standalone-root-template.xml"],
 }
 
+// Test configuration template for standalone ART gtests run with a boot image on target (not
+// bundled with the ART APEX).
+filegroup {
+    name: "art-gtests-target-standalone-with-boot-image-template",
+    srcs: ["art-gtests-target-standalone-with-boot-image-template.xml"],
+}
+
 art_cc_defaults {
     name: "art_standalone_test_defaults",
     defaults: [
@@ -222,6 +229,7 @@
         "libdexfiled",
         "libprofiled",
         "libartbased",
+        "libartbased-testing",
 
         // Library `libnativehelper` needs to appear after `libartd` here,
         // otherwise the following tests from module `libartd-runtime-gtest`
@@ -291,11 +299,13 @@
         "libnativehelper",
     ],
     static_libs: [
-        // For now, link `libart-gtest` statically for simplicity, to save the
-        // added complexity to package it in test suites (along with other test
-        // artifacts) and install it on device during tests.
+        // For now, link `libart-gtest` and `libartbase-testing` statically for
+        // simplicity, to save the added complexity to package it in test suites
+        // (along with other test artifacts) and install it on device during
+        // tests.
         // TODO(b/192070541): Consider linking `libart-gtest` dynamically.
         "libart-gtest",
+        "libartbase-testing",
     ],
 
     test_for: [
@@ -422,6 +432,7 @@
         "libdexfile",
         "libprofile",
         "libartbase",
+        "libartbase-testing",
     ],
 }
 
@@ -442,6 +453,7 @@
         "libdexfiled",
         "libprofiled",
         "libartbased",
+        "libartbased-testing",
     ],
 }
 
@@ -1305,6 +1317,7 @@
         // We need the ART testing apex, which contains all gtest binaries.
         // Note that due to build system and linker constraints the gtests must be in the apex.
         ":com.android.art.testing",
+        ":generate-boot-image",
     ],
     test_suites: ["general-tests"],
     test_config: "art-gtests-target-chroot.xml",
diff --git a/test/art-gtests-target-chroot.xml b/test/art-gtests-target-chroot.xml
index aa2473d..5fd76f8 100644
--- a/test/art-gtests-target-chroot.xml
+++ b/test/art-gtests-target-chroot.xml
@@ -21,6 +21,20 @@
     <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
     <target_preparer class="com.android.tradefed.targetprep.ArtChrootPreparer" />
 
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="append-bitness" value="true" />
+        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/art-test-chroot/data/local/tmp/generate-boot-image" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/art-test-chroot/data/local/tmp/art_boot_images" />
+        <!-- `compiler-filter=speed-profile` is required because OatDumpTest checks the compiled code in the boot image. -->
+        <option name="run-command" value="chroot /data/local/tmp/art-test-chroot /data/local/tmp/generate-boot-image --output-dir=/data/local/tmp/art_boot_images --compiler-filter=speed-profile" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/art-test-chroot/data/local/tmp/art_boot_images" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.ArtGTest" >
         <!-- TODO(b/147821328): These tests do not work since they need to write to /system -->
         <option name="exclude-filter" value="HiddenApiTest.DexDomain_System*:OatFileAssistantTest.SystemFrameworkDir" />
diff --git a/test/art-gtests-target-standalone-with-boot-image-template.xml b/test/art-gtests-target-standalone-with-boot-image-template.xml
new file mode 100644
index 0000000..f028f2f
--- /dev/null
+++ b/test/art-gtests-target-standalone-with-boot-image-template.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<!-- Note: This test config file for {MODULE} is generated from a template. -->
+<configuration description="Runs {MODULE}.">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}/{MODULE}" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="append-bitness" value="true" />
+        <option name="push-file" key="generate-boot-image" value="/data/local/tmp/{MODULE}/generate-boot-image" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <option name="run-command" value="mkdir -p /data/local/tmp/{MODULE}/art_boot_images" />
+        <option name="run-command" value="/data/local/tmp/{MODULE}/generate-boot-image --output-dir=/data/local/tmp/{MODULE}/art_boot_images" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/{MODULE}/art_boot_images" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp/{MODULE}" />
+        <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>
+
+    <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/test/generate-boot-image/Android.bp b/test/generate-boot-image/Android.bp
new file mode 100644
index 0000000..e03aade
--- /dev/null
+++ b/test/generate-boot-image/Android.bp
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "art_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["art_license"],
+}
+
+art_cc_binary {
+    name: "generate-boot-image",
+    defaults: [
+        "art_defaults",
+    ],
+    host_supported: false,
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    required: [
+        "dex2oat",
+    ],
+    srcs: ["generate-boot-image.cc"],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+    static_libs: [
+        "libartbase",
+        "libartbase-testing",
+    ],
+    tidy: true,
+    tidy_flags: [
+        "-format-style=file",
+    ],
+}
diff --git a/test/generate-boot-image/generate-boot-image.cc b/test/generate-boot-image/generate-boot-image.cc
new file mode 100644
index 0000000..475bfb1
--- /dev/null
+++ b/test/generate-boot-image/generate-boot-image.cc
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** A commandline tool to generate a primary boot image for testing. */
+
+#include <sys/stat.h>
+#include <sysexits.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <string>
+#include <vector>
+
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "arch/instruction_set.h"
+#include "base/file_utils.h"
+#include "base/globals.h"
+#include "base/os.h"
+#include "base/testing.h"
+
+namespace art {
+
+namespace {
+
+using ::android::base::Join;
+using ::android::base::StringPrintf;
+using ::art::testing::GetLibCoreDexFileNames;
+
+std::string GetCompilerExecutable() {
+  std::string compiler_executable = GetArtBinDir() + "/dex2oat";
+  if (kIsDebugBuild) {
+    compiler_executable += 'd';
+  }
+  compiler_executable += Is64BitInstructionSet(kRuntimeISA) ? "64" : "32";
+  return compiler_executable;
+}
+
+// Joins a list of commandline args into a single string, where each part is quoted with double
+// quotes. Note that this is a naive implementation that does NOT escape existing double quotes,
+// which is fine since we don't have existing double quotes in the args in this particular use case
+// and this code is never used in production.
+std::string BuildCommand(const std::vector<std::string>& args) {
+  std::string command = "";
+  for (const std::string& arg : args) {
+    if (!command.empty()) {
+      command += " ";
+    }
+    command += '"' + arg + '"';
+  }
+  return command;
+}
+
+int GenerateBootImage(const std::string& dir, const std::string& compiler_filter) {
+  std::string isa = GetInstructionSetString(kRuntimeISA);
+
+  std::vector<std::string> args;
+  args.push_back(GetCompilerExecutable());
+
+  std::vector<std::string> dex_files = GetLibCoreDexFileNames(/*core_only=*/true);
+  args.push_back("--runtime-arg");
+  args.push_back("-Xbootclasspath:" + Join(dex_files, ":"));
+  for (const std::string& file : dex_files) {
+    args.push_back("--dex-file=" + file);
+  }
+
+  args.push_back("--instruction-set=" + isa);
+  args.push_back(StringPrintf("--base=0x%08x", ART_BASE_ADDRESS));
+  args.push_back("--compiler-filter=" + compiler_filter);
+  args.push_back(StringPrintf("--profile-file=%s/etc/boot-image.prof", GetArtRoot().c_str()));
+  args.push_back("--avoid-storing-invocation");
+  args.push_back("--generate-debug-info");
+  args.push_back("--generate-build-id");
+  args.push_back("--image-format=lz4hc");
+  args.push_back("--strip");
+  args.push_back("--android-root=out/empty");
+
+  std::string path = StringPrintf("%s/%s", dir.c_str(), isa.c_str());
+  if (!OS::DirectoryExists(path.c_str())) {
+    CHECK_EQ(mkdir(path.c_str(), S_IRWXU), 0);
+  }
+  args.push_back(StringPrintf("--image=%s/boot.art", path.c_str()));
+  args.push_back(StringPrintf("--oat-file=%s/boot.oat", path.c_str()));
+
+  int exit_code = system(BuildCommand(args).c_str());
+  if (exit_code != 0) {
+    LOG(ERROR) << "dex2oat invocation failed. Exit code: " << exit_code;
+  }
+  return exit_code;
+}
+
+}  // namespace
+}  // namespace art
+
+int main(int argc, char** argv) {
+  android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
+
+  std::string dir = "";
+  // Set the compiler filter to `verify` by default to make test preparation faster.
+  std::string compiler_filter = "verify";
+  for (int i = 1; i < argc; i++) {
+    std::string_view arg{argv[i]};
+    if (android::base::ConsumePrefix(&arg, "--output-dir=")) {
+      dir = arg;
+    } else if (android::base::ConsumePrefix(&arg, "--compiler-filter=")) {
+      compiler_filter = arg;
+    } else {
+      LOG(ERROR) << android::base::StringPrintf("Unrecognized argument: '%s'", argv[i]);
+      exit(EX_USAGE);
+    }
+  }
+
+  if (dir.empty()) {
+    LOG(ERROR) << "--output-dir must be specified";
+    exit(EX_USAGE);
+  }
+
+  return art::GenerateBootImage(dir, compiler_filter);
+}
diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh
index b89efa2..8a1c10b 100755
--- a/tools/buildbot-build.sh
+++ b/tools/buildbot-build.sh
@@ -133,6 +133,8 @@
   make_command+=" event-log-tags"
   # Needed to extract prebuilt APEXes.
   make_command+=" deapexer"
+  # Needed to generate the primary boot image for testing.
+  make_command+=" generate-boot-image"
   # Build/install the required APEXes.
   make_command+=" ${apexes[*]}"
   make_command+=" ${specific_targets}"
diff --git a/tools/buildbot-sync.sh b/tools/buildbot-sync.sh
index 8c3e8ff..277839b 100755
--- a/tools/buildbot-sync.sh
+++ b/tools/buildbot-sync.sh
@@ -110,3 +110,20 @@
 activate_apex com.android.tzdata
 activate_apex com.android.conscrypt
 activate_apex com.android.os.statsd
+
+# Generate primary boot images on device for testing.
+for b in {32,64}; do
+  basename="generate-boot-image$b"
+  bin_on_host="$ANDROID_PRODUCT_OUT/system/bin/$basename"
+  bin_on_device="/data/local/tmp/$basename"
+  output_dir="/data/local/tmp/art_boot_images"
+  if [ -f $bin_on_host ]; then
+    msginfo "Generating the primary boot image ($b-bit)..."
+    adb push "$bin_on_host" "$ART_TEST_CHROOT$bin_on_device"
+    adb shell mkdir -p "$ART_TEST_CHROOT$output_dir"
+    # `compiler-filter=speed-profile` is required because OatDumpTest checks the compiled code in
+    # the boot image.
+    adb shell chroot "$ART_TEST_CHROOT" \
+      "$bin_on_device" --output-dir=$output_dir --compiler-filter=speed-profile
+  fi
+done