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

Commit 300617fe authored by John Wu's avatar John Wu Committed by Android (Google) Code Review
Browse files

Merge changes I4ee50e0b,Ic0659538 into main

* changes:
  [Ravenwood] Load native code without class load hook
  [Ravenwood] Always initialize Ravenwood environment
parents ce3e6e24 008072aa
Loading
Loading
Loading
Loading
+36 −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.ravenwoodtest.bivalenttest;

import static org.junit.Assert.assertNotNull;

import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;

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

/**
 * Test to make sure the environment is still initialized when no config and no rules are set.
 */
@RunWith(AndroidJUnit4.class)
public class RavenwoodNoConfigNoRuleTest {

    @Test
    public void testInitialization() {
        assertNotNull(InstrumentationRegistry.getInstrumentation());
    }
}
+8 −39
Original line number Diff line number Diff line
@@ -15,8 +15,6 @@
 */
package android.platform.test.ravenwood;

import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;

import static org.junit.Assert.fail;

import android.os.Bundle;
@@ -27,10 +25,6 @@ import android.util.Log;

import androidx.test.platform.app.InstrumentationRegistry;

import com.android.internal.os.RuntimeInit;
import com.android.platform.test.ravenwood.runtimehelper.ClassLoadHook;
import com.android.ravenwood.common.RavenwoodCommonUtils;

import org.junit.runner.Description;
import org.junit.runners.model.TestClass;

@@ -49,15 +43,17 @@ public class RavenwoodAwareTestRunnerHook {
    private RavenwoodAwareTestRunnerHook() {
    }

    /**
     * Called before any code starts. Internally it will only initialize the environment once.
     */
    public static void performGlobalInitialization() {
        RavenwoodRuntimeEnvironmentController.globalInitOnce();
    }

    /**
     * Called when a runner starts, before the inner runner gets a chance to run.
     */
    public static void onRunnerInitializing(RavenwoodAwareTestRunner runner, TestClass testClass) {
        // TODO: Move the initialization code to a better place.

        initOnce();

        // This log call also ensures the framework JNI is loaded.
        Log.i(TAG, "onRunnerInitializing: testClass=" + testClass.getJavaClass()
                + " runner=" + runner);

@@ -65,33 +61,6 @@ public class RavenwoodAwareTestRunnerHook {
        InstrumentationRegistry.registerInstance(null, Bundle.EMPTY);
    }

    private static boolean sInitialized = false;

    private static void initOnce() {
        if (sInitialized) {
            return;
        }
        sInitialized = true;

        // We haven't initialized liblog yet, so directly write to System.out here.
        RavenwoodCommonUtils.log(TAG, "initOnce()");

        // Make sure libandroid_runtime is loaded.
        ClassLoadHook.onClassLoaded(Log.class);

        // Redirect stdout/stdin to liblog.
        RuntimeInit.redirectLogStreams();

        // This will let AndroidJUnit4 use the original runner.
        System.setProperty("android.junit.runner",
                "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
        System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");

        // Do the basic set up for the android sysprops.
        RavenwoodRuntimeEnvironmentController.setSystemProperties(
                RavenwoodSystemProperties.DEFAULT_VALUES);
    }

    /**
     * Called when a whole test class is skipped.
     */
+134 −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 android.platform.test.ravenwood;

import com.android.ravenwood.common.RavenwoodCommonUtils;

import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * We use this class to load libandroid_runtime.
 * In the future, we may load other native libraries.
 */
public final class RavenwoodNativeLoader {
    public static final String CORE_NATIVE_CLASSES = "core_native_classes";
    public static final String ICU_DATA_PATH = "icu.data.path";
    public static final String KEYBOARD_PATHS = "keyboard_paths";
    public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes";

    public static final String LIBANDROID_RUNTIME_NAME = "android_runtime";

    /**
     * Classes with native methods that are backed by libandroid_runtime.
     *
     * See frameworks/base/core/jni/platform/host/HostRuntime.cpp
     */
    private static final Class<?>[] sLibandroidClasses = {
            android.util.Log.class,
            android.os.Parcel.class,
            android.os.Binder.class,
            android.content.res.ApkAssets.class,
            android.content.res.AssetManager.class,
            android.content.res.StringBlock.class,
            android.content.res.XmlBlock.class,
    };

    /**
     * Classes with native methods that are backed by libhwui.
     *
     * See frameworks/base/libs/hwui/apex/LayoutlibLoader.cpp
     */
    private static final Class<?>[] sLibhwuiClasses = {
            android.graphics.Interpolator.class,
            android.graphics.Matrix.class,
            android.graphics.Path.class,
            android.graphics.Color.class,
            android.graphics.ColorSpace.class,
    };

    /**
     * Extra strings needed to pass to register_android_graphics_classes().
     *
     * `android.graphics.Graphics` is not actually a class, so we just hardcode it here.
     */
    public final static String[] GRAPHICS_EXTRA_INIT_PARAMS = new String[] {
            "android.graphics.Graphics"
    };

    private RavenwoodNativeLoader() {
    }

    private static void log(String message) {
        System.out.println("RavenwoodNativeLoader: " + message);
    }

    private static void log(String fmt, Object... args) {
        log(String.format(fmt, args));
    }

    private static void ensurePropertyNotSet(String key) {
        if (System.getProperty(key) != null) {
            throw new RuntimeException("System property \"" + key + "\" is set unexpectedly");
        }
    }

    private static void setProperty(String key, String value) {
        System.setProperty(key, value);
        log("Property set: %s=\"%s\"", key, value);
    }

    private static void dumpSystemProperties() {
        for (var prop : System.getProperties().entrySet()) {
            log("  %s=\"%s\"", prop.getKey(), prop.getValue());
        }
    }

    /**
     * libandroid_runtime uses Java's system properties to decide what JNI methods to set up.
     * Set up these properties and load the native library
     */
    public static void loadFrameworkNativeCode() {
        if ("1".equals(System.getenv("RAVENWOOD_DUMP_PROPERTIES"))) {
            log("Java system properties:");
            dumpSystemProperties();
        }

        // Make sure these properties are not set.
        ensurePropertyNotSet(CORE_NATIVE_CLASSES);
        ensurePropertyNotSet(ICU_DATA_PATH);
        ensurePropertyNotSet(KEYBOARD_PATHS);
        ensurePropertyNotSet(GRAPHICS_NATIVE_CLASSES);

        // Build the property values
        final var joiner = Collectors.joining(",");
        final var libandroidClasses =
                Arrays.stream(sLibandroidClasses).map(Class::getName).collect(joiner);
        final var libhwuiClasses = Stream.concat(
                Arrays.stream(sLibhwuiClasses).map(Class::getName),
                Arrays.stream(GRAPHICS_EXTRA_INIT_PARAMS)
        ).collect(joiner);

        // Load the libraries
        setProperty(CORE_NATIVE_CLASSES, libandroidClasses);
        setProperty(GRAPHICS_NATIVE_CLASSES, libhwuiClasses);
        log("Loading " + LIBANDROID_RUNTIME_NAME + " for '" + libandroidClasses + "' and '"
                + libhwuiClasses + "'");
        RavenwoodCommonUtils.loadJniLibrary(LIBANDROID_RUNTIME_NAME);
    }
}
+28 −18
Original line number Diff line number Diff line
@@ -63,6 +63,7 @@ public final class RavenwoodRunnerState {

    private RavenwoodConfig mCurrentConfig;
    private RavenwoodRule mCurrentRule;
    private boolean mHasRavenwoodRule;

    public Description getClassDescription() {
        return mClassDescription;
@@ -71,6 +72,7 @@ public final class RavenwoodRunnerState {
    public void enterTestClass(Description classDescription) throws IOException {
        mClassDescription = classDescription;

        mHasRavenwoodRule = hasRavenwoodRule(mRunner.getTestClass().getJavaClass());
        mCurrentConfig = extractConfiguration(mRunner.getTestClass().getJavaClass());

        if (mCurrentConfig != null) {
@@ -97,9 +99,13 @@ public final class RavenwoodRunnerState {
    }

    public void enterRavenwoodRule(RavenwoodRule rule) throws IOException {
        if (!mHasRavenwoodRule) {
            fail("If you have a RavenwoodRule in your test, make sure the field type is"
                    + " RavenwoodRule so Ravenwood can detect it.");
        }
        if (mCurrentConfig != null) {
            fail("RavenwoodConfiguration and RavenwoodRule cannot be used in the same class."
                    + " Suggest migrating to RavenwoodConfiguration.");
            fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
                    + " Suggest migrating to RavenwoodConfig.");
        }
        if (mCurrentRule != null) {
            fail("Multiple nesting RavenwoodRule's are detected in the same class,"
@@ -125,16 +131,20 @@ public final class RavenwoodRunnerState {
     * @return a configuration from a test class, if any.
     */
    @Nullable
    private static RavenwoodConfig extractConfiguration(Class<?> testClass) {
        final boolean hasRavenwoodRule = hasRavenwoodRule(testClass);

    private RavenwoodConfig extractConfiguration(Class<?> testClass) {
        var field = findConfigurationField(testClass);
        if (field == null) {
            if (mHasRavenwoodRule) {
                // Should be handled by RavenwoodRule
                return null;
            }
        if (hasRavenwoodRule) {
            fail("RavenwoodConfiguration and RavenwoodRule cannot be used in the same class."
                    + " Suggest migrating to RavenwoodConfiguration.");

            // If no RavenwoodConfig and no RavenwoodRule, return a default config
            return new RavenwoodConfig.Builder().build();
        }
        if (mHasRavenwoodRule) {
            fail("RavenwoodConfig and RavenwoodRule cannot be used in the same class."
                    + " Suggest migrating to RavenwoodConfig.");
        }

        try {
@@ -155,7 +165,7 @@ public final class RavenwoodRunnerState {
    private static boolean hasRavenwoodRule(Class<?> testClass) {
        for (var field : testClass.getDeclaredFields()) {
            if (!field.isAnnotationPresent(Rule.class)
                    && field.isAnnotationPresent(ClassRule.class)) {
                    && !field.isAnnotationPresent(ClassRule.class)) {
                continue;
            }
            if (field.getType().equals(RavenwoodRule.class)) {
@@ -165,7 +175,7 @@ public final class RavenwoodRunnerState {
        // JUnit supports rules as methods, so we need to check them too.
        for (var method : testClass.getDeclaredMethods()) {
            if (!method.isAnnotationPresent(Rule.class)
                    && method.isAnnotationPresent(ClassRule.class)) {
                    && !method.isAnnotationPresent(ClassRule.class)) {
                continue;
            }
            if (method.getReturnType().equals(RavenwoodRule.class)) {
@@ -180,8 +190,8 @@ public final class RavenwoodRunnerState {
    }

    /**
     * Find and return a field with @RavenwoodConfiguration.Config, which must be of type
     * RavenwoodConfiguration.
     * Find and return a field with @RavenwoodConfig.Config, which must be of type
     * RavenwoodConfig.
     */
    @Nullable
    private static Field findConfigurationField(Class<?> testClass) {
@@ -198,7 +208,7 @@ public final class RavenwoodRunnerState {
                        fail(String.format(
                                "Class %s has multiple fields with %s",
                                testClass.getCanonicalName(),
                                "@RavenwoodConfiguration.Config"));
                                "@RavenwoodConfig.Config"));
                    }
                    // Make sure it's static public
                    ensureIsPublicMember(field, true);
@@ -209,8 +219,8 @@ public final class RavenwoodRunnerState {
                            "Field %s.%s has %s but type is not %s",
                            testClass.getCanonicalName(),
                            field.getName(),
                            "@RavenwoodConfiguration.Config",
                            "RavenwoodConfiguration"));
                            "@RavenwoodConfig.Config",
                            "RavenwoodConfig"));
                    return null; // unreachable
                }
            } else {
@@ -219,8 +229,8 @@ public final class RavenwoodRunnerState {
                            "Field %s.%s does not have %s but type is %s",
                            testClass.getCanonicalName(),
                            field.getName(),
                            "@RavenwoodConfiguration.Config",
                            "RavenwoodConfiguration"));
                            "@RavenwoodConfig.Config",
                            "RavenwoodConfig"));
                    return null; // unreachable
                } else {
                    // Unrelated field, ignore.
+42 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.platform.test.ravenwood;

import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING;
import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP;

import static org.junit.Assert.fail;

@@ -31,10 +32,14 @@ import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.ServiceManager;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;

import androidx.test.platform.app.InstrumentationRegistry;

import com.android.internal.os.RuntimeInit;
import com.android.ravenwood.common.RavenwoodCommonUtils;
import com.android.ravenwood.common.RavenwoodRuntimeException;
import com.android.ravenwood.common.SneakyThrow;
import com.android.server.LocalServices;
@@ -114,6 +119,43 @@ public class RavenwoodRuntimeEnvironmentController {
    }

    private static RavenwoodConfig sConfig;
    private static boolean sInitialized = false;

    /**
     * Initialize the global environment.
     */
    public static void globalInitOnce() {
        if (sInitialized) {
            return;
        }
        sInitialized = true;

        // We haven't initialized liblog yet, so directly write to System.out here.
        RavenwoodCommonUtils.log(TAG, "globalInit()");

        // Do the basic set up for the android sysprops.
        setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES);

        // Make sure libandroid_runtime is loaded.
        RavenwoodNativeLoader.loadFrameworkNativeCode();

        // Redirect stdout/stdin to liblog.
        RuntimeInit.redirectLogStreams();

        if (RAVENWOOD_VERBOSE_LOGGING) {
            RavenwoodCommonUtils.log(TAG, "Force enabling verbose logging");
            try {
                Os.setenv("ANDROID_LOG_TAGS", "*:v", true);
            } catch (ErrnoException e) {
                // Shouldn't happen.
            }
        }

        System.setProperty(RAVENWOOD_VERSION_JAVA_SYSPROP, "1");
        // This will let AndroidJUnit4 use the original runner.
        System.setProperty("android.junit.runner",
                "androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner");
    }

    /**
     * Initialize the environment.
Loading