Avoid dynamic lib dependencies on internal ART libraries in the
libartpalette and dex2oat CTS gtests.

We cannot use art_standalone_gtest_defaults. dex2oat_cts_test.cc rips
out the necessary bits from common_art_test.cc instead, with as few
changes as possible.

With this change we can reenable art_standalone_dex2oat_cts_tests
again (cf. b/288212464#comment5).

Merged-In set to a CL in main to avoid merging there - gtests are
already linked statically there.

Test: m art_standalone_libartpalette_tests \
        art_standalone_dex2oat_cts_tests
  Do `readelf -d` on the test binaries to check that only stable ABI
  libraries are in the NEEDED list.
Test: cts-tradefed run commandAndExit cts \
        -m art_standalone_dex2oat_cts_tests
  on a tm-release image.
Test: cts-tradefed run commandAndExit cts \
        -m art_standalone_libartpalette_tests
  on a tm-release image.
Bug: 306064090
Bug: 288212464
Merged-In: I75a844f53663385ef98351f60d3adb900157f5e5
(cherry picked from https://android-review.googlesource.com/q/commit:27c09bed3bbda943f47e869b00b8b627c4c8402b)
Merged-In: I1ff4174490ff50dbd2177cf5999fc6ef4bfbaa49
Change-Id: I4a8d1eddaacf9e476f7d3d783edec0223cb39c04
diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp
index 7341774..2938127 100644
--- a/dex2oat/Android.bp
+++ b/dex2oat/Android.bp
@@ -620,7 +620,7 @@
 // Counterpart to art_standalone_dex2oat_tests for tests that go into CTS.
 art_cc_test {
     name: "art_standalone_dex2oat_cts_tests",
-    defaults: ["art_standalone_gtest_defaults"],
+    defaults: ["art_cts_gtest_defaults"],
     srcs: ["dex2oat_cts_test.cc"],
     data: [
         ":art-gtest-jars-Main",
diff --git a/dex2oat/dex2oat_cts_test.cc b/dex2oat/dex2oat_cts_test.cc
index 2541264..25cd94b 100644
--- a/dex2oat/dex2oat_cts_test.cc
+++ b/dex2oat/dex2oat_cts_test.cc
@@ -14,24 +14,252 @@
  * limitations under the License.
  */
 
+#include <dirent.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <fstream>
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/file.h"
+#include "android-base/unique_fd.h"
 #include "base/file_utils.h"
-#include "dex2oat_environment_test.h"
+#include "base/os.h"
+#include "base/stl_util.h"
+#include "base/unix_file/fd_file.h"
+#include "gtest/gtest.h"
 
 namespace art {
+namespace {
 
-class Dex2oatCtsTest : public CommonArtTest, public Dex2oatScratchDirs {
+void ClearDirectory(const char* dirpath, bool recursive = true) {
+  ASSERT_TRUE(dirpath != nullptr);
+  DIR* dir = opendir(dirpath);
+  ASSERT_TRUE(dir != nullptr);
+  dirent* e;
+  struct stat s;
+  while ((e = readdir(dir)) != nullptr) {
+    if ((strcmp(e->d_name, ".") == 0) || (strcmp(e->d_name, "..") == 0)) {
+      continue;
+    }
+    std::string filename(dirpath);
+    filename.push_back('/');
+    filename.append(e->d_name);
+    int stat_result = lstat(filename.c_str(), &s);
+    ASSERT_EQ(0, stat_result) << "unable to stat " << filename;
+    if (S_ISDIR(s.st_mode)) {
+      if (recursive) {
+        ClearDirectory(filename.c_str());
+        int rmdir_result = rmdir(filename.c_str());
+        ASSERT_EQ(0, rmdir_result) << filename;
+      }
+    } else {
+      int unlink_result = unlink(filename.c_str());
+      ASSERT_EQ(0, unlink_result) << filename;
+    }
+  }
+  closedir(dir);
+}
+
+class Dex2oatScratchDirs {
  public:
+  void SetUp(const std::string& android_data) {
+    // Create a scratch directory to work from.
+
+    // Get the realpath of the android data. The oat dir should always point to real location
+    // when generating oat files in dalvik-cache. This avoids complicating the unit tests
+    // when matching the expected paths.
+    UniqueCPtr<const char[]> android_data_real(realpath(android_data.c_str(), nullptr));
+    ASSERT_TRUE(android_data_real != nullptr)
+        << "Could not get the realpath of the android data" << android_data << strerror(errno);
+
+    scratch_dir_.assign(android_data_real.get());
+    scratch_dir_ += "/Dex2oatEnvironmentTest";
+    ASSERT_EQ(0, mkdir(scratch_dir_.c_str(), 0700));
+
+    // Create a subdirectory in scratch for odex files.
+    odex_oat_dir_ = scratch_dir_ + "/oat";
+    ASSERT_EQ(0, mkdir(odex_oat_dir_.c_str(), 0700));
+
+    odex_dir_ = odex_oat_dir_ + "/" + std::string(GetInstructionSetString(kRuntimeISA));
+    ASSERT_EQ(0, mkdir(odex_dir_.c_str(), 0700));
+  }
+
+  void TearDown() {
+    ClearDirectory(odex_dir_.c_str());
+    ASSERT_EQ(0, rmdir(odex_dir_.c_str()));
+
+    ClearDirectory(odex_oat_dir_.c_str());
+    ASSERT_EQ(0, rmdir(odex_oat_dir_.c_str()));
+
+    ClearDirectory(scratch_dir_.c_str());
+    ASSERT_EQ(0, rmdir(scratch_dir_.c_str()));
+  }
+
+  // Scratch directory, for dex and odex files (oat files will go in the
+  // dalvik cache).
+  const std::string& GetScratchDir() const { return scratch_dir_; }
+
+  // Odex directory is the subdirectory in the scratch directory where odex
+  // files should be located.
+  const std::string& GetOdexDir() const { return odex_dir_; }
+
+ private:
+  std::string scratch_dir_;
+  std::string odex_oat_dir_;
+  std::string odex_dir_;
+};
+
+class Dex2oatCtsTest : public Dex2oatScratchDirs, public testing::Test {
+ public:
+  void SetUpAndroidDataDir(std::string& android_data) {
+    android_data = "/data/local/tmp";
+    android_data += "/art-data-XXXXXX";
+    if (mkdtemp(&android_data[0]) == nullptr) {
+      PLOG(FATAL) << "mkdtemp(\"" << &android_data[0] << "\") failed";
+    }
+    setenv("ANDROID_DATA", android_data.c_str(), 1);
+  }
+
+  void TearDownAndroidDataDir(const std::string& android_data,
+                              bool fail_on_error) {
+    if (fail_on_error) {
+      ASSERT_EQ(rmdir(android_data.c_str()), 0);
+    } else {
+      rmdir(android_data.c_str());
+    }
+  }
+
+  std::string GetTestDexFileName(const char* name) const {
+    CHECK(name != nullptr);
+    // The needed jar files for gtest are located next to the gtest binary itself.
+    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())) {
+        return path;
+      }
+    }
+    LOG(FATAL) << "Test file " << name << " not found";
+    UNREACHABLE();
+  }
+
   void SetUp() override {
-    CommonArtTest::SetUp();
+    SetUpAndroidDataDir(android_data_);
     Dex2oatScratchDirs::SetUp(android_data_);
   }
 
   void TearDown() override {
     Dex2oatScratchDirs::TearDown();
-    CommonArtTest::TearDown();
+    TearDownAndroidDataDir(android_data_, true);
+  }
+
+  struct ForkAndExecResult {
+    enum Stage {
+      kLink,
+      kFork,
+      kWaitpid,
+      kFinished,
+    };
+    Stage stage;
+    int status_code;
+
+    bool StandardSuccess() {
+      return stage == kFinished && WIFEXITED(status_code) && WEXITSTATUS(status_code) == 0;
+    }
+  };
+  using OutputHandlerFn = std::function<void(char*, size_t)>;
+  using PostForkFn = std::function<bool()>;
+
+  ForkAndExecResult ForkAndExec(
+      const std::vector<std::string>& argv,
+      const PostForkFn& post_fork,
+      const OutputHandlerFn& handler) {
+    ForkAndExecResult result;
+    result.status_code = 0;
+    result.stage = ForkAndExecResult::kLink;
+
+    std::vector<const char*> c_args;
+    c_args.reserve(argv.size() + 1);
+    for (const std::string& str : argv) {
+      c_args.push_back(str.c_str());
+    }
+    c_args.push_back(nullptr);
+
+    android::base::unique_fd link[2];
+    {
+      int link_fd[2];
+
+      if (pipe(link_fd) == -1) {
+        return result;
+      }
+      link[0].reset(link_fd[0]);
+      link[1].reset(link_fd[1]);
+    }
+
+    result.stage = ForkAndExecResult::kFork;
+
+    pid_t pid = fork();
+    if (pid == -1) {
+      return result;
+    }
+
+    if (pid == 0) {
+      if (!post_fork()) {
+        LOG(ERROR) << "Failed post-fork function";
+        exit(1);
+        UNREACHABLE();
+      }
+
+      // Redirect stdout and stderr.
+      dup2(link[1].get(), STDOUT_FILENO);
+      dup2(link[1].get(), STDERR_FILENO);
+
+      link[0].reset();
+      link[1].reset();
+
+      execv(c_args[0], const_cast<char* const*>(c_args.data()));
+      exit(1);
+      UNREACHABLE();
+    }
+
+    result.stage = ForkAndExecResult::kWaitpid;
+    link[1].reset();
+
+    char buffer[128] = { 0 };
+    ssize_t bytes_read = 0;
+    while (TEMP_FAILURE_RETRY(bytes_read = read(link[0].get(), buffer, 128)) > 0) {
+      handler(buffer, bytes_read);
+    }
+    handler(buffer, 0u);  // End with a virtual write of zero length to simplify clients.
+
+    link[0].reset();
+
+    if (waitpid(pid, &result.status_code, 0) == -1) {
+      return result;
+    }
+
+    result.stage = ForkAndExecResult::kFinished;
+    return result;
+  }
+
+  ForkAndExecResult ForkAndExec(
+      const std::vector<std::string>& argv, const PostForkFn& post_fork, std::string* output) {
+    auto string_collect_fn = [output](char* buf, size_t len) {
+      *output += std::string(buf, len);
+    };
+    return ForkAndExec(argv, post_fork, string_collect_fn);
   }
 
  protected:
+  std::string android_data_;
+
   // Stripped down counterpart to Dex2oatEnvironmentTest::Dex2Oat that only adds
   // enough arguments for our purposes.
   int Dex2Oat(const std::vector<std::string>& dex2oat_args,
@@ -63,6 +291,8 @@
   }
 };
 
+}  // namespace
+
 // Run dex2oat with --enable-palette-compilation-hooks to force calls to
 // PaletteNotify{Start,End}Dex2oatCompilation.
 TEST_F(Dex2oatCtsTest, CompilationHooks) {
diff --git a/libartpalette/Android.bp b/libartpalette/Android.bp
index 6e7415e..7c38752 100644
--- a/libartpalette/Android.bp
+++ b/libartpalette/Android.bp
@@ -116,7 +116,6 @@
     srcs: ["apex/palette_test.cc"],
     shared_libs: [
         "libartpalette",
-        "libnativehelper",
     ],
     target: {
         android: {
@@ -133,6 +132,9 @@
         "art_gtest_defaults",
         "art_libartpalette_tests_defaults",
     ],
+    shared_libs: [
+        "libnativehelper",
+    ],
     host_supported: true,
 }
 
@@ -141,9 +143,15 @@
 art_cc_test {
     name: "art_standalone_libartpalette_tests",
     defaults: [
-        "art_standalone_gtest_defaults",
+        "art_cts_gtest_defaults",
         "art_libartpalette_tests_defaults",
     ],
+    static_libs: [
+        "libc++fs",
+        // libnativehelper_lazy has the glue we need to dlsym the platform-only
+        // APIs we need, like JniInvocationInit.
+        "libnativehelper_lazy",
+    ],
     test_config_template: ":art-gtests-target-standalone-cts-template",
     test_suites: ["cts"],
 }
diff --git a/libartpalette/apex/palette_test.cc b/libartpalette/apex/palette_test.cc
index e961396..f2b4858 100644
--- a/libartpalette/apex/palette_test.cc
+++ b/libartpalette/apex/palette_test.cc
@@ -29,6 +29,7 @@
 #endif
 
 #include "gtest/gtest.h"
+#include "nativehelper/JniInvocation.h"                                 \
 
 namespace {
 
@@ -96,6 +97,11 @@
   bool enabled;
   EXPECT_EQ(PALETTE_STATUS_OK, PaletteShouldReportJniInvocations(&enabled));
 
+  // This calls JniInvocationInit, which is necessary to load the VM. It's not
+  // public but still stable.
+  JniInvocation jni_invocation;
+  ASSERT_TRUE(jni_invocation.Init(nullptr));
+
   JavaVMInitArgs vm_args;
   JavaVMOption options[] = {
       {.optionString = "-verbose:jni", .extraInfo = nullptr},
diff --git a/test/Android.bp b/test/Android.bp
index 6bc5990..5fc8cf9 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -343,6 +343,43 @@
     ],
 }
 
+// Variant of art_standalone_gtest_defaults that doesn't link dynamically to any
+// internal ART libraries.
+art_cc_defaults {
+    name: "art_cts_gtest_defaults",
+    defaults: [
+        // Note: We don't include "art_debug_defaults" here, as standalone ART
+        // gtests link with the "non-d" versions of the libraries contained in
+        // the ART APEX, so that they can be used with all ART APEX flavors
+        // (including the Release ART APEX).
+        "art_standalone_test_defaults",
+        "art_gtest_common_defaults",
+    ],
+    gtest: true,
+
+    // Support multilib variants (using different suffix per sub-architecture), which is needed on
+    // build targets with secondary architectures, as the MTS test suite packaging logic flattens
+    // all test artifacts into a single `testcases` directory.
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    static_libs: [
+        "libartbase",
+    ],
+
+    test_suites: [
+        "general-tests",
+        "mts-art",
+    ],
+}
+
 // Properties common to `libart-gtest-defaults` and `libartd-gtest-defaults`.
 art_cc_defaults {
     name: "libart-gtest-common-defaults",