xref: /aosp_15_r20/frameworks/base/ravenwood/test-authors.md (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1*d57664e9SAndroid Build Coastguard Worker# Ravenwood for Test Authors
2*d57664e9SAndroid Build Coastguard Worker
3*d57664e9SAndroid Build Coastguard WorkerThe Ravenwood testing environment runs inside a single Java process on the host side, and provides a limited yet growing set of Android API functionality.
4*d57664e9SAndroid Build Coastguard Worker
5*d57664e9SAndroid Build Coastguard WorkerRavenwood explicitly does not support “large” integration tests that expect a fully booted Android OS.  Instead, it’s more suited for “small” and “medium” tests where your code-under-test has been factored to remove dependencies on a fully booted device.
6*d57664e9SAndroid Build Coastguard Worker
7*d57664e9SAndroid Build Coastguard WorkerWhen writing tests under Ravenwood, all Android API symbols associated with your declared `sdk_version` are available to link against using, but unsupported APIs will throw an exception.  This design choice enables mocking of unsupported APIs, and supports sharing of test code to build “bivalent” test suites that run against either Ravenwood or a traditional device.
8*d57664e9SAndroid Build Coastguard Worker
9*d57664e9SAndroid Build Coastguard Worker## Manually running tests
10*d57664e9SAndroid Build Coastguard Worker
11*d57664e9SAndroid Build Coastguard WorkerTo run all Ravenwood tests, use:
12*d57664e9SAndroid Build Coastguard Worker
13*d57664e9SAndroid Build Coastguard Worker```
14*d57664e9SAndroid Build Coastguard Worker./frameworks/base/ravenwood/scripts/run-ravenwood-tests.sh
15*d57664e9SAndroid Build Coastguard Worker```
16*d57664e9SAndroid Build Coastguard Worker
17*d57664e9SAndroid Build Coastguard WorkerTo run a specific test, use "atest" as normal, selecting the test from a Ravenwood suite such as:
18*d57664e9SAndroid Build Coastguard Worker
19*d57664e9SAndroid Build Coastguard Worker```
20*d57664e9SAndroid Build Coastguard Workeratest CtsOsTestCasesRavenwood:ParcelTest\#testSetDataCapacityNegative
21*d57664e9SAndroid Build Coastguard Worker```
22*d57664e9SAndroid Build Coastguard Worker
23*d57664e9SAndroid Build Coastguard Worker## Typical test structure
24*d57664e9SAndroid Build Coastguard Worker
25*d57664e9SAndroid Build Coastguard WorkerBelow are the typical steps needed to add a straightforward “small” unit test:
26*d57664e9SAndroid Build Coastguard Worker
27*d57664e9SAndroid Build Coastguard Worker* Define an `android_ravenwood_test` rule in your `Android.bp` file:
28*d57664e9SAndroid Build Coastguard Worker
29*d57664e9SAndroid Build Coastguard Worker```
30*d57664e9SAndroid Build Coastguard Workerandroid_ravenwood_test {
31*d57664e9SAndroid Build Coastguard Worker    name: "MyTestsRavenwood",
32*d57664e9SAndroid Build Coastguard Worker    static_libs: [
33*d57664e9SAndroid Build Coastguard Worker        "androidx.annotation_annotation",
34*d57664e9SAndroid Build Coastguard Worker        "androidx.test.ext.junit",
35*d57664e9SAndroid Build Coastguard Worker        "androidx.test.rules",
36*d57664e9SAndroid Build Coastguard Worker    ],
37*d57664e9SAndroid Build Coastguard Worker    srcs: [
38*d57664e9SAndroid Build Coastguard Worker        "src/com/example/MyCode.java",
39*d57664e9SAndroid Build Coastguard Worker        "tests/src/com/example/MyCodeTest.java",
40*d57664e9SAndroid Build Coastguard Worker    ],
41*d57664e9SAndroid Build Coastguard Worker    sdk_version: "test_current",
42*d57664e9SAndroid Build Coastguard Worker    auto_gen_config: true,
43*d57664e9SAndroid Build Coastguard Worker}
44*d57664e9SAndroid Build Coastguard Worker```
45*d57664e9SAndroid Build Coastguard Worker
46*d57664e9SAndroid Build Coastguard Worker* Write your unit test just like you would for an Android device:
47*d57664e9SAndroid Build Coastguard Worker
48*d57664e9SAndroid Build Coastguard Worker```
49*d57664e9SAndroid Build Coastguard Workerimport android.platform.test.annotations.DisabledOnRavenwood;
50*d57664e9SAndroid Build Coastguard Workerimport android.platform.test.ravenwood.RavenwoodRule;
51*d57664e9SAndroid Build Coastguard Worker
52*d57664e9SAndroid Build Coastguard Workerimport androidx.test.ext.junit.runners.AndroidJUnit4;
53*d57664e9SAndroid Build Coastguard Worker
54*d57664e9SAndroid Build Coastguard Workerimport org.junit.Test;
55*d57664e9SAndroid Build Coastguard Workerimport org.junit.runner.RunWith;
56*d57664e9SAndroid Build Coastguard Worker
57*d57664e9SAndroid Build Coastguard Worker@RunWith(AndroidJUnit4.class)
58*d57664e9SAndroid Build Coastguard Workerpublic class MyCodeTest {
59*d57664e9SAndroid Build Coastguard Worker    @Test
60*d57664e9SAndroid Build Coastguard Worker    public void testSimple() {
61*d57664e9SAndroid Build Coastguard Worker        // ...
62*d57664e9SAndroid Build Coastguard Worker    }
63*d57664e9SAndroid Build Coastguard Worker}
64*d57664e9SAndroid Build Coastguard Worker```
65*d57664e9SAndroid Build Coastguard Worker
66*d57664e9SAndroid Build Coastguard Worker* 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:
67*d57664e9SAndroid Build Coastguard Worker
68*d57664e9SAndroid Build Coastguard Worker```
69*d57664e9SAndroid Build Coastguard Workerimport android.platform.test.annotations.DisabledOnRavenwood;
70*d57664e9SAndroid Build Coastguard Workerimport android.platform.test.ravenwood.RavenwoodRule;
71*d57664e9SAndroid Build Coastguard Worker
72*d57664e9SAndroid Build Coastguard Workerimport androidx.test.runner.AndroidJUnit4;
73*d57664e9SAndroid Build Coastguard Worker
74*d57664e9SAndroid Build Coastguard Workerimport org.junit.Test;
75*d57664e9SAndroid Build Coastguard Workerimport org.junit.runner.RunWith;
76*d57664e9SAndroid Build Coastguard Worker
77*d57664e9SAndroid Build Coastguard Worker@RunWith(AndroidJUnit4.class)
78*d57664e9SAndroid Build Coastguard Workerpublic class MyCodeTest {
79*d57664e9SAndroid Build Coastguard Worker    @Rule
80*d57664e9SAndroid Build Coastguard Worker    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
81*d57664e9SAndroid Build Coastguard Worker            .setProcessApp()
82*d57664e9SAndroid Build Coastguard Worker            .setProvideMainThread(true)
83*d57664e9SAndroid Build Coastguard Worker            .build();
84*d57664e9SAndroid Build Coastguard Worker```
85*d57664e9SAndroid Build Coastguard Worker
86*d57664e9SAndroid Build Coastguard WorkerOnce you’ve defined your test, you can use typical commands to execute it locally:
87*d57664e9SAndroid Build Coastguard Worker
88*d57664e9SAndroid Build Coastguard Worker```
89*d57664e9SAndroid Build Coastguard Worker$ atest --host MyTestsRavenwood
90*d57664e9SAndroid Build Coastguard Worker```
91*d57664e9SAndroid Build Coastguard Worker
92*d57664e9SAndroid Build Coastguard Worker> **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.
93*d57664e9SAndroid Build Coastguard Worker
94*d57664e9SAndroid Build Coastguard WorkerYou can also run your new tests automatically via `TEST_MAPPING` rules like this:
95*d57664e9SAndroid Build Coastguard Worker
96*d57664e9SAndroid Build Coastguard Worker```
97*d57664e9SAndroid Build Coastguard Worker{
98*d57664e9SAndroid Build Coastguard Worker  "ravenwood-presubmit": [
99*d57664e9SAndroid Build Coastguard Worker    {
100*d57664e9SAndroid Build Coastguard Worker      "name": "MyTestsRavenwood",
101*d57664e9SAndroid Build Coastguard Worker      "host": true
102*d57664e9SAndroid Build Coastguard Worker    }
103*d57664e9SAndroid Build Coastguard Worker  ]
104*d57664e9SAndroid Build Coastguard Worker}
105*d57664e9SAndroid Build Coastguard Worker```
106*d57664e9SAndroid Build Coastguard Worker
107*d57664e9SAndroid Build Coastguard Worker> **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.
108*d57664e9SAndroid Build Coastguard Worker
109*d57664e9SAndroid Build Coastguard Worker## Strategies for migration/bivalent tests
110*d57664e9SAndroid Build Coastguard Worker
111*d57664e9SAndroid Build Coastguard WorkerRavenwood 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.
112*d57664e9SAndroid Build Coastguard Worker
113*d57664e9SAndroid Build Coastguard WorkerIn 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.
114*d57664e9SAndroid Build Coastguard Worker
115*d57664e9SAndroid Build Coastguard WorkerTest 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.
116*d57664e9SAndroid Build Coastguard Worker
117*d57664e9SAndroid Build Coastguard Worker```
118*d57664e9SAndroid Build Coastguard Worker@RunWith(AndroidJUnit4.class)
119*d57664e9SAndroid Build Coastguard Workerpublic class MyCodeTest {
120*d57664e9SAndroid Build Coastguard Worker    @Rule
121*d57664e9SAndroid Build Coastguard Worker    public final RavenwoodRule mRavenwood = new RavenwoodRule();
122*d57664e9SAndroid Build Coastguard Worker
123*d57664e9SAndroid Build Coastguard Worker    @Test
124*d57664e9SAndroid Build Coastguard Worker    public void testSimple() {
125*d57664e9SAndroid Build Coastguard Worker        // Simple test that runs on both devices and Ravenwood
126*d57664e9SAndroid Build Coastguard Worker    }
127*d57664e9SAndroid Build Coastguard Worker
128*d57664e9SAndroid Build Coastguard Worker    @Test
129*d57664e9SAndroid Build Coastguard Worker    @DisabledOnRavenwood(blockedBy = PackageManager.class)
130*d57664e9SAndroid Build Coastguard Worker    public void testComplex() {
131*d57664e9SAndroid Build Coastguard Worker        // Complex test that runs on devices, but is ignored under Ravenwood
132*d57664e9SAndroid Build Coastguard Worker    }
133*d57664e9SAndroid Build Coastguard Worker}
134*d57664e9SAndroid Build Coastguard Worker```
135*d57664e9SAndroid Build Coastguard Worker
136*d57664e9SAndroid Build Coastguard WorkerAt 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:
137*d57664e9SAndroid Build Coastguard Worker
138*d57664e9SAndroid Build Coastguard Worker```
139*d57664e9SAndroid Build Coastguard Workerandroid_test {
140*d57664e9SAndroid Build Coastguard Worker    name: "MyTestsDevice",
141*d57664e9SAndroid Build Coastguard Worker    resource_dirs: ["res"],
142*d57664e9SAndroid Build Coastguard Worker...
143*d57664e9SAndroid Build Coastguard Worker
144*d57664e9SAndroid Build Coastguard Workerandroid_ravenwood_test {
145*d57664e9SAndroid Build Coastguard Worker    name: "MyTestsRavenwood",
146*d57664e9SAndroid Build Coastguard Worker    srcs: [
147*d57664e9SAndroid Build Coastguard Worker        ":MyTestsDevice{.aapt.srcjar}",
148*d57664e9SAndroid Build Coastguard Worker...
149*d57664e9SAndroid Build Coastguard Worker```
150*d57664e9SAndroid Build Coastguard Worker
151*d57664e9SAndroid Build Coastguard Worker## Strategies for unsupported APIs
152*d57664e9SAndroid Build Coastguard Worker
153*d57664e9SAndroid Build Coastguard WorkerAs 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:
154*d57664e9SAndroid Build Coastguard Worker
155*d57664e9SAndroid Build Coastguard Worker* Your code-under-test may benefit from subtle dependency refactoring to reduce coupling.  (For example, providing a specific `File` argument instead of deriving paths internally from a `Context` or `Environment`.)
156*d57664e9SAndroid Build Coastguard Worker    * One common use-case is providing a directory for your test to store temporary files, which can easily be accomplished using the `Files.createTempDirectory()` API which works on both physical devices and under Ravenwood:
157*d57664e9SAndroid Build Coastguard Worker
158*d57664e9SAndroid Build Coastguard Worker```
159*d57664e9SAndroid Build Coastguard Workerimport java.nio.file.Files;
160*d57664e9SAndroid Build Coastguard Worker
161*d57664e9SAndroid Build Coastguard Worker@RunWith(AndroidJUnit4.class)
162*d57664e9SAndroid Build Coastguard Workerpublic class MyTest {
163*d57664e9SAndroid Build Coastguard Worker    @Before
164*d57664e9SAndroid Build Coastguard Worker    public void setUp() throws Exception {
165*d57664e9SAndroid Build Coastguard Worker        File tempDir = Files.createTempDirectory("MyTest").toFile();
166*d57664e9SAndroid Build Coastguard Worker...
167*d57664e9SAndroid Build Coastguard Worker```
168*d57664e9SAndroid Build Coastguard Worker
169*d57664e9SAndroid Build Coastguard Worker* Although mocking code that your team doesn’t own is a generally discouraged testing practice, it can be a valuable pressure relief valve when a dependency isn’t yet supported.
170*d57664e9SAndroid Build Coastguard Worker
171*d57664e9SAndroid Build Coastguard Worker## Strategies for debugging test development
172*d57664e9SAndroid Build Coastguard Worker
173*d57664e9SAndroid Build Coastguard WorkerWhen writing tests you may encounter odd or hard to debug behaviors.  One good place to start is at the beginning of the logs stored by atest:
174*d57664e9SAndroid Build Coastguard Worker
175*d57664e9SAndroid Build Coastguard Worker```
176*d57664e9SAndroid Build Coastguard Worker$ atest MyTestsRavenwood
177*d57664e9SAndroid Build Coastguard Worker...
178*d57664e9SAndroid Build Coastguard WorkerTest Logs have saved in /tmp/atest_result/20231128_094010_0e90t8v8/log
179*d57664e9SAndroid Build Coastguard WorkerRun 'atest --history' to review test result history.
180*d57664e9SAndroid Build Coastguard Worker```
181*d57664e9SAndroid Build Coastguard Worker
182*d57664e9SAndroid Build Coastguard WorkerThe most useful logs are in the `isolated-java-logs` text file, which can typically be tab-completed by copy-pasting the logs path mentioned in the atest output:
183*d57664e9SAndroid Build Coastguard Worker
184*d57664e9SAndroid Build Coastguard Worker```
185*d57664e9SAndroid Build Coastguard Worker$ less /tmp/atest_result/20231128_133105_h9al__79/log/i*/i*/isolated-java-logs*
186*d57664e9SAndroid Build Coastguard Worker```
187*d57664e9SAndroid Build Coastguard Worker
188*d57664e9SAndroid Build Coastguard WorkerHere are some common known issues and recommended workarounds:
189*d57664e9SAndroid Build Coastguard Worker
190*d57664e9SAndroid Build Coastguard Worker* 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()`.
191*d57664e9SAndroid Build Coastguard Worker* 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.
192*d57664e9SAndroid Build Coastguard Worker    * This may also manifest as very odd behavior, such as test not being executed at all, tracked by bug #312517322
193*d57664e9SAndroid Build Coastguard Worker    * This may also manifest as an obscure Mockito error claiming “Mockito can only mock non-private & non-final classes”
194