Add tests for loading native libraries in various locations through
absolute paths (reland).

This relands https://r.android.com/2304014 with a more reliable way of
determining the native library directory ("lib" or "lib64").

Test: atest libnativeloader_e2e_tests
Bug: 258340826
Change-Id: I8823c93cafa4747ef24ee57a3fcfeebe0f5788c7
diff --git a/libnativeloader/test/src/android/test/app/DataAppTest.java b/libnativeloader/test/src/android/test/app/DataAppTest.java
index 922565e..2e60ff0 100644
--- a/libnativeloader/test/src/android/test/app/DataAppTest.java
+++ b/libnativeloader/test/src/android/test/app/DataAppTest.java
@@ -81,4 +81,16 @@
         TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("product_private5"));
         VendorSharedLib.loadLibrary("vendor_private5");
     }
+
+    @Test
+    public void testLoadPrivateLibrariesWithAbsolutePaths() {
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system", "system_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system_ext", "systemext_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/product", "product_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/vendor", "vendor_private6")));
+    }
 }
diff --git a/libnativeloader/test/src/android/test/app/ProductAppTest.java b/libnativeloader/test/src/android/test/app/ProductAppTest.java
index 7714e10..11c34a6 100644
--- a/libnativeloader/test/src/android/test/app/ProductAppTest.java
+++ b/libnativeloader/test/src/android/test/app/ProductAppTest.java
@@ -81,4 +81,15 @@
         TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("product_private5"));
         VendorSharedLib.loadLibrary("vendor_private5");
     }
+
+    @Test
+    public void testLoadPrivateLibrariesWithAbsolutePaths() {
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system", "system_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system_ext", "systemext_private6")));
+        System.load(TestUtils.libPath("/product", "product_private6"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/vendor", "vendor_private6")));
+    }
 }
diff --git a/libnativeloader/test/src/android/test/app/SystemAppTest.java b/libnativeloader/test/src/android/test/app/SystemAppTest.java
index 7c78e1b..12e70fa 100644
--- a/libnativeloader/test/src/android/test/app/SystemAppTest.java
+++ b/libnativeloader/test/src/android/test/app/SystemAppTest.java
@@ -79,4 +79,14 @@
         TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("product_private5"));
         TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("vendor_private5"));
     }
+
+    @Test
+    public void testLoadPrivateLibrariesWithAbsolutePaths() {
+        System.load(TestUtils.libPath("/system", "system_private6"));
+        System.load(TestUtils.libPath("/system_ext", "systemext_private6"));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/product", "product_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/vendor", "vendor_private6")));
+    }
 }
diff --git a/libnativeloader/test/src/android/test/app/VendorAppTest.java b/libnativeloader/test/src/android/test/app/VendorAppTest.java
index 0a392c1..f5f12e8 100644
--- a/libnativeloader/test/src/android/test/app/VendorAppTest.java
+++ b/libnativeloader/test/src/android/test/app/VendorAppTest.java
@@ -80,4 +80,15 @@
         TestUtils.assertLibraryNotFound(() -> VendorSharedLib.loadLibrary("product_private5"));
         VendorSharedLib.loadLibrary("vendor_private5");
     }
+
+    @Test
+    public void testLoadPrivateLibrariesWithAbsolutePaths() {
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system", "system_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/system_ext", "systemext_private6")));
+        TestUtils.assertLinkerNamespaceError(
+                () -> System.load(TestUtils.libPath("/product", "product_private6")));
+        System.load(TestUtils.libPath("/vendor", "vendor_private6"));
+    }
 }
diff --git a/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java b/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java
index 5ba96cc..5e825dd 100644
--- a/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java
+++ b/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java
@@ -30,6 +30,7 @@
 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
 import com.android.tradefed.util.CommandResult;
 import com.google.common.io.ByteStreams;
 import java.io.File;
@@ -119,33 +120,41 @@
     public void testSystemPrivApp() throws Exception {
         // There's currently no difference in the tests between /system/priv-app and /system/app, so
         // let's reuse the same one.
-        runDeviceTests("android.test.app.system_priv", "android.test.app.SystemAppTest");
+        runTests("android.test.app.system_priv", "android.test.app.SystemAppTest");
     }
 
     @Test
     public void testSystemApp() throws Exception {
-        runDeviceTests("android.test.app.system", "android.test.app.SystemAppTest");
+        runTests("android.test.app.system", "android.test.app.SystemAppTest");
     }
 
     @Test
     public void testSystemExtApp() throws Exception {
         // /system_ext should behave the same as /system, so run the same test class there.
-        runDeviceTests("android.test.app.system_ext", "android.test.app.SystemAppTest");
+        runTests("android.test.app.system_ext", "android.test.app.SystemAppTest");
     }
 
     @Test
     public void testProductApp() throws Exception {
-        runDeviceTests("android.test.app.product", "android.test.app.ProductAppTest");
+        runTests("android.test.app.product", "android.test.app.ProductAppTest");
     }
 
     @Test
     public void testVendorApp() throws Exception {
-        runDeviceTests("android.test.app.vendor", "android.test.app.VendorAppTest");
+        runTests("android.test.app.vendor", "android.test.app.VendorAppTest");
     }
 
     @Test
     public void testDataApp() throws Exception {
-        runDeviceTests("android.test.app.data", "android.test.app.DataAppTest");
+        runTests("android.test.app.data", "android.test.app.DataAppTest");
+    }
+
+    private void runTests(String pkgName, String testClassName) throws Exception {
+        DeviceContext ctx = new DeviceContext(getTestInformation());
+        var options = new DeviceTestRunOptions(pkgName)
+                              .setTestClassName(testClassName)
+                              .addInstrumentationArg("libDirName", ctx.libDirName());
+        runDeviceTests(options);
     }
 
     // Utility class that keeps track of a set of paths the need to be deleted after testing.
@@ -234,7 +243,7 @@
         void pushPrivateLibs(ZipFile libApk) throws Exception {
             // Push the libraries once for each test. Since we cannot unload them, we need a fresh
             // never-before-loaded library in each loadLibrary call.
-            for (int i = 1; i <= 5; ++i) {
+            for (int i = 1; i <= 6; ++i) {
                 pushNativeTestLib(libApk, "/system/${LIB}/libsystem_private" + i + ".so");
                 pushNativeTestLib(libApk, "/system_ext/${LIB}/libsystemext_private" + i + ".so");
                 pushNativeTestLib(libApk, "/product/${LIB}/libproduct_private" + i + ".so");
@@ -272,6 +281,10 @@
             return mTestArch;
         }
 
+        String libDirName() throws DeviceNotAvailableException {
+            return getTestArch().contains("64") ? "lib64" : "lib";
+        }
+
         // Pushes the given file contents to the device at the given destination path. destPath is
         // assumed to have no risk of overlapping with existing files, and is deleted in tearDown(),
         // along with any directory levels that had to be created.
@@ -305,8 +318,7 @@
                 libraryTempFile = writeStreamToTempFile("libnativeloader_testlib.so", inStream);
             }
 
-            String libDir = getTestArch().contains("64") ? "lib64" : "lib";
-            destPath = destPath.replace("${LIB}", libDir);
+            destPath = destPath.replace("${LIB}", libDirName());
 
             mCleanup.addPath(destPath);
             assertThat(mDevice.pushFile(libraryTempFile, destPath)).isTrue();
diff --git a/libnativeloader/test/src/android/test/lib/TestUtils.java b/libnativeloader/test/src/android/test/lib/TestUtils.java
index eda9993..1dd917f 100644
--- a/libnativeloader/test/src/android/test/lib/TestUtils.java
+++ b/libnativeloader/test/src/android/test/lib/TestUtils.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertThrows;
 
+import androidx.test.platform.app.InstrumentationRegistry;
 import org.junit.function.ThrowingRunnable;
 
 public final class TestUtils {
@@ -32,4 +33,9 @@
         assertThat(t.getMessage())
                 .containsMatch("dlopen failed: .* is not accessible for the namespace");
     }
+
+    public static String libPath(String dir, String libName) {
+        String libDirName = InstrumentationRegistry.getArguments().getString("libDirName");
+        return dir + "/" + libDirName + "/lib" + libName + ".so";
+    }
 }