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

Commit 9ac0521c authored by Jeff Sharkey's avatar Jeff Sharkey
Browse files

Ravenwood support for feature flags.

Since feature flags are becoming an important part of development
work, ensure that Ravenwood tests can use existing tools such as
`SetFlagsRule` to populate values for code-under-test to consume.

To enable this, we extend `AndroidHeuristicsFilter` to recognize
feature flags generated code, and include that in the Ravenwood
environment by default.

Bug: 311370221
Test: atest FrameworksCoreTestsRavenwood FrameworksCoreTests
Change-Id: I526af65f816d153cb6e365cf6810a5a304d3e6a6
parent 7178e2b9
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -179,8 +179,10 @@ android_ravenwood_test {
        "androidx.test.ext.junit",
        "mockito_ravenwood",
        "platform-test-annotations",
        "flag-junit",
    ],
    srcs: [
        "src/android/os/BuildTest.java",
        "src/android/os/FileUtilsTest.java",
        "src/android/util/**/*.java",
        "src/com/android/internal/util/**/*.java",
+35 −3
Original line number Diff line number Diff line
@@ -16,19 +16,37 @@

package android.os;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.flag.junit.SetFlagsRule;
import android.platform.test.ravenwood.RavenwoodRule;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import junit.framework.Assert;
import junit.framework.TestCase;

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

/**
 * Provides test cases for android.os.Build and, in turn, many of the
 * system properties set by the build system.
 */
public class BuildTest extends TestCase {

@RunWith(AndroidJUnit4.class)
public class BuildTest {
    private static final String TAG = "BuildTest";

    @Rule
    public final RavenwoodRule mRavenwood = new RavenwoodRule();

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    /**
     * Asserts that a String is non-null and non-empty.  If it is not,
     * an AssertionFailedError is thrown with the given message.
@@ -50,7 +68,9 @@ public class BuildTest extends TestCase {
    /**
     * Asserts that all android.os.Build fields are non-empty and/or in a valid range.
     */
    @Test
    @SmallTest
    @IgnoreUnderRavenwood(blockedBy = Build.class)
    public void testBuildFields() throws Exception {
        assertNotEmpty("ID", Build.ID);
        assertNotEmpty("DISPLAY", Build.DISPLAY);
@@ -72,4 +92,16 @@ public class BuildTest extends TestCase {
        // (e.g., must be a C identifier, must be a valid filename, must not contain any spaces)
        // add tests for them.
    }

    @Test
    public void testFlagEnabled() throws Exception {
        mSetFlagsRule.enableFlags(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM);
        assertTrue(Flags.androidOsBuildVanillaIceCream());
    }

    @Test
    public void testFlagDisabled() throws Exception {
        mSetFlagsRule.disableFlags(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM);
        assertFalse(Flags.androidOsBuildVanillaIceCream());
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -3,6 +3,9 @@
# Keep all AIDL interfaces
class :aidl stubclass

# Keep all feature flag implementations
class :feature_flags stubclass

# Collections
class android.util.ArrayMap stubclass
class android.util.ArraySet stubclass
+23 −2
Original line number Diff line number Diff line
@@ -71,10 +71,10 @@ public class MyCodeTest {
Once you’ve defined your test, you can use typical commands to execute it locally:

```
$ atest MyTestsRavenwood
$ atest --host MyTestsRavenwood
```

> **Note:** There's a known bug where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing.
> **Note:** There's a known bug where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing. Using the `--host` argument above is a way to bypass this requirement until bug #312525698 is fixed.

You can also run your new tests automatically via `TEST_MAPPING` rules like this:

@@ -89,6 +89,27 @@ You can also run your new tests automatically via `TEST_MAPPING` rules like this
}
```

## Strategies for feature flags

Ravenwood supports writing tests against logic that uses feature flags through the existing `SetFlagsRule` infrastructure maintained by the feature flagging team:

```
import android.platform.test.flag.junit.SetFlagsRule;

@RunWith(AndroidJUnit4.class)
public class MyCodeTest {
    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();

    @Test
    public void testEnabled() {
        mSetFlagsRule.enableFlags(Flags.FLAG_MY_FLAG);
        // verify test logic that depends on flag being enabled
    }
```

This naturally composes together well with any `RavenwoodRule` that your test may need.

## Strategies for migration/bivalent tests

Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can run on both a real Android device and under a Ravenwood environment.
+17 −1
Original line number Diff line number Diff line
@@ -23,12 +23,16 @@ import com.android.hoststubgen.asm.ClassNodes
class AndroidHeuristicsFilter(
        private val classes: ClassNodes,
        val aidlPolicy: FilterPolicyWithReason?,
        val featureFlagsPolicy: FilterPolicyWithReason?,
        fallback: OutputFilter
) : DelegatingFilter(fallback) {
    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
        if (aidlPolicy != null && classes.isAidlClass(className)) {
            return aidlPolicy
        }
        if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) {
            return featureFlagsPolicy
        }
        return super.getPolicyForClass(className)
    }
}
@@ -41,3 +45,15 @@ private fun ClassNodes.isAidlClass(className: String): Boolean {
            hasClass("$className\$Stub") &&
            hasClass("$className\$Stub\$Proxy")
}

/**
 * @return if a given class "seems like" an feature flags class.
 */
private fun ClassNodes.isFeatureFlagsClass(className: String): Boolean {
    // Matches template classes defined here:
    // https://cs.android.com/android/platform/superproject/+/master:build/make/tools/aconfig/templates/
    return className.endsWith("/Flags")
            || className.endsWith("/FeatureFlags")
            || className.endsWith("/FeatureFlagsImpl")
            || className.endsWith("/FakeFeatureFlagsImpl");
}
Loading