// This method implementation runs as-is on devices, but calls to this
// method is redirected to MyComplexClass_ravenwood#doComplex()
// under Ravenwood.
}
@RavenwoodRedirect
public static void staticDoComplex(int i, int j, int k) {
// This method implementation runs as-is on devices, but calls to this
// method is redirected to MyComplexClass_ravenwood#staticDoComplex()
// under Ravenwood.
}
/**
* This class is only available in the Ravenwood environment.
*/
class MyComplexClass_ravenwood {
public static void doComplex(MyComplexClass obj, int i, int j, int k) {
// Because the original method is a non-static method, the current
// object instance of the original method (the "this" reference)
// will be passed over as the first argument of the redirection method,
// with the remaining arguments to follow.
}
public static void staticDoComplex(int i, int j, int k) {
// Because the original method is a static method, there is no current
// object instance, so the parameter list of the redirection method
// is the same as the original method.
}
}
```
@@ -74,21 +117,6 @@ For example, consider a constructor or static initializer that relies on unsuppo
## Strategies for JNI
At the moment, JNI isn't yet supported under Ravenwood, but you may still want to support APIs that are partially implemented with JNI. The current approach is to use the “replace” strategy to offer a pure-Java alternative implementation for any JNI-provided logic.
At the moment, JNI support is considered "experimental". To ensure that teams are well-supported, for any JNI usage, please reach out to ravenwood@ so we can offer design advice and help you onboard native methods. If a native method can be trivially re-implemented in pure-Java, using the replacement or redirection mechanisms described above is also a viable option.
Since this approach requires potentially complex re-implementation, it should only be considered for core infrastructure that is critical to unblocking widespread testing use-cases. Other less-common usages of JNI should instead wait for offical JNI support in the Ravenwood environment.
When a pure-Java implementation grows too large or complex to host within the original class, the `@RavenwoodNativeSubstitutionClass` annotation can be used to host it in a separate source file:
public static void nativeDoThing(long nativePtr) {
// ...
}
```
The main caveat regarding JNI support on Ravenwood is due to how native code is built for Ravenwood. Unlike Java/Kotlin code where Ravenwood directly uses the same intermediate artifacts when building the real target for Android, Ravenwood uses the "host" variant of native libraries. The "host" variant of native libraries usually either don't exist, are extremely limited, or behave drastically different compared to the Android variant.
* APIs available under Ravenwood are stateless by default. If your test requires explicit states (such as defining the UID you’re running under, or requiring a main `Looper` thread), add a `RavenwoodRule` to declare that:
public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
.setProcessApp()
.setProvideMainThread(true)
.build();
```
Once you’ve defined your test, you can use typical commands to execute it locally:
```
$ atest --host MyTestsRavenwood
$ atest MyTestsRavenwood
```
> **Note:** There's a known bug #312525698 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 the bug is fixed.
You can also run your new tests automatically via `TEST_MAPPING` rules like this:
```
@@ -104,22 +82,44 @@ You can also run your new tests automatically via `TEST_MAPPING` rules like this
}
```
> **Note:** There's a known bug #308854804 where `TEST_MAPPING` is not being applied, so we're currently planning to run all Ravenwood tests unconditionally in presubmit for changes to `frameworks/base/` and `cts/` until there is a better path forward.
## Using resources
At the moment, the `android_ravenwood_test` module type cannot directly build resources yet. In order to use resources in Ravenwood tests, you have to build the resource APK in a separate `android_app` module and "borrow" the resources and R classes:
```
android_app {
name: "MyTests-res",
resource_dirs: ["res"],
// This has to be set to false, or ".aapt.srcjar" will not be generated
use_resource_processor: false,
}
android_ravenwood_test {
name: "MyTestsRavenwood",
srcs: [
"src/**/*.java",
// ...
// Include R.java from the resource APK
":MyTests-res{.aapt.srcjar}",
],
// Set the resource APK
resource_apk: "MyTests-res",
// ...
}
```
## Strategies for migration/bivalent tests
Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can be dual-compiled to run on both a real Android device and under a Ravenwood environment.
In situations where a test method depends on API functionality not yet available under Ravenwood, we provide an annotation to quietly “ignore” that test under Ravenwood, while continuing to validate that test on real devices. The annotation can be applied to either individual methods or to an entire test class. Please note that your test class must declare a `RavenwoodRule` for the annotation to take effect.
In situations where a test method depends on API functionality not yet available under Ravenwood, we provide an annotation to quietly skip that test under Ravenwood, while continuing to validate that test on real devices. The annotation can be applied to either individual methods or to an entire test class.
Test authors are encouraged to provide a `blockedBy` or `reason` argument to help future maintainers understand why a test is being ignored, and under what conditions it might be supported in the future.
```
@RunWith(AndroidJUnit4.class)
public class MyCodeTest {
@Rule
public final RavenwoodRule mRavenwood = new RavenwoodRule();
@Test
public void testSimple() {
// Simple test that runs on both devices and Ravenwood
// Complex test that runs on devices, but is ignored under Ravenwood
// Complex test that runs on devices, but is disabled under Ravenwood
}
}
```
At the moment, the `android.content.res.Resources` subsystem isn't yet supported under Ravenwood, but you may still want to dual-compile test suites that depend on references to resources. Below is a strategy for supporting dual-compiliation, where you can "borrow" the generated resource symbols from your traditional `android_test` target:
```
android_test {
name: "MyTestsDevice",
resource_dirs: ["res"],
...
android_ravenwood_test {
name: "MyTestsRavenwood",
srcs: [
":MyTestsDevice{.aapt.srcjar}",
...
```
## Strategies for unsupported APIs
As you write tests against Ravenwood, you’ll likely discover API dependencies that aren’t supported yet. Here’s a few strategies that can help you make progress:
@@ -187,7 +172,4 @@ $ less /tmp/atest_result/20231128_133105_h9al__79/log/i*/i*/isolated-java-logs*
Here are some common known issues and recommended workarounds:
* Some code may unconditionally interact with unsupported APIs, such as via static initializers. One strategy is to shift the logic into `@Before` methods and make it conditional by testing `RavenwoodRule.isUnderRavenwood()`.
* Some code may reference API symbols not yet present in the Ravenwood runtime, such as ART or ICU internals, or APIs from Mainline modules. One strategy is to refactor to avoid these internal dependencies, but Ravenwood aims to better support them soon.
* This may also manifest as very odd behavior, such as test not being executed at all, tracked by bug #312517322
* This may also manifest as an obscure Mockito error claiming “Mockito can only mock non-private & non-final classes”
* Some code may unconditionally interact with unsupported APIs, such as via static initializers. One strategy is to shift the logic into `@Before` methods and make it conditional by testing `RavenwoodRule.isOnRavenwood()`.