Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit be841ede authored by Makoto Onuki's avatar Makoto Onuki
Browse files

Adding test for loading JNI libraries in individual tests

Add a bivalent test that uses a native library.

Also added a utility method to load it in the same way on both
sides.

Bug: 318393625
Bug: 323931246

Test: run-ravenwood-tests.sh
Test: atest RavenwoodBivalentTest_device
Change-Id: I270058c15f718ff20640681742035e33e5e015a1
parent 8e4eb7ed
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
@@ -7,6 +7,30 @@ package {
    default_applicable_licenses: ["frameworks_base_license"],
}

cc_library_shared {
    name: "libravenwoodbivalenttest_jni",
    host_supported: true,

    cflags: [
        "-Wall",
        "-Werror",
        "-Wno-unused-parameter",
        "-Wthread-safety",
    ],

    srcs: [
        "jni/*.cpp",
    ],

    shared_libs: [
        "libbase",
        "liblog",
        "libnativehelper",
        "libutils",
        "libcutils",
    ],
}

android_ravenwood_test {
    name: "RavenwoodBivalentTest",

@@ -18,6 +42,9 @@ android_ravenwood_test {
    srcs: [
        "test/**/*.java",
    ],
    jni_libs: [
        "libravenwoodbivalenttest_jni",
    ],
    sdk_version: "test_current",
    auto_gen_config: true,
}
@@ -38,6 +65,9 @@ android_test {

        "ravenwood-junit",
    ],
    jni_libs: [
        "libravenwoodbivalenttest_jni",
    ],
    test_suites: [
        "device-tests",
    ],
+52 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 <nativehelper/JNIHelp.h>
#include "jni.h"
#include "utils/Log.h"
#include "utils/misc.h"

static jint add(JNIEnv* env, jclass clazz, jint a, jint b) {
    return a + b;
}

static const JNINativeMethod sMethods[] =
{
    { "add", "(II)I", (void*)add },
};

extern "C" jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
    JNIEnv* env = NULL;
    jint result = -1;

    if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
        ALOGE("GetEnv failed!");
        return result;
    }
    ALOG_ASSERT(env, "Could not retrieve the env!");

    ALOGI("%s: JNI_OnLoad", __FILE__);

    int res = jniRegisterNativeMethods(env,
            "com/android/platform/test/ravenwood/bivalenttest/RavenwoodJniTest",
            sMethods, NELEM(sMethods));
    if (res < 0) {
        return res;
    }

    return JNI_VERSION_1_4;
}
+44 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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 com.android.platform.test.ravenwood.bivalenttest;

import static junit.framework.Assert.assertEquals;

import android.platform.test.ravenwood.RavenwoodRule;
import android.platform.test.ravenwood.RavenwoodUtils;

import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public final class RavenwoodJniTest {
    static {
        RavenwoodUtils.loadJniLibrary("ravenwoodbivalenttest_jni");
    }

    @Rule
    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder().build();

    private static native int add(int a, int b);

    @Test
    public void testNativeMethod() {
        assertEquals(5, add(2, 3));
    }
}
+127 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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 android.platform.test.ravenwood;

import java.io.File;
import java.io.PrintStream;
import java.util.Arrays;

/**
 * Utilities for writing (bivalent) ravenwood tests.
 */
public class RavenwoodUtils {
    private RavenwoodUtils() {
    }

    /**
     * Load a JNI library respecting {@code java.library.path}
     * (which reflects {@code LD_LIBRARY_PATH}).
     *
     * <p>{@code libname} must be the library filename without:
     * - directory
     * - "lib" prefix
     * - and the ".so" extension
     *
     * <p>For example, in order to load "libmyjni.so", then pass "myjni".
     *
     * <p>This is basically the same thing as Java's {@link System#loadLibrary(String)},
     * but this API works slightly different on ART and on the desktop Java, namely
     * the desktop Java version uses a different entry point method name
     * {@code JNI_OnLoad_libname()} (note the included "libname")
     * while ART always seems to use {@code JNI_OnLoad()}.
     *
     * <p>This method provides the same behavior on both the device side and on Ravenwood --
     * it uses {@code JNI_OnLoad()} as the entry point name on both.
     */
    public static void loadJniLibrary(String libname) {
        if (RavenwoodRule.isOnRavenwood()) {
            loadLibraryOnRavenwood(libname);
        } else {
            // Just delegate to the loadLibrary().
            System.loadLibrary(libname);
        }
    }

    private static void loadLibraryOnRavenwood(String libname) {
        var path = System.getProperty("java.library.path");
        var filename = "lib" + libname + ".so";

        System.out.println("Looking for library " + libname + ".so in java.library.path:" + path);

        try {
            if (path == null) {
                throw new UnsatisfiedLinkError("Cannot load library " + libname + "."
                        + " Property java.library.path not set!");
            }
            for (var dir : path.split(":")) {
                var file = new File(dir + "/" + filename);
                if (file.exists()) {
                    System.load(file.getAbsolutePath());
                    return;
                }
            }
            throw new UnsatisfiedLinkError("Library " + libname + " no found in "
                    + "java.library.path: " + path);
        } catch (Exception e) {
            dumpFiles(System.out);
            throw e;
        }
    }

    private static void dumpFiles(PrintStream out) {
        try {
            var path = System.getProperty("java.library.path");
            out.println("# java.library.path=" + path);

            for (var dir : path.split(":")) {
                listFiles(out, new File(dir), "");

                var gparent = new File((new File(dir)).getAbsolutePath() + "../../..")
                        .getCanonicalFile();
                if (gparent.getName().contains("testcases")) {
                    // Special case: if we found this directory, dump its contents too.
                    listFiles(out, gparent, "");
                }
            }
        } catch (Throwable th) {
            out.println("Error: " + th.toString());
            th.printStackTrace(out);
        }
    }

    private static void listFiles(PrintStream out, File dir, String prefix) {
        if (!dir.isDirectory()) {
            out.println(prefix + dir.getAbsolutePath() + " is not a directory!");
            return;
        }
        out.println(prefix + ":" + dir.getAbsolutePath() + "/");
        // First, list the files.
        for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
            out.println(prefix + "  " + file.getName() + "" + (file.isDirectory() ? "/" : ""));
        }

        // Then recurse.
        if (dir.getAbsolutePath().startsWith("/usr") || dir.getAbsolutePath().startsWith("/lib")) {
            // There would be too many files, so don't recurse.
            return;
        }
        for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
            if (file.isDirectory()) {
                listFiles(out, file, prefix + "  ");
            }
        }
    }
}